You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4398 lines
116 KiB
4398 lines
116 KiB
/**************************************************************************** |
|
* apps/n etutils/ftpd.c |
|
* |
|
* Copyright (C) 2012 Gregory Nutt. All rights reserved. |
|
* Author: Gregory Nutt <gnutt@nuttx.org> |
|
* |
|
* Includes original code as well as logic adapted from hwport_ftpd, written |
|
* by Jaehyuk Cho <minzkn@minzkn.com> which is released under a BSD license. |
|
* |
|
* Copyright (C) hwport.com. All rights reserved. |
|
* Author: Jaehyuk Cho <mailto:minzkn@minzkn.com> |
|
* |
|
* Redistribution and use in source and binary forms, with or without |
|
* modification, are permitted provided that the following conditions |
|
* are met: |
|
* |
|
* 1. Redistributions of source code must retain the above copyright |
|
* notice, this list of conditions and the following disclaimer. |
|
* 2. Redistributions in binary form must reproduce the above copyright |
|
* notice, this list of conditions and the following disclaimer in |
|
* the documentation and/or other materials provided with the |
|
* distribution. |
|
* 3. Neither the name NuttX nor the names of its contributors may be |
|
* used to endorse or promote products derived from this software |
|
* without specific prior written permission. |
|
* |
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
|
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
|
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS |
|
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED |
|
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
|
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
|
* POSSIBILITY OF SUCH DAMAGE. |
|
* |
|
****************************************************************************/ |
|
|
|
/**************************************************************************** |
|
* Included Files |
|
****************************************************************************/ |
|
|
|
#include <nuttx/config.h> |
|
|
|
#include <sys/socket.h> |
|
#include <sys/stat.h> |
|
|
|
#include <stdio.h> |
|
#include <stdlib.h> |
|
#include <unistd.h> |
|
#include <dirent.h> |
|
#include <string.h> |
|
#include <ctype.h> |
|
#include <fcntl.h> |
|
#include <poll.h> |
|
#include <libgen.h> |
|
#include <errno.h> |
|
#include <debug.h> |
|
|
|
#include <arpa/inet.h> |
|
|
|
#include <apps/netutils/ftpd.h> |
|
|
|
#include "ftpd.h" |
|
|
|
/**************************************************************************** |
|
* Pre-processor Definitions |
|
****************************************************************************/ |
|
|
|
#define __NUTTX__ 1 /* Flags some unusual NuttX dependencies */ |
|
|
|
/**************************************************************************** |
|
* Private Function Prototypes |
|
****************************************************************************/ |
|
/* Account functions */ |
|
|
|
static FAR struct ftpd_account_s *ftpd_account_new(FAR const char *user, |
|
uint8_t accountflags); |
|
static void ftpd_account_free(FAR struct ftpd_account_s *account); |
|
static int ftpd_account_setpassword(FAR struct ftpd_account_s *account, |
|
FAR const char *passwd); |
|
static int ftpd_account_add(FAR struct ftpd_server_s *server, |
|
FAR struct ftpd_account_s *account); |
|
static int ftpd_account_sethome(FAR struct ftpd_account_s *account, |
|
FAR const char *home); |
|
static FAR struct ftpd_account_s * |
|
ftpd_account_search_user(FAR struct ftpd_session_s *session, |
|
FAR const char *user); |
|
static FAR struct ftpd_account_s * |
|
ftpd_account_login(FAR struct ftpd_session_s *session, |
|
FAR const char *user, FAR const char *passwd); |
|
|
|
/* Parsing functions */ |
|
|
|
static FAR char *ftpd_strtok(bool skipspace, FAR const char *delimiters, |
|
FAR char **str); |
|
static FAR char *ftpd_strtok_alloc(bool skipspace, |
|
FAR const char *delimiters, FAR const char **str); |
|
|
|
/* Socket helpers */ |
|
|
|
static int ftpd_rxpoll(int sd, int timeout); |
|
static int ftpd_txpoll(int sd, int timeout); |
|
static int ftpd_accept(int sd, FAR void *addr, FAR socklen_t *addrlen, |
|
int timeout); |
|
static ssize_t ftpd_recv(int sd, FAR void *data, size_t size, int timeout); |
|
static ssize_t ftpd_send(int sd, FAR const void *data, size_t size, |
|
int timeout); |
|
static ssize_t ftpd_response(int sd, int timeout, FAR const char *fmt, ...); |
|
|
|
static int ftpd_dataopen(FAR struct ftpd_session_s *session); |
|
static int ftpd_dataclose(FAR struct ftpd_session_s *session); |
|
static FAR struct ftpd_server_s *ftpd_openserver(int port); |
|
|
|
/* Path helpers */ |
|
|
|
static int ftpd_pathignore(FAR struct ftpd_pathnode_s *currpath); |
|
static void ftpd_nodefree(FAR struct ftpd_pathnode_s *node); |
|
static FAR struct ftpd_pathnode_s *ftpd_path2node(FAR const char *path); |
|
static FAR char *ftpd_node2path(FAR struct ftpd_pathnode_s *node, |
|
bool strip); |
|
static FAR struct ftpd_pathnode_s * |
|
ftpd_nodeappend(FAR struct ftpd_pathnode_s *head, |
|
FAR struct ftpd_pathnode_s *node, bool override); |
|
static int ftpd_getpath(FAR struct ftpd_session_s *session, |
|
FAR const char *path, FAR char **abspath, |
|
FAR char **workpath); |
|
|
|
/* Commmand helpers */ |
|
|
|
static int ftpd_changedir(FAR struct ftpd_session_s *session, |
|
FAR const char *rempath); |
|
static off_t ftpd_offsatoi(FAR const char *filename, off_t offset); |
|
static int ftpd_stream(FAR struct ftpd_session_s *session, int cmdtype); |
|
static uint8_t ftpd_listoption(FAR char **param); |
|
static int ftpd_listbuffer(FAR struct ftpd_session_s *session, |
|
FAR char *path, FAR struct stat *st, FAR char *buffer, |
|
size_t buflen, unsigned int opton); |
|
static int fptd_listscan(FAR struct ftpd_session_s *session, |
|
FAR char *path, unsigned int opton); |
|
static int ftpd_list(FAR struct ftpd_session_s *session, |
|
unsigned int opton); |
|
|
|
/* Command handlers */ |
|
|
|
static int ftpd_command_user(FAR struct ftpd_session_s *session); |
|
static int ftpd_command_pass(FAR struct ftpd_session_s *session); |
|
static int ftpd_command_syst(FAR struct ftpd_session_s *session); |
|
static int ftpd_command_type(FAR struct ftpd_session_s *session); |
|
static int ftpd_command_mode(FAR struct ftpd_session_s *session); |
|
static int ftpd_command_abor(FAR struct ftpd_session_s *session); |
|
static int ftpd_command_quit(FAR struct ftpd_session_s *session); |
|
static int ftpd_command_noop(FAR struct ftpd_session_s *session); |
|
static int ftpd_command_port(FAR struct ftpd_session_s *session); |
|
static int ftpd_command_eprt(FAR struct ftpd_session_s *session); |
|
static int ftpd_command_pwd(FAR struct ftpd_session_s *session); |
|
static int ftpd_command_cwd(FAR struct ftpd_session_s *session); |
|
static int ftpd_command_cdup(FAR struct ftpd_session_s *session); |
|
static int ftpd_command_rmd(FAR struct ftpd_session_s *session); |
|
static int ftpd_command_mkd(FAR struct ftpd_session_s *session); |
|
static int ftpd_command_dele(FAR struct ftpd_session_s *session); |
|
static int ftpd_command_pasv(FAR struct ftpd_session_s *session); |
|
static int ftpd_command_epsv(FAR struct ftpd_session_s *session); |
|
static int ftpd_command_list(FAR struct ftpd_session_s *session); |
|
static int ftpd_command_nlst(FAR struct ftpd_session_s *session); |
|
static int ftpd_command_acct(FAR struct ftpd_session_s *session); |
|
static int ftpd_command_size(FAR struct ftpd_session_s *session); |
|
static int ftpd_command_stru(FAR struct ftpd_session_s *session); |
|
static int ftpd_command_rnfr(FAR struct ftpd_session_s *session); |
|
static int ftpd_command_rnto(FAR struct ftpd_session_s *session); |
|
static int ftpd_command_retr(FAR struct ftpd_session_s *session); |
|
static int ftpd_command_stor(FAR struct ftpd_session_s *session); |
|
static int ftpd_command_appe(FAR struct ftpd_session_s *session); |
|
static int ftpd_command_rest(FAR struct ftpd_session_s *session); |
|
static int ftpd_command_mdtm(FAR struct ftpd_session_s *session); |
|
static int ftpd_command_opts(FAR struct ftpd_session_s *session); |
|
static int ftpd_command_site(FAR struct ftpd_session_s *session); |
|
static int ftpd_command_help(FAR struct ftpd_session_s *session); |
|
|
|
static int ftpd_command(FAR struct ftpd_session_s *session); |
|
|
|
/* Worker thread */ |
|
|
|
static int ftpd_startworker(pthread_startroutine_t handler, FAR void *arg, |
|
size_t stacksize); |
|
static void ftpd_freesession(FAR struct ftpd_session_s *session); |
|
static void ftpd_workersetup(FAR struct ftpd_session_s *session); |
|
static FAR void *ftpd_worker(FAR void *arg); |
|
|
|
/**************************************************************************** |
|
* Private Data |
|
****************************************************************************/ |
|
|
|
static const struct ftpd_cmd_s g_ftpdcmdtab[] = |
|
{ |
|
{"USER", ftpd_command_user, 0}, /* USER <SP> <username> <CRLF> */ |
|
{"PASS", ftpd_command_pass, 0}, /* PASS <SP> <password> <CRLF> */ |
|
{"SYST", ftpd_command_syst, FTPD_CMDFLAG_LOGIN}, /* SYST <CRLF> */ |
|
{"TYPE", ftpd_command_type, FTPD_CMDFLAG_LOGIN}, /* TYPE <SP> <type-code> <CRLF> */ |
|
{"MODE", ftpd_command_mode, FTPD_CMDFLAG_LOGIN}, /* MODE <SP> <mode-code> <CRLF> */ |
|
{"ABOR", ftpd_command_abor, FTPD_CMDFLAG_LOGIN}, /* ABOR <CRLF> */ |
|
{"QUIT", ftpd_command_quit, 0}, /* QUIT <CRLF> */ |
|
{"NOOP", ftpd_command_noop, FTPD_CMDFLAG_LOGIN}, /* NOOP <CRLF> */ |
|
{"PORT", ftpd_command_port, FTPD_CMDFLAG_LOGIN}, /* PORT <SP> <host-port> <CRLF> */ |
|
{"EPRT", ftpd_command_eprt, FTPD_CMDFLAG_LOGIN}, /* EPRT <SP> <d> <net-prt> <d> <net-addr> <d> <tcp-port> <d> <CRLF> */ |
|
{"PWD" , ftpd_command_pwd , FTPD_CMDFLAG_LOGIN}, /* PWD <CRLF> */ |
|
{"XPWD", ftpd_command_pwd , FTPD_CMDFLAG_LOGIN}, /* XPWD <CRLF> */ |
|
{"CWD" , ftpd_command_cwd , FTPD_CMDFLAG_LOGIN}, /* CWD <SP> <pathname> <CRLF> */ |
|
{"XCWD", ftpd_command_cwd , FTPD_CMDFLAG_LOGIN}, /* XCWD <SP> <pathname> <CRLF> */ |
|
{"CDUP", ftpd_command_cdup, FTPD_CMDFLAG_LOGIN}, /* CDUP <CRLF> */ |
|
{"XCUP", ftpd_command_cdup, FTPD_CMDFLAG_LOGIN}, /* XCUP <CRLF> */ |
|
{"RMD" , ftpd_command_rmd , FTPD_CMDFLAG_LOGIN}, /* RMD <SP> <pathname> <CRLF> */ |
|
{"XRMD", ftpd_command_rmd , FTPD_CMDFLAG_LOGIN}, /* XRMD <SP> <pathname> <CRLF> */ |
|
{"MKD" , ftpd_command_mkd , FTPD_CMDFLAG_LOGIN}, /* MKD <SP> <pathname> <CRLF> */ |
|
{"XMKD", ftpd_command_mkd , FTPD_CMDFLAG_LOGIN}, /* XMKD <SP> <pathname> <CRLF> */ |
|
{"DELE", ftpd_command_dele, FTPD_CMDFLAG_LOGIN}, /* DELE <SP> <pathname> <CRLF> */ |
|
{"PASV", ftpd_command_pasv, FTPD_CMDFLAG_LOGIN}, /* PASV <CRLF> */ |
|
{"EPSV", ftpd_command_epsv, FTPD_CMDFLAG_LOGIN}, /* EPSV <SP> <net-prt> <CRLF> OR EPSV <SP> ALL <CRLF> */ |
|
{"LPSV", ftpd_command_epsv, FTPD_CMDFLAG_LOGIN}, /* LPSV ??? */ |
|
{"LIST", ftpd_command_list, FTPD_CMDFLAG_LOGIN}, /* LIST [<SP> <pathname>] <CRLF> */ |
|
{"NLST", ftpd_command_nlst, FTPD_CMDFLAG_LOGIN}, /* NLST [<SP> <pathname>] <CRLF> */ |
|
{"ACCT", ftpd_command_acct, FTPD_CMDFLAG_LOGIN}, /* ACCT <SP> <account-information> <CRLF> */ |
|
{"SIZE", ftpd_command_size, FTPD_CMDFLAG_LOGIN}, /* SIZE <SP> <pathname> <CRLF> */ |
|
{"STRU", ftpd_command_stru, FTPD_CMDFLAG_LOGIN}, /* STRU <SP> <structure-code> <CRLF> */ |
|
{"RNFR", ftpd_command_rnfr, FTPD_CMDFLAG_LOGIN}, /* RNFR <SP> <pathname> <CRLF> */ |
|
{"RNTO", ftpd_command_rnto, FTPD_CMDFLAG_LOGIN}, /* RNTO <SP> <pathname> <CRLF> */ |
|
{"RETR", ftpd_command_retr, FTPD_CMDFLAG_LOGIN}, /* RETR <SP> <pathname> <CRLF> */ |
|
{"STOR", ftpd_command_stor, FTPD_CMDFLAG_LOGIN}, /* STOR <SP> <pathname> <CRLF> */ |
|
{"APPE", ftpd_command_appe, FTPD_CMDFLAG_LOGIN}, /* APPE <SP> <pathname> <CRLF> */ |
|
{"REST", ftpd_command_rest, FTPD_CMDFLAG_LOGIN}, /* REST <SP> <marker> <CRLF> */ |
|
{"MDTM", ftpd_command_mdtm, FTPD_CMDFLAG_LOGIN}, /* MDTM <SP> <pathname> <CRLF> */ |
|
{"OPTS", ftpd_command_opts, FTPD_CMDFLAG_LOGIN}, /* OPTS <SP> <option> <value> <CRLF> */ |
|
{"SITE", ftpd_command_site, FTPD_CMDFLAG_LOGIN}, /* SITE <SP> <string> <CRLF> */ |
|
{"HELP", ftpd_command_help, FTPD_CMDFLAG_LOGIN}, /* HELP [<SP> <string>] <CRLF> */ |
|
#if 0 |
|
{"SMNT", ftpd_command_smnt, FTPD_CMDFLAG_LOGIN}, /* SMNT <SP> <pathname> <CRLF> */ |
|
{"REIN", ftpd_command_rein, FTPD_CMDFLAG_LOGIN}, /* REIN <CRLF> */ |
|
{"STOU", ftpd_command_stou, FTPD_CMDFLAG_LOGIN}, /* STOU <CRLF> */ |
|
{"STAT", ftpd_command_stat, FTPD_CMDFLAG_LOGIN}, /* STAT [<SP> <pathname>] <CRLF> */ |
|
{"ALLO", ftpd_command_stat, FTPD_CMDFLAG_LOGIN}, /* ALLO <SP> <decimal-integer> [<SP> R <SP> <decimal-integer>] <CRLF> */ |
|
#endif |
|
{NULL, (ftpd_cmdhandler_t)0, 0} |
|
}; |
|
|
|
static const char g_cdup[] = ".."; |
|
static const char g_respfmt1[] = "%03u%c%s\r\n"; /* Integer, character, string */ |
|
static const char g_respfmt2[] = "%03u%c%s%s\r\n"; /* Integer, character, two strings */ |
|
|
|
static const char *g_monthtab[] = |
|
{ |
|
"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" |
|
}; |
|
|
|
static const char *g_ftpdhelp[] = |
|
{ |
|
"The following commands are recognized (* =>'s unimplemented):", |
|
"CWD XCWD CDUP XCUP SMNT* QUIT PORT PASV", |
|
"EPRT* EPSV* ALLO* RNFR RNTO DELE MDTM RMD", |
|
"XRMD MKD XMKD PWD XPWD SIZE SYST HELP", |
|
"NOOP FEAT* OPTS AUTH* CCC* CONF* ENC* MIC*", |
|
"PBSZ* PROT* TYPE STRU* MODE* RETR STOR STOU*", |
|
"APPE REST ABOR USER PASS ACCT* REIN* LIST", |
|
"NLST STAT* SITE* MLSD* MLST*", |
|
"Direct comments to " CONFIG_FTPD_VENDORID, |
|
NULL |
|
}; |
|
|
|
/**************************************************************************** |
|
* Private Functions |
|
****************************************************************************/ |
|
|
|
/**************************************************************************** |
|
* Account Functions |
|
****************************************************************************/ |
|
/**************************************************************************** |
|
* Name: ftpd_account_new |
|
****************************************************************************/ |
|
|
|
static FAR struct ftpd_account_s *ftpd_account_new(FAR const char *user, |
|
uint8_t accountflags) |
|
{ |
|
FAR struct ftpd_account_s *ret; |
|
size_t usersize; |
|
size_t allocsize; |
|
|
|
/* Get the size of the allocation */ |
|
|
|
allocsize = sizeof(struct ftpd_account_s); |
|
if (!user) |
|
{ |
|
usersize = 0; |
|
} |
|
else |
|
{ |
|
usersize = strlen(user); |
|
allocsize += usersize + 1; |
|
} |
|
|
|
/* Allocate the account and user string */ |
|
|
|
ret = (struct ftpd_account_s *)zalloc(allocsize); |
|
if (!ret) |
|
{ |
|
ndbg("Failed to allocate account\n"); |
|
return NULL; |
|
} |
|
|
|
/* Initialize the account and user string */ |
|
|
|
ret->flags = accountflags; |
|
|
|
if (user) |
|
{ |
|
ret->user = (FAR char *)&ret[1]; |
|
strcpy(ret->user, user); |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_account_free |
|
****************************************************************************/ |
|
|
|
static void ftpd_account_free(FAR struct ftpd_account_s *account) |
|
{ |
|
struct ftpd_account_s *prev; |
|
DEBUGASSERT(account); |
|
|
|
/* Back up to the first entry in the list */ |
|
|
|
while (account->blink) |
|
{ |
|
account = account->blink; |
|
} |
|
|
|
/* Then free the entire list */ |
|
|
|
while (account) |
|
{ |
|
prev = account; |
|
account = account->flink; |
|
|
|
/* Free the home path and the password */ |
|
|
|
if (prev->home) |
|
{ |
|
free(prev->home); |
|
} |
|
|
|
if (prev->password) |
|
{ |
|
free(prev->password); |
|
} |
|
|
|
/* Then free the container itself */ |
|
|
|
free(prev); |
|
} |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_account_setpassword |
|
****************************************************************************/ |
|
|
|
static int ftpd_account_setpassword(FAR struct ftpd_account_s *account, |
|
FAR const char *passwd) |
|
{ |
|
FAR char *temp; |
|
DEBUGASSERT(account); |
|
|
|
/* Make of copy of the password string (if it is non-null) */ |
|
|
|
temp = NULL; |
|
if (passwd) |
|
{ |
|
temp = strdup(passwd); |
|
if (!temp) |
|
{ |
|
return -ENOMEM; |
|
} |
|
} |
|
|
|
/* Free any existing password string */ |
|
|
|
if (account->password) |
|
{ |
|
free(account->password); |
|
} |
|
|
|
/* Set the new password */ |
|
|
|
account->password = temp; |
|
return OK; |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_account_add |
|
****************************************************************************/ |
|
|
|
static int ftpd_account_add(FAR struct ftpd_server_s *server, |
|
FAR struct ftpd_account_s *account) |
|
{ |
|
FAR struct ftpd_account_s *head; |
|
FAR struct ftpd_account_s *tail; |
|
DEBUGASSERT(server && account); |
|
|
|
/* Find the beginning of the list */ |
|
|
|
head = account; |
|
while (head->blink) |
|
{ |
|
head = head->blink; |
|
} |
|
|
|
/* Find the tail of the list */ |
|
|
|
tail = account; |
|
while (tail->flink) |
|
{ |
|
tail = tail->flink; |
|
} |
|
|
|
/* Handle the case where the list is empty */ |
|
|
|
if (!server->head) |
|
{ |
|
server->head = head; |
|
} |
|
else |
|
{ |
|
head->blink = server->tail; |
|
server->tail->flink = head; |
|
} |
|
|
|
server->tail = tail; |
|
return OK; |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_account_sethome |
|
****************************************************************************/ |
|
|
|
static int ftpd_account_sethome(FAR struct ftpd_account_s *account, |
|
FAR const char *home) |
|
{ |
|
FAR char *temp; |
|
|
|
DEBUGASSERT(account); |
|
|
|
/* Make a copy of the home path string (unless it is NULL) */ |
|
|
|
temp = NULL; |
|
if (home) |
|
{ |
|
temp = strdup(home); |
|
if (!temp) |
|
{ |
|
return -ENOMEM; |
|
} |
|
} |
|
|
|
/* Free any existing home path string */ |
|
|
|
if (account->home) |
|
{ |
|
free(account->home); |
|
} |
|
|
|
/* And set the new home path string */ |
|
|
|
account->home = temp; |
|
return OK; |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_account_search_user |
|
****************************************************************************/ |
|
|
|
static FAR struct ftpd_account_s * |
|
ftpd_account_search_user(FAR struct ftpd_session_s *session, |
|
FAR const char *user) |
|
{ |
|
FAR struct ftpd_account_s *newaccount = NULL; |
|
FAR struct ftpd_account_s *account; |
|
uint8_t accountflags; |
|
|
|
account = session->head; |
|
while (account) |
|
{ |
|
accountflags = account->flags; |
|
|
|
/* Check if the account has a user */ |
|
|
|
if (!account->user) |
|
{ |
|
/* No.. The account has no user, was a user name provided? */ |
|
|
|
if (!user) |
|
{ |
|
/* Yes.. create the account */ |
|
|
|
newaccount = ftpd_account_new(NULL, accountflags); |
|
if (newaccount) |
|
{ |
|
if (ftpd_account_setpassword(newaccount, account->password) < 0) |
|
{ |
|
ftpd_account_free(newaccount); |
|
newaccount = NULL; |
|
} |
|
else if (ftpd_account_sethome(newaccount, account->home) < 0) |
|
{ |
|
ftpd_account_free(newaccount); |
|
newaccount = NULL; |
|
} |
|
} |
|
break; |
|
} |
|
} |
|
|
|
/* Was a user name provided? */ |
|
|
|
else if (user) |
|
{ |
|
/* Check if matches the user name on the account */ |
|
|
|
if (strcmp(user, (FAR const char *)account->user) == 0) |
|
{ |
|
/* Yes.. create the account */ |
|
|
|
newaccount = ftpd_account_new(account->user, accountflags); |
|
if (newaccount) |
|
{ |
|
if (ftpd_account_setpassword(newaccount, account->password) != 0) |
|
{ |
|
ftpd_account_free(newaccount); |
|
newaccount = NULL; |
|
} |
|
else if (ftpd_account_sethome(newaccount, account->home) != 0) |
|
{ |
|
ftpd_account_free(newaccount); |
|
newaccount = NULL; |
|
} |
|
} |
|
break; |
|
} |
|
} |
|
|
|
/* Try the next account */ |
|
|
|
account = account->flink; |
|
} |
|
|
|
return newaccount; |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_account_login |
|
****************************************************************************/ |
|
|
|
static FAR struct ftpd_account_s * |
|
ftpd_account_login(FAR struct ftpd_session_s *session, |
|
FAR const char *user, FAR const char *passwd) |
|
{ |
|
FAR struct ftpd_account_s *account; |
|
bool pwvalid; |
|
FAR char *home; |
|
|
|
account = ftpd_account_search_user(session, user); |
|
if (!account) |
|
{ |
|
return NULL; |
|
} |
|
|
|
if (!account->password) |
|
{ |
|
if (!passwd) |
|
{ |
|
pwvalid = true; |
|
} |
|
else if (passwd[0] == '\0') |
|
{ |
|
pwvalid = true; |
|
} |
|
else |
|
{ |
|
pwvalid = false; |
|
} |
|
} |
|
else if (!passwd) |
|
{ |
|
pwvalid = false; |
|
} |
|
else if (strcmp(passwd, (FAR const char *)account->password) == 0) |
|
{ |
|
pwvalid = true; |
|
} |
|
else |
|
{ |
|
pwvalid = false; |
|
} |
|
|
|
if (!pwvalid) |
|
{ |
|
ftpd_account_free(account); |
|
return NULL; |
|
} |
|
|
|
home = account->home; |
|
if (!home) |
|
{ |
|
home = getenv("HOME"); |
|
} |
|
|
|
if ((account->flags & FTPD_ACCOUNTFLAG_ADMIN) != 0) |
|
{ |
|
/* admin user */ |
|
|
|
session->home = strdup("/"); |
|
session->work = strdup(!home ? "/" : home); |
|
} |
|
else |
|
{ |
|
/* normal user */ |
|
|
|
session->home = strdup(!home ? "/" : home); |
|
session->work = strdup("/"); |
|
} |
|
|
|
ftpd_account_free(account); |
|
return account; |
|
} |
|
|
|
/**************************************************************************** |
|
* Parsing Functions |
|
****************************************************************************/ |
|
/**************************************************************************** |
|
* Name: ftpd_strtok |
|
****************************************************************************/ |
|
|
|
static FAR char *ftpd_strtok(bool skipspace, FAR const char *delimiters, |
|
FAR char **str) |
|
{ |
|
FAR const char *dptr; |
|
FAR char *sptr; |
|
FAR char *ret; |
|
|
|
sptr = *str; |
|
|
|
/* Skip any leading spaces */ |
|
|
|
if (skipspace) |
|
{ |
|
while (isspace(*sptr)) |
|
{ |
|
sptr++; |
|
} |
|
} |
|
|
|
ret = sptr; |
|
|
|
/* The following is an implementation of strtok. It does not modify the |
|
* original string as strtok does, however. |
|
*/ |
|
|
|
while (*sptr != '\0') |
|
{ |
|
dptr = delimiters; |
|
while (*sptr != *dptr && *dptr != '\0') |
|
{ |
|
dptr++; |
|
} |
|
|
|
if (*sptr == *dptr) |
|
{ |
|
break; |
|
} |
|
|
|
sptr++; |
|
} |
|
|
|
/* Save the place where we will resuming searching */ |
|
|
|
*str = sptr; |
|
return ret; |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_strtok_alloc |
|
****************************************************************************/ |
|
|
|
static FAR char *ftpd_strtok_alloc(bool skipspace, FAR const char *delimiters, |
|
FAR const char **str) |
|
{ |
|
FAR const char *sptr; |
|
FAR const char *left; |
|
FAR const char *right; |
|
FAR const char *dptr; |
|
FAR char *ret; |
|
size_t tokenlen; |
|
|
|
sptr = *str; |
|
|
|
if (skipspace) |
|
{ |
|
while (isspace(*sptr)) |
|
{ |
|
sptr++; |
|
} |
|
} |
|
|
|
right = sptr; |
|
left = sptr; |
|
|
|
/* The the following logic is similar to strtok(), but only bounds the |
|
* token of interest between left (the first character of the substring) |
|
* and right (the character after the end of the substring). |
|
*/ |
|
|
|
while (*sptr != '\0') |
|
{ |
|
dptr = delimiters; |
|
while (*sptr != *dptr && *dptr != '\0') |
|
{ |
|
dptr++; |
|
} |
|
|
|
if (*sptr == *dptr) |
|
{ |
|
break; |
|
} |
|
|
|
sptr++; |
|
|
|
/* Adjust the right pointer but ignoring any trailing spaces if |
|
* 'skipspace' is selected. |
|
*/ |
|
|
|
if (!skipspace || !isspace(*sptr)) |
|
{ |
|
right = sptr; |
|
} |
|
} |
|
|
|
/* Allocate memory large enough to hold the entire sub-string (including |
|
* the NUL terminator. |
|
*/ |
|
|
|
tokenlen = (size_t)(right - left); |
|
ret = (FAR char *)malloc(tokenlen + 1); |
|
if (ret) |
|
{ |
|
if (tokenlen > 0) |
|
{ |
|
memcpy(ret, left, tokenlen); |
|
} |
|
ret[tokenlen] = '\0'; |
|
} |
|
|
|
/* Save the place where we will resuming searching */ |
|
|
|
*str = sptr; |
|
return ret; |
|
} |
|
|
|
/**************************************************************************** |
|
* Socket Helpers |
|
****************************************************************************/ |
|
/**************************************************************************** |
|
* Name: ftpd_rxpoll |
|
****************************************************************************/ |
|
|
|
static int ftpd_rxpoll(int sd, int timeout) |
|
{ |
|
struct pollfd fds[1]; |
|
int ret; |
|
|
|
/* Set up the poll */ |
|
|
|
fds[0].fd = sd; |
|
fds[0].events = POLLIN; |
|
fds[0].revents = 0; |
|
|
|
/* Perform the poll. */ |
|
|
|
ret = poll(fds, 1, timeout); |
|
|
|
/* Handle the result of the poll. On success, poll returns the number |
|
* of structures that have nonzero revents fields. A value of 0 indicates |
|
* that the call timed out and no file descriptors were ready. On error, |
|
* -1 is returned, and errno is set appropriately: |
|
*/ |
|
|
|
if (ret == 0) |
|
{ |
|
//nvdbg("poll() timed out\n"); |
|
return -ETIMEDOUT; |
|
} |
|
else if (ret < 0) |
|
{ |
|
int errval = errno; |
|
nvdbg("poll() failed: %d\n", errval); |
|
return -errval; |
|
} |
|
else |
|
{ |
|
return OK; |
|
} |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_txpoll |
|
****************************************************************************/ |
|
|
|
static int ftpd_txpoll(int sd, int timeout) |
|
{ |
|
struct pollfd fds[1]; |
|
int ret; |
|
|
|
/* Set up the poll */ |
|
|
|
fds[0].fd = sd; |
|
fds[0].events = POLLOUT; |
|
fds[0].revents = 0; |
|
|
|
/* Perform the poll. */ |
|
|
|
ret = poll(fds, 1, timeout); |
|
|
|
/* Handle the result of the poll. On success, poll returns the number |
|
* of structures that have nonzero revents fields. A value of 0 indicates |
|
* that the call timed out and no file descriptors were ready. On error, |
|
* -1 is returned, and errno is set appropriately: |
|
*/ |
|
|
|
if (ret == 0) |
|
{ |
|
nvdbg("poll() timed out\n"); |
|
return -ETIMEDOUT; |
|
} |
|
else if (ret < 0) |
|
{ |
|
int errval = errno; |
|
nvdbg("poll() failed: %d\n", errval); |
|
return -errval; |
|
} |
|
else |
|
{ |
|
return OK; |
|
} |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_accept |
|
****************************************************************************/ |
|
|
|
static int ftpd_accept(int sd, FAR void *addr, FAR socklen_t *addrlen, |
|
int timeout) |
|
{ |
|
int acceptsd; |
|
int ret; |
|
|
|
/* Handle any requested timeout */ |
|
|
|
if (timeout >= 0) |
|
{ |
|
ret = ftpd_rxpoll(sd, timeout); |
|
if (ret < 0) |
|
{ |
|
/* Only report interesting, infrequent errors (not the common timeout) */ |
|
|
|
#ifdef CONFIG_DEBUG_NET |
|
if (ret != -ETIMEDOUT) |
|
{ |
|
ndbg("ftpd_rxpoll() failed: %d\n", ret); |
|
} |
|
#endif |
|
return ret; |
|
} |
|
} |
|
|
|
/* Accept the connection -- waiting if necessary */ |
|
|
|
acceptsd = accept(sd, (FAR struct sockaddr *)addr, addrlen); |
|
if (acceptsd < 0) |
|
{ |
|
int errval = errno; |
|
ndbg("accept() failed: %d\n", errval); |
|
return -errval; |
|
} |
|
|
|
return acceptsd; |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_recv |
|
****************************************************************************/ |
|
|
|
static ssize_t ftpd_recv(int sd, FAR void *data, size_t size, int timeout) |
|
{ |
|
ssize_t ret; |
|
int status; |
|
|
|
/* Handle any requested timetout */ |
|
|
|
if (timeout >= 0) |
|
{ |
|
status = ftpd_rxpoll(sd, timeout); |
|
if (status < 0) |
|
{ |
|
nvdbg("ftpd_rxpoll: %d\n", status); |
|
return (ssize_t)status; |
|
} |
|
} |
|
|
|
/* Receive the data... waiting if necessary. The client side will break the |
|
* connection after the file has been sent. Zero (end-of-file) should be |
|
* received in this case. |
|
*/ |
|
|
|
ret = recv(sd, data, size, 0); |
|
if (ret < 0) |
|
{ |
|
int errval = errno; |
|
|
|
ndbg("recv() failed: %d\n", errval); |
|
return -errval; |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_send |
|
****************************************************************************/ |
|
|
|
static ssize_t ftpd_send(int sd, FAR const void *data, size_t size, int timeout) |
|
{ |
|
ssize_t ret; |
|
|
|
/* Handle any requested timetout */ |
|
|
|
if (timeout >= 0) |
|
{ |
|
int status = ftpd_txpoll(sd, timeout); |
|
if (status < 0) |
|
{ |
|
nvdbg("ftpd_rxpoll: %d\n", status); |
|
return (ssize_t)status; |
|
} |
|
} |
|
|
|
/* Then send the data (waiting if necessary) */ |
|
|
|
ret = send(sd, data, size, 0); |
|
if (ret < 0) |
|
{ |
|
ssize_t errval = errno; |
|
ndbg("send() failed: %d\n", errval); |
|
return -errval; |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_response |
|
****************************************************************************/ |
|
|
|
static ssize_t ftpd_response(int sd, int timeout, FAR const char *fmt, ...) |
|
{ |
|
FAR char *buffer; |
|
ssize_t bytessent; |
|
va_list ap; |
|
|
|
va_start(ap, fmt); |
|
avsprintf(&buffer, fmt, ap); |
|
va_end(ap); |
|
|
|
if (!buffer) |
|
{ |
|
return -ENOMEM; |
|
} |
|
|
|
bytessent = ftpd_send(sd, buffer, strlen(buffer), timeout); |
|
free(buffer); |
|
|
|
return bytessent; |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_dataopen |
|
****************************************************************************/ |
|
|
|
static int ftpd_dataopen(FAR struct ftpd_session_s *session) |
|
{ |
|
int sd; |
|
int ret; |
|
|
|
if (session->data.sd < 0) |
|
{ |
|
/* PORT session */ |
|
|
|
#ifdef CONFIG_NET_IPv6 |
|
if (session->data.addr.ss.ss_family == AF_INET6) |
|
{ |
|
session->data.sd = socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP); |
|
} |
|
else |
|
{ |
|
session->data.sd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); |
|
} |
|
#else |
|
session->data.sd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); |
|
#endif |
|
|
|
if (session->data.sd < 0) |
|
{ |
|
int errval = errno; |
|
ndbg("socket() failed: %d\n", errval); |
|
(void)ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 451, ' ', "Socket error !"); |
|
return -errval; |
|
} |
|
|
|
session->data.addrlen = (socklen_t)sizeof(session->data.addr); |
|
ret = connect(session->data.sd, (FAR const struct sockaddr *)(&session->data.addr), |
|
session->data.addrlen); |
|
if (ret < 0) |
|
{ |
|
int errval = errno; |
|
ndbg("connect() failed: %d\n", errval); |
|
(void)ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 451, ' ', "Connect error !"); |
|
(void)ftpd_dataclose(session); |
|
return -errval; |
|
} |
|
|
|
#ifdef CONFIG_NET_HAVE_SOLINGER |
|
{ |
|
struct linger ling; |
|
|
|
(void)memset(&ling, 0, sizeof(ling)); |
|
ling.l_onoff = 1; |
|
ling.l_linger = 4; |
|
(void)setsockopt(session->data.sd, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling)); |
|
} |
|
#endif |
|
|
|
return OK; |
|
} |
|
|
|
/* PASV session */ |
|
|
|
session->data.addrlen = sizeof(session->data.addr); |
|
sd = ftpd_accept(session->data.sd, (struct sockaddr *)(&session->data.addr), |
|
&session->data.addrlen, -1); |
|
if (sd < 0) |
|
{ |
|
ndbg("ftpd_accept() failed: %d\n", sd); |
|
(void)ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 451, ' ', "Accept error !"); |
|
(void)ftpd_dataclose(session); |
|
return sd; |
|
} |
|
|
|
close(session->data.sd); |
|
session->data.sd = sd; |
|
|
|
#ifdef CONFIG_NET_HAVE_SOLINGER |
|
{ |
|
struct linger ling; |
|
|
|
(void)memset(&ling, 0, sizeof(ling)); |
|
ling.l_onoff = 1; |
|
ling.l_linger = 4; |
|
(void)setsockopt(session->data.sd, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling)); |
|
} |
|
#endif |
|
|
|
return OK; |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_dataclose |
|
****************************************************************************/ |
|
|
|
static int ftpd_dataclose(FAR struct ftpd_session_s *session) |
|
{ |
|
if (session->data.sd >= 0) |
|
{ |
|
close(session->data.sd); |
|
session->data.sd = -1; |
|
} |
|
|
|
return OK; |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_openserver |
|
****************************************************************************/ |
|
|
|
static FAR struct ftpd_server_s *ftpd_openserver(int port) |
|
{ |
|
FAR struct ftpd_server_s *server; |
|
sa_family_t family; |
|
socklen_t addrlen; |
|
FAR const void *addr; |
|
#if defined(SOMAXCONN) |
|
int backlog = SOMAXCONN; |
|
#else |
|
int backlog = 5; |
|
#endif |
|
int ret; |
|
|
|
/* Allocate the server instance */ |
|
|
|
server = (FAR struct ftpd_server_s *)zalloc(sizeof(struct ftpd_server_s)); |
|
if (!server) |
|
{ |
|
ndbg("Failed to allocate server\n"); |
|
return NULL; |
|
} |
|
|
|
/* Initialize the server instance */ |
|
|
|
server->sd = -1; |
|
server->head = NULL; |
|
server->tail = NULL; |
|
|
|
/* Create the server listen socket */ |
|
|
|
#ifdef CONFIG_NET_IPv6 |
|
server->sd = socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP); |
|
if (server->sd < 0) |
|
{ |
|
ftpd_close((FTPD_SESSION)server); |
|
return NULL; |
|
} |
|
|
|
family = AF_INET6; |
|
addrlen = (socklen_t)sizeof(server->addr.in6); |
|
addr = (FAR void *)(&server->addr.in6); |
|
|
|
server->addr.in6.sin6_family = family; |
|
server->addr.in6.sin6_flowinfo = 0; |
|
server->addr.in6.sin6_addr = in6addr_any; |
|
server->addr.in6.sin6_port = htons(port); |
|
#else |
|
server->sd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); |
|
if (server->sd < 0) |
|
{ |
|
ftpd_close((FTPD_SESSION)server); |
|
return NULL; |
|
} |
|
|
|
family = AF_INET; |
|
addrlen = (socklen_t)sizeof(server->addr.in4); |
|
addr = (FAR void *)(&server->addr.in4); |
|
|
|
server->addr.in4.sin_family = family; |
|
server->addr.in4.sin_addr.s_addr = htonl(INADDR_ANY); |
|
server->addr.in4.sin_port = htons(port); |
|
#endif |
|
|
|
#ifdef CONFIG_NET_HAVE_REUSEADDR |
|
{ |
|
int reuse = 1; |
|
(void)setsockopt(server->sd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)); |
|
} |
|
#endif |
|
|
|
/* Bind the socket to the address */ |
|
|
|
ret = bind(server->sd, (FAR const struct sockaddr *)addr, addrlen); |
|
if (ret < 0) |
|
{ |
|
ftpd_close((FTPD_SESSION)server); |
|
return NULL; |
|
} |
|
|
|
/* Listen on the socket */ |
|
|
|
ret = listen(server->sd, backlog); |
|
if (ret < 0) |
|
{ |
|
ftpd_close((FTPD_SESSION)server); |
|
return NULL; |
|
} |
|
|
|
return (FTPD_SESSION)server; |
|
} |
|
|
|
/**************************************************************************** |
|
* Path Helpers |
|
****************************************************************************/ |
|
/**************************************************************************** |
|
* Name: ftpd_pathignore |
|
****************************************************************************/ |
|
|
|
static int ftpd_pathignore(FAR struct ftpd_pathnode_s *currpath) |
|
{ |
|
FAR struct ftpd_pathnode_s *node; |
|
size_t namelen; |
|
|
|
namelen = !currpath->name ? 0 : strlen(currpath->name); |
|
|
|
if (namelen == 0) |
|
{ |
|
if (currpath->blink) |
|
{ |
|
currpath->ignore = true; |
|
} |
|
|
|
return OK; |
|
} |
|
|
|
if (strcmp(currpath->name, "..") == 0) |
|
{ |
|
currpath->ignore = true; |
|
|
|
node = currpath->blink; |
|
while (node) |
|
{ |
|
if (!node->ignore) |
|
{ |
|
namelen = !node->name ? 0 : strlen(node->name); |
|
|
|
if (namelen > 0) |
|
{ |
|
node->ignore = true; |
|
} |
|
break; |
|
} |
|
node = node->blink; |
|
} |
|
|
|
return OK; |
|
} |
|
|
|
if (strcmp(currpath->name, ".") == 0) |
|
{ |
|
currpath->ignore = true; |
|
return OK; |
|
} |
|
|
|
return OK; |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: |
|
****************************************************************************/ |
|
|
|
static void ftpd_nodefree(FAR struct ftpd_pathnode_s *node) |
|
{ |
|
FAR struct ftpd_pathnode_s *prev; |
|
|
|
while (node) |
|
{ |
|
prev = node; |
|
node = node->flink; |
|
|
|
if (prev->name) |
|
{ |
|
free(prev->name); |
|
} |
|
free(prev); |
|
} |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_path2node |
|
****************************************************************************/ |
|
|
|
static FAR struct ftpd_pathnode_s *ftpd_path2node(FAR const char *path) |
|
{ |
|
FAR struct ftpd_pathnode_s *head = NULL; |
|
FAR struct ftpd_pathnode_s *tail = NULL; |
|
FAR struct ftpd_pathnode_s *newnode; |
|
FAR char *name; |
|
|
|
if (!path) |
|
{ |
|
return NULL; |
|
} |
|
|
|
while (path[0] != '\0') |
|
{ |
|
name = ftpd_strtok_alloc(false, "/\\", &path); |
|
if (!name) |
|
{ |
|
break; |
|
} |
|
|
|
if (path[0] != '\0') |
|
{ |
|
path++; |
|
} |
|
|
|
newnode = (FAR struct ftpd_pathnode_s *)malloc(sizeof(struct ftpd_pathnode_s)); |
|
if (!newnode) |
|
{ |
|
free(name); |
|
ftpd_nodefree(head); |
|
return NULL; |
|
} |
|
|
|
newnode->blink = tail; |
|
newnode->flink = NULL; |
|
newnode->ignore = false; |
|
newnode->name = name; |
|
|
|
if (!tail) |
|
{ |
|
head = newnode; |
|
} |
|
else |
|
{ |
|
tail->flink = newnode; |
|
} |
|
|
|
tail = newnode; |
|
|
|
(void)ftpd_pathignore(newnode); |
|
} |
|
|
|
return head; |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_node2path |
|
****************************************************************************/ |
|
|
|
static FAR char *ftpd_node2path(FAR struct ftpd_pathnode_s *node, |
|
bool strip) |
|
{ |
|
FAR struct ftpd_pathnode_s *node1; |
|
FAR struct ftpd_pathnode_s *node2; |
|
FAR char *path; |
|
FAR size_t allocsize; |
|
FAR size_t namelen; |
|
|
|
if (!node) |
|
{ |
|
return NULL; |
|
} |
|
|
|
allocsize = 0; |
|
node1 = node; |
|
while (node1) |
|
{ |
|
if (strip) |
|
{ |
|
if (node1->ignore) |
|
{ |
|
node1 = node1->flink; |
|
continue; |
|
} |
|
} |
|
|
|
node2 = node1->flink; |
|
while (strip && node2) |
|
{ |
|
if (!node2->ignore) |
|
{ |
|
break; |
|
} |
|
|
|
node2 = node2->flink; |
|
} |
|
|
|
namelen = !node1->name ? 0 : strlen(node1->name); |
|
if (!node2) |
|
{ |
|
if (namelen <= 0) |
|
{ |
|
allocsize += 2; |
|
} |
|
else |
|
{ |
|
allocsize += namelen +1; |
|
} |
|
} |
|
else |
|
{ |
|
allocsize += namelen + 1; |
|
} |
|
|
|
node1 = node1->flink; |
|
} |
|
|
|
path = (FAR char *)malloc(allocsize); |
|
if (!path) |
|
{ |
|
return NULL; |
|
} |
|
|
|
allocsize = 0; |
|
node1 = node; |
|
while (node1) |
|
{ |
|
if (strip != 0) |
|
{ |
|
if (node1->ignore) |
|
{ |
|
node1 = node1->flink; |
|
continue; |
|
} |
|
} |
|
|
|
node2 = node1->flink; |
|
while (strip && node2) |
|
{ |
|
if (!node2->ignore) |
|
{ |
|
break; |
|
} |
|
|
|
node2 = node2->flink; |
|
} |
|
|
|
namelen = !node1->name ? 0 : strlen(node1->name); |
|
|
|
if (!node2) |
|
{ |
|
if (namelen <= 0) |
|
{ |
|
allocsize += sprintf(&path[allocsize], "/"); |
|
} |
|
else |
|
{ |
|
allocsize += sprintf(&path[allocsize], "%s", node1->name); |
|
} |
|
} |
|
else |
|
{ |
|
allocsize += sprintf(&path[allocsize], "%s%s", node1->name, "/"); |
|
} |
|
|
|
node1 = node1->flink; |
|
} |
|
|
|
return path; |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_nodeappend |
|
****************************************************************************/ |
|
|
|
static FAR struct ftpd_pathnode_s * |
|
ftpd_nodeappend(FAR struct ftpd_pathnode_s *head, |
|
FAR struct ftpd_pathnode_s *node, bool override) |
|
{ |
|
FAR struct ftpd_pathnode_s *temp; |
|
|
|
if (override) |
|
{ |
|
if (node && node->name && strlen(node->name) <= 0) |
|
{ |
|
ftpd_nodefree(head); |
|
head = NULL; |
|
} |
|
} |
|
|
|
if (!head) |
|
{ |
|
if (node) |
|
{ |
|
node->blink = NULL; |
|
} |
|
|
|
head = node; |
|
node = NULL; |
|
} |
|
|
|
if (node) |
|
{ |
|
temp = head; |
|
while (temp->flink) |
|
{ |
|
temp = temp->flink; |
|
} |
|
|
|
node->blink = temp; |
|
temp->flink = node; |
|
} |
|
|
|
/* clear ignore */ |
|
|
|
temp = head; |
|
while (temp) |
|
{ |
|
temp->ignore = false; |
|
temp = temp->flink; |
|
} |
|
|
|
/* restrip */ |
|
|
|
temp = head; |
|
while (temp) |
|
{ |
|
(void)ftpd_pathignore(temp); |
|
temp = temp->flink; |
|
} |
|
|
|
return head; |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_getpath |
|
****************************************************************************/ |
|
|
|
static int ftpd_getpath(FAR struct ftpd_session_s *session, |
|
FAR const char *path, FAR char **abspath, |
|
FAR char **workpath) |
|
{ |
|
FAR struct ftpd_pathnode_s *abspath_node; |
|
FAR struct ftpd_pathnode_s *worknode; |
|
FAR struct ftpd_pathnode_s *appendnode; |
|
FAR char *abspath_local; |
|
FAR char *workpath_local; |
|
|
|
if (abspath) |
|
{ |
|
*abspath = NULL; |
|
} |
|
|
|
if (workpath) |
|
{ |
|
*workpath = NULL; |
|
} |
|
|
|
worknode = ftpd_path2node(!session->work ? "" : session->work); |
|
if (!worknode) |
|
{ |
|
return -ENOMEM; |
|
} |
|
|
|
appendnode = ftpd_path2node(path); |
|
worknode = ftpd_nodeappend(worknode, appendnode, true); |
|
workpath_local = ftpd_node2path(worknode, 1); |
|
|
|
if (!workpath_local) |
|
{ |
|
ftpd_nodefree(worknode); |
|
return -ENOMEM; |
|
} |
|
|
|
abspath_node = ftpd_path2node(!session->home ? "" : session->home); |
|
if (!abspath_node) |
|
{ |
|
free(workpath_local); |
|
ftpd_nodefree(worknode); |
|
return -ENOMEM; |
|
} |
|
|
|
appendnode = ftpd_path2node(workpath_local); |
|
abspath_node = ftpd_nodeappend(abspath_node, appendnode, false); |
|
abspath_local = ftpd_node2path(abspath_node, 1); |
|
|
|
if (!abspath_local) |
|
{ |
|
free(workpath_local); |
|
ftpd_nodefree(abspath_node); |
|
ftpd_nodefree(worknode); |
|
return -ENOMEM; |
|
} |
|
|
|
if (!workpath) |
|
{ |
|
free(workpath_local); |
|
} |
|
else |
|
{ |
|
*workpath = workpath_local; |
|
} |
|
|
|
if (!abspath) |
|
{ |
|
free(abspath_local); |
|
} |
|
else |
|
{ |
|
*abspath = abspath_local; |
|
} |
|
|
|
ftpd_nodefree(abspath_node); |
|
ftpd_nodefree(worknode); |
|
return OK; |
|
} |
|
|
|
/**************************************************************************** |
|
* Command Helpers |
|
****************************************************************************/ |
|
/**************************************************************************** |
|
* Name: ftpd_changedir |
|
****************************************************************************/ |
|
|
|
static int ftpd_changedir(FAR struct ftpd_session_s *session, |
|
FAR const char *rempath) |
|
{ |
|
FAR char *abspath; |
|
FAR char *workpath; |
|
struct stat st; |
|
int ret; |
|
|
|
ret = ftpd_getpath(session, rempath, (char **)(&abspath), (char **)(&workpath)); |
|
if (ret < 0) |
|
{ |
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 550, ' ', |
|
"Can not change directory !"); |
|
} |
|
|
|
ret = stat(abspath, &st); |
|
if (ret < 0) |
|
{ |
|
free(workpath); |
|
free(abspath); |
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt2, 550, ' ', rempath, |
|
": No such file or directory"); |
|
} |
|
|
|
if (S_ISDIR(st.st_mode) == 0) |
|
{ |
|
free(workpath); |
|
free(abspath); |
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt2, 550, ' ', rempath, |
|
": No such file or directory"); |
|
} |
|
|
|
free(abspath); |
|
if (session->work) |
|
{ |
|
free(session->work); |
|
} |
|
session->work = workpath; |
|
|
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 250, ' ', "CWD command successful"); |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_offsatoi |
|
****************************************************************************/ |
|
|
|
static off_t ftpd_offsatoi(FAR const char *filename, off_t offset) |
|
{ |
|
off_t ret; |
|
off_t temp; |
|
FILE *outstream; |
|
int ch; |
|
|
|
outstream = fopen(filename, "r"); |
|
if (!outstream) |
|
{ |
|
int errval = errno; |
|
ndbg("Failed to open %s: %d\n", filename, errval); |
|
return -errval; |
|
} |
|
|
|
ret = 0; |
|
temp = 0; |
|
|
|
if (offset == (off_t)(-1)) |
|
{ |
|
for (;;) |
|
{ |
|
ch = getc(outstream); |
|
if (ch == EOF) |
|
{ |
|
break; |
|
} |
|
ret++; |
|
if (ch == '\n') |
|
{ |
|
ret++; |
|
} |
|
} |
|
/* ret is ascii mode size */ |
|
} |
|
else |
|
{ |
|
while (offset < temp) |
|
{ |
|
ch = getc(outstream); |
|
if (ch == EOF) |
|
{ |
|
ret = -errno; |
|
break; |
|
} |
|
|
|
ret++; |
|
temp++; |
|
|
|
if (ch == '\n') |
|
{ |
|
temp++; |
|
} |
|
} |
|
|
|
/* ret is binary mode offset */ |
|
} |
|
|
|
(void)fclose(outstream); |
|
return ret; |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_stream |
|
****************************************************************************/ |
|
|
|
static int ftpd_stream(FAR struct ftpd_session_s *session, int cmdtype) |
|
{ |
|
FAR char *abspath; |
|
FAR char *path; |
|
bool isnew; |
|
int oflags; |
|
FAR char *buffer; |
|
size_t buflen; |
|
size_t wantsize; |
|
ssize_t rdbytes; |
|
ssize_t wrbytes; |
|
off_t pos = 0; |
|
int errval = 0; |
|
int ret; |
|
|
|
ret = ftpd_getpath(session, session->param, &abspath, NULL); |
|
if (ret < 0) |
|
{ |
|
ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 550, ' ', "Stream error !"); |
|
goto errout; |
|
} |
|
path = abspath; |
|
|
|
ret = ftpd_dataopen(session); |
|
if (ret < 0) |
|
{ |
|
goto errout_with_path; |
|
} |
|
|
|
switch (cmdtype) |
|
{ |
|
case 0: /* retr */ |
|
oflags = O_RDONLY; |
|
break; |
|
|
|
case 1: /* stor */ |
|
oflags = O_CREAT | O_WRONLY; |
|
break; |
|
|
|
case 2: /* appe */ |
|
oflags = O_CREAT | O_WRONLY | O_APPEND; |
|
break; |
|
|
|
default: |
|
oflags = O_RDONLY; |
|
break; |
|
} |
|
|
|
#if defined(O_LARGEFILE) |
|
oflags |= O_LARGEFILE; |
|
#endif |
|
#if defined(O_BINARY) |
|
oflags |= O_BINARY; |
|
#endif |
|
|
|
/* Are we creating the file? */ |
|
|
|
if ((oflags & O_CREAT) != 0) |
|
{ |
|
int mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH; |
|
|
|
if (session->restartpos <= 0) |
|
{ |
|
oflags |= O_TRUNC; |
|
} |
|
|
|
isnew = true; |
|
session->fd = open(path, oflags | O_EXCL, mode); |
|
if (session->fd < 0) |
|
{ |
|
isnew = false; |
|
session->fd = open(path, oflags, mode); |
|
} |
|
} |
|
else |
|
{ |
|
/* No.. we are opening an existing file */ |
|
|
|
isnew = false; |
|
session->fd = open(path, oflags); |
|
} |
|
|
|
if (session->fd < 0) |
|
{ |
|
ret = -errno; |
|
(void)ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 550, ' ', "Can not open file !"); |
|
goto errout_with_data; |
|
} |
|
|
|
/* Restart position */ |
|
|
|
if (session->restartpos > 0) |
|
{ |
|
off_t seekoffs = (off_t)-1; |
|
off_t seekpos; |
|
|
|
/* Get the seek position */ |
|
|
|
if (session->type == FTPD_SESSIONTYPE_A) |
|
{ |
|
seekpos = ftpd_offsatoi(path, session->restartpos); |
|
if (seekpos < 0) |
|
{ |
|
ndbg("ftpd_offsatoi failed: %d\n", seekpos); |
|
errval = -seekpos; |
|
} |
|
} |
|
else |
|
{ |
|
seekpos = session->restartpos; |
|
if (seekpos < 0) |
|
{ |
|
ndbg("Bad restartpos: %d\n", seekpos); |
|
errval = EINVAL; |
|
} |
|
} |
|
|
|
/* Seek to the request position */ |
|
|
|
if (seekpos >= 0) |
|
{ |
|
seekoffs = lseek(session->fd, seekpos, SEEK_SET); |
|
if (seekoffs < 0) |
|
{ |
|
errval = errno; |
|
ndbg("lseek failed: %d\n", errval); |
|
} |
|
} |
|
|
|
/* Report errors. If an error occurred, seekoffs will be negative and |
|
* errval will hold the (positive) error code. |
|
*/ |
|
|
|
if (seekoffs < 0) |
|
{ |
|
(void)ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 550, ' ', "Can not seek file !"); |
|
ret = -errval; |
|
goto errout_with_session; |
|
} |
|
|
|
pos += (off_t)seekoffs; |
|
} |
|
|
|
/* Send success message */ |
|
|
|
ret = ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 150, ' ', "Opening data connection"); |
|
if (ret < 0) |
|
{ |
|
ndbg("ftpd_response failed: %d\n", ret); |
|
goto errout_with_session; |
|
} |
|
|
|
for (;;) |
|
{ |
|
/* Read from the source (file or TCP connection) */ |
|
|
|
if (session->type == FTPD_SESSIONTYPE_A) |
|
{ |
|
buffer = &session->data.buffer[session->data.buflen >> 2]; |
|
wantsize = session->data.buflen >> 2; |
|
} |
|
else |
|
{ |
|
buffer = session->data.buffer; |
|
wantsize = session->data.buflen; |
|
} |
|
|
|
if (cmdtype == 0) |
|
{ |
|
/* Read from the file. Read returns the error condition via errno. */ |
|
|
|
rdbytes = read(session->fd, session->data.buffer, wantsize); |
|
if (rdbytes < 0) |
|
{ |
|
errval = errno; |
|
} |
|
} |
|
else |
|
{ |
|
/* Read from the TCP connection, ftpd_recve returns the negated error |
|
* condition. |
|
*/ |
|
|
|
rdbytes = ftpd_recv(session->data.sd, session->data.buffer, |
|
wantsize, session->rxtimeout); |
|
if (rdbytes < 0) |
|
{ |
|
errval = -rdbytes; |
|
} |
|
} |
|
|
|
/* A negative vaule of rdbytes indicates a read error. errval has the |
|
* (positive) error code associated with the failure. |
|
*/ |
|
|
|
if (rdbytes < 0) |
|
{ |
|
ndbg("Read failed: rdbytes=%d errval=%d\n", rdbytes, errval); |
|
(void)ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 550, ' ', "Data read error !"); |
|
ret = -errval; |
|
break; |
|
} |
|
|
|
/* A value of rdbytes == 0 means that we have read the entire source |
|
* stream. |
|
*/ |
|
|
|
if (rdbytes == 0) |
|
{ |
|
/* End-of-file */ |
|
|
|
(void)ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 226, ' ', "Transfer complete"); |
|
|
|
/* Return success */ |
|
|
|
ret = 0; |
|
break; |
|
} |
|
|
|
/* Write to the destination (file or TCP connection) */ |
|
|
|
if (session->type == FTPD_SESSIONTYPE_A) |
|
{ |
|
/* Change to ascii */ |
|
|
|
size_t offset = 0; |
|
buflen = 0; |
|
while (offset < ((size_t)rdbytes)) |
|
{ |
|
if (session->data.buffer[offset] == '\n') |
|
{ |
|
buffer[buflen++] = '\r'; |
|
} |
|
buffer[buflen++] = session->data.buffer[offset++]; |
|
} |
|
} |
|
else |
|
{ |
|
buffer = session->data.buffer; |
|
buflen = (size_t)rdbytes; |
|
} |
|
|
|
if (cmdtype == 0) |
|
{ |
|
/* Write to the TCP connection */ |
|
|
|
wrbytes = ftpd_send(session->data.sd, buffer, buflen, session->txtimeout); |
|
if (wrbytes < 0) |
|
{ |
|
errval = -wrbytes; |
|
ndbg("ftpd_send failed: %d\n", errval); |
|
} |
|
} |
|
else |
|
{ |
|
/* Write to the file */ |
|
|
|
wrbytes = write(session->fd, buffer, buflen); |
|
if (wrbytes < 0) |
|
{ |
|
errval = errno; |
|
ndbg("write() failed: %d\n", errval); |
|
} |
|
} |
|
|
|
/* If the number of bytes returned by the write is not equal to the |
|
* number that we wanted to write, then an error (or at least an |
|
* unhandled condition) has occurred. errval should should hold |
|
* the (positive) error code. |
|
*/ |
|
|
|
if (wrbytes != ((ssize_t)buflen)) |
|
{ |
|
ndbg("Write failed: wrbytes=%d errval=%d\n", wrbytes, errval); |
|
(void)ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 550, ' ', "Data send error !"); |
|
ret = -errval; |
|
break; |
|
} |
|
|
|
/* Get the next file offset */ |
|
|
|
pos += (off_t)wrbytes; |
|
} |
|
|
|
errout_with_session:; |
|
close(session->fd); |
|
session->fd = -1; |
|
|
|
if (isnew && ret < 0) |
|
{ |
|
(void)unlink(path); |
|
} |
|
|
|
errout_with_data:; |
|
(void)ftpd_dataclose(session); |
|
|
|
errout_with_path: |
|
free(abspath); |
|
|
|
errout: |
|
return ret; |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_listoption |
|
****************************************************************************/ |
|
|
|
static uint8_t ftpd_listoption(FAR char **param) |
|
{ |
|
FAR char *ptr = *param; |
|
uint8_t ret = 0; |
|
|
|
while (*ptr == '-') |
|
{ |
|
while (*ptr != '\0' && !isspace(*ptr)) |
|
{ |
|
switch (*ptr) |
|
{ |
|
case 'a': |
|
case 'A': |
|
ret |= FTPD_LISTOPTION_A; |
|
break; |
|
|
|
case 'l': |
|
case 'L': |
|
ret |= FTPD_LISTOPTION_L; |
|
break; |
|
|
|
case 'f': |
|
case 'F': |
|
ret |= FTPD_LISTOPTION_F; |
|
break; |
|
|
|
case 'r': |
|
case 'R': |
|
ret |= FTPD_LISTOPTION_R; |
|
break; |
|
|
|
default: |
|
ret |= FTPD_LISTOPTION_UNKNOWN; |
|
break; |
|
} |
|
|
|
ptr++; |
|
} |
|
|
|
if (*ptr != '\0') |
|
{ |
|
while (*ptr != '\0' && isspace(*ptr)) |
|
{ |
|
ptr++; |
|
} |
|
} |
|
} |
|
|
|
*param = ptr; |
|
return ret; |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: fptd_listscan |
|
****************************************************************************/ |
|
|
|
static int ftpd_listbuffer(FAR struct ftpd_session_s *session, FAR char *path, |
|
FAR struct stat *st, FAR char *buffer, |
|
size_t buflen, unsigned int opton) |
|
{ |
|
FAR char *name; |
|
size_t offset = 0; |
|
|
|
name = basename(path); |
|
|
|
if ((opton & FTPD_LISTOPTION_L) != 0) |
|
{ |
|
FAR const char *str; |
|
struct tm tm; |
|
time_t now; |
|
|
|
if (S_ISREG(st->st_mode) != 0) |
|
{ |
|
str = "-"; |
|
} |
|
else if (S_ISDIR(st->st_mode) != 0) |
|
{ |
|
str = "d"; |
|
} |
|
else if (S_ISCHR(st->st_mode) != 0) |
|
{ |
|
str = "c"; |
|
} |
|
else if (S_ISBLK(st->st_mode) != 0) |
|
{ |
|
str = "b"; |
|
} |
|
else if (S_ISFIFO(st->st_mode) != 0) |
|
{ |
|
str = "p"; |
|
} |
|
else if (S_ISLNK(st->st_mode) != 0) |
|
{ |
|
str = "l"; |
|
} |
|
else if (S_ISSOCK(st->st_mode) != 0) |
|
{ |
|
str = "s"; |
|
} |
|
else |
|
{ |
|
str = "-"; |
|
} |
|
|
|
offset += snprintf(&buffer[offset], buflen - offset, "%s", str); |
|
|
|
/* User */ |
|
|
|
str = ((st->st_mode & S_IRUSR) != 0) ? "r" : "-"; |
|
offset += snprintf(&buffer[offset], buflen - offset, "%s", str); |
|
|
|
str = ((st->st_mode & S_IWUSR) != 0) ? "w" : "-"; |
|
offset += snprintf(&buffer[offset], buflen - offset, "%s", str); |
|
|
|
if ((st->st_mode & S_ISUID) != 0 && (st->st_mode & S_IXUSR) != 0) |
|
{ |
|
str = "s"; |
|
} |
|
else if ((st->st_mode & S_ISUID) != 0) |
|
{ |
|
str = "S"; |
|
} |
|
else if ((st->st_mode & S_IXUSR) != 0) |
|
{ |
|
str = "x"; |
|
} |
|
else |
|
{ |
|
str = "-"; |
|
} |
|
|
|
offset += snprintf(&buffer[offset], buflen - offset, "%s", str); |
|
|
|
/* group */ |
|
|
|
str = ((st->st_mode & S_IRGRP) != 0) ? "r" : "-"; |
|
offset += snprintf(&buffer[offset], buflen - offset, "%s", str); |
|
|
|
str = ((st->st_mode & S_IWGRP) != 0) ? "w" : "-"; |
|
offset += snprintf(&buffer[offset], buflen - offset, "%s", str); |
|
|
|
if ((st->st_mode & S_ISGID) != 0 && (st->st_mode & S_IXGRP) != 0) |
|
{ |
|
str = "s"; |
|
} |
|
else if ((st->st_mode & S_ISGID) != 0) |
|
{ |
|
str = "S"; |
|
} |
|
else if ((st->st_mode & S_IXGRP) != 0) |
|
{ |
|
str = "x"; |
|
} |
|
else |
|
{ |
|
str = "-"; |
|
} |
|
|
|
offset += snprintf(&buffer[offset], buflen - offset, "%s", str); |
|
|
|
/* other */ |
|
|
|
str = ((st->st_mode & S_IROTH) != 0) ? "r" : "-"; |
|
offset += snprintf(&buffer[offset], buflen - offset, "%s", str); |
|
|
|
str = ((st->st_mode & S_IWOTH) != 0) ? "w" : "-"; |
|
offset += snprintf(&buffer[offset], buflen - offset, "%s", str); |
|
|
|
if ((st->st_mode & S_ISVTX) != 0 && (st->st_mode & S_IXOTH) != 0) |
|
{ |
|
str = "t"; |
|
} |
|
else if ((st->st_mode & S_ISVTX) != 0) |
|
{ |
|
str = "T"; |
|
} |
|
else if ((st->st_mode & S_IXOTH) != 0) |
|
{ |
|
str = "x"; |
|
} |
|
else |
|
{ |
|
str = "-"; |
|
} |
|
|
|
offset += snprintf(&buffer[offset], buflen - offset, "%s", str); |
|
|
|
#ifdef __NUTTX__ |
|
/* Fake nlink, user id, and group id */ |
|
|
|
offset += snprintf(&buffer[offset], buflen - offset, "%4u %8u %8u", 1, 1001, 512); |
|
#else |
|
/* nlink */ |
|
|
|
offset += snprintf(&buffer[offset], buflen - offset, "%4u", st->st_nlink); |
|
|
|
/* user id */ |
|
|
|
offset += snprintf(&buffer[offset], buflen - offset, " %8u", st->st_uid); |
|
|
|
/* group id */ |
|
|
|
offset += snprintf(&buffer[offset], buflen - offset, " %8u", st->st_gid); |
|
#endif |
|
|
|
/* size */ |
|
|
|
offset += snprintf(&buffer[offset], buflen - offset, " %8u", st->st_size); |
|
|
|
/* time */ |
|
|
|
memcpy(&tm, localtime((FAR const time_t *)&st->st_mtime), sizeof(tm)); |
|
offset += snprintf(&buffer[offset], buflen - offset, " %s %2u", |
|
g_monthtab[tm.tm_mon], tm.tm_mday); |
|
now = time(0); |
|
if ((now - st->st_mtime) > (time_t)(60 * 60 * 24 * 180)) |
|
{ |
|
offset += snprintf(&buffer[offset], buflen - offset, " %5u", |
|
tm.tm_year + 1900); |
|
} |
|
else |
|
{ |
|
offset += snprintf(&buffer[offset], buflen - offset, " %02u:%02u", |
|
tm.tm_hour, tm.tm_min); |
|
} |
|
|
|
/* basename */ |
|
|
|
offset += snprintf(&buffer[offset], buflen - offset, " %s", name); |
|
|
|
/* linkname */ |
|
|
|
#ifndef __NUTTX__ |
|
if (S_ISLNK(st->st_mode) != 0) |
|
{ |
|
FAR char *temp; |
|
int namelen; |
|
|
|
temp = (FAR char *)malloc(PATH_MAX + 1); |
|
if (temp) |
|
{ |
|
namelen = readlink(path, temp, PATH_MAX); |
|
if (namelen != (-1))\ |
|
{ |
|
temp[namelen] = '\0'; |
|
} |
|
|
|
offset += snprintf(&buffer[offset], buflen - offset, " -> %s", temp); |
|
free(temp); |
|
} |
|
} |
|
#endif |
|
|
|
/* end */ |
|
|
|
offset += snprintf(&buffer[offset], buflen - offset, "\r\n"); |
|
} |
|
else |
|
{ |
|
/* basename */ |
|
|
|
offset += snprintf(&buffer[offset], buflen - offset, "%s\r\n", name); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: fptd_listscan |
|
****************************************************************************/ |
|
|
|
static int fptd_listscan(FAR struct ftpd_session_s *session, FAR char *path, |
|
unsigned int opton) |
|
{ |
|
FAR char *temp; |
|
DIR *dir; |
|
struct dirent *entry; |
|
struct stat st; |
|
int ret; |
|
|
|
ret = stat(path, &st); |
|
if (ret < 0) |
|
{ |
|
return -errno; |
|
} |
|
|
|
if (!S_ISDIR(st.st_mode)) |
|
{ |
|
ret = ftpd_listbuffer(session, path, &st, session->data.buffer, |
|
session->data.buflen, opton); |
|
if (ret == 0) |
|
{ |
|
ret = ftpd_response(session->data.sd, session->txtimeout, |
|
"%s", (FAR char *)session->data.buffer); |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
dir = opendir(path); |
|
if (!dir) |
|
{ |
|
int errval = errno; |
|
ndbg("dir() failed\n", errval); |
|
return -errval; |
|
} |
|
|
|
for (;;) |
|
{ |
|
entry = readdir(dir); |
|
if (!entry) |
|
{ |
|
break; |
|
} |
|
|
|
if (entry->d_name[0] == '.') |
|
{ |
|
if ((opton & FTPD_LISTOPTION_A) == 0) |
|
{ |
|
continue; |
|
} |
|
} |
|
|
|
asprintf(&temp, "%s/%s", path, entry->d_name); |
|
if (!temp) |
|
{ |
|
continue; |
|
} |
|
|
|
ret = stat(temp, &st); |
|
if (ret < 0) |
|
{ |
|
free(temp); |
|
continue; |
|
} |
|
|
|
ret = ftpd_listbuffer(session, temp, &st, session->data.buffer, |
|
session->data.buflen, opton); |
|
if (ret >= 0) |
|
{ |
|
ret = ftpd_response(session->data.sd, session->txtimeout, |
|
"%s", session->data.buffer); |
|
} |
|
|
|
free(temp); |
|
if (ret < 0) |
|
{ |
|
break; |
|
} |
|
} |
|
|
|
(void)closedir(dir); |
|
return ret; |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_list |
|
****************************************************************************/ |
|
|
|
static int ftpd_list(FAR struct ftpd_session_s *session, unsigned int opton) |
|
{ |
|
FAR char *abspath; |
|
int ret; |
|
|
|
ret = ftpd_getpath(session, session->param, &abspath, NULL); |
|
if (ret >= 0) |
|
{ |
|
ret = fptd_listscan(session, abspath, opton); |
|
free(abspath); |
|
} |
|
|
|
return OK; |
|
} |
|
|
|
/**************************************************************************** |
|
* Command Handlers |
|
****************************************************************************/ |
|
/**************************************************************************** |
|
* Name: ftpd_command_user |
|
****************************************************************************/ |
|
|
|
static int ftpd_command_user(FAR struct ftpd_session_s *session) |
|
{ |
|
int ret; |
|
|
|
/* Clear session status */ |
|
|
|
session->flags = 0; |
|
session->restartpos = 0; |
|
|
|
/* Free session strings */ |
|
|
|
if (session->user) |
|
{ |
|
free(session->user); |
|
session->user = NULL; |
|
} |
|
|
|
if (session->renamefrom) |
|
{ |
|
free(session->renamefrom); |
|
session->renamefrom = NULL; |
|
} |
|
|
|
/* Set up the new user */ |
|
|
|
session->user = strdup(session->param); |
|
if (!session->user) |
|
{ |
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 451, ' ', "Memory exhausted !"); |
|
} |
|
session->flags |= FTPD_SESSIONFLAG_USER; |
|
|
|
/* If there is no account information, then no login is required. */ |
|
|
|
if (!session->head) |
|
{ |
|
FAR char *home; |
|
|
|
home = getenv("HOME"); |
|
session->curr = NULL; |
|
session->home = strdup(!home ? "/" : home); |
|
session->work = strdup("/"); |
|
|
|
ret = ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 230, ' ', "Login successful."); |
|
if (ret < 0) |
|
{ |
|
session->curr = NULL; |
|
} |
|
return ret; |
|
} |
|
|
|
/* Try to login with no password. This willwork if no password is |
|
* required for the account. |
|
*/ |
|
|
|
session->curr = ftpd_account_login(session, session->param, NULL); |
|
if (session->curr) |
|
{ |
|
ret = ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 230, ' ', "Login successful."); |
|
if (ret < 0) |
|
{ |
|
session->curr = NULL; |
|
} |
|
return ret; |
|
} |
|
|
|
/* A password is required */ |
|
|
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt2, 331, ' ', "Password required for ", |
|
session->user); |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_command_pass |
|
****************************************************************************/ |
|
|
|
static int ftpd_command_pass(FAR struct ftpd_session_s *session) |
|
{ |
|
int ret; |
|
|
|
if (!session->user) |
|
{ |
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 530, ' ', "Please login with USER !"); |
|
} |
|
|
|
session->curr = ftpd_account_login(session, session->user, session->param); |
|
if (session->curr) |
|
{ |
|
ret = ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 230, ' ', "Login successful."); |
|
if (ret < 0) |
|
{ |
|
session->curr = NULL; |
|
} |
|
return ret; |
|
} |
|
|
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 530, ' ', "Login incorrect !"); |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_command_syst |
|
****************************************************************************/ |
|
|
|
static int ftpd_command_syst(FAR struct ftpd_session_s *session) |
|
{ |
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 215, ' ', "UNIX Type: L8"); |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_command_type |
|
****************************************************************************/ |
|
|
|
static int ftpd_command_type(FAR struct ftpd_session_s *session) |
|
{ |
|
size_t parmlen = strlen(session->param); |
|
|
|
if (parmlen == 1) |
|
{ |
|
switch (toupper(session->param[0])) |
|
{ |
|
case 'A': |
|
{ |
|
session->type = FTPD_SESSIONTYPE_A; |
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 200, ' ', "Type set to A"); |
|
} |
|
|
|
case 'I': |
|
{ |
|
session->type = FTPD_SESSIONTYPE_I; |
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 200, ' ', "Type set to I"); |
|
} |
|
|
|
case 'L': |
|
{ |
|
session->type = FTPD_SESSIONTYPE_L8; |
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 200, ' ', |
|
"Type set to L (byte size 8)"); |
|
} |
|
|
|
default: |
|
{ |
|
session->type = FTPD_SESSIONTYPE_NONE; |
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 501, ' ', "Type unknown !"); |
|
} |
|
} |
|
} |
|
else if (parmlen == 3) |
|
{ |
|
if (toupper(session->param[0]) == 'L' && session->param[1] == ' ') |
|
{ |
|
if (session->param[2] == '8') |
|
{ |
|
session->type = FTPD_SESSIONTYPE_L8; |
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 200, ' ', "Type set to L 8"); |
|
} |
|
else |
|
{ |
|
session->type = FTPD_SESSIONTYPE_NONE; |
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 504, ' ', "Byte size must be 8 !"); |
|
} |
|
} |
|
} |
|
|
|
session->type = FTPD_SESSIONTYPE_NONE; |
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 500, ' ', "TYPE not understood !"); |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_command_mode |
|
****************************************************************************/ |
|
|
|
static int ftpd_command_mode(FAR struct ftpd_session_s *session) |
|
{ |
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 502, ' ', |
|
"MODE command not implemented !"); |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_command_abor |
|
****************************************************************************/ |
|
|
|
static int ftpd_command_abor(FAR struct ftpd_session_s *session) |
|
{ |
|
(void)ftpd_dataclose(session); |
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 426, ' ', |
|
"Transfer aborted. Data connection closed."); |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_command_quit |
|
****************************************************************************/ |
|
|
|
static int ftpd_command_quit(FAR struct ftpd_session_s *session) |
|
{ |
|
(void)ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 221, ' ', "Good-bye"); |
|
|
|
/* Return a negative value to force the server to disconnect */ |
|
|
|
return -1; |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_command_noop |
|
****************************************************************************/ |
|
|
|
static int ftpd_command_noop(FAR struct ftpd_session_s *session) |
|
{ |
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 200, ' ', |
|
"NOOP command successful"); |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_command_port |
|
****************************************************************************/ |
|
|
|
static int ftpd_command_port(FAR struct ftpd_session_s *session) |
|
{ |
|
uint8_t value[6]; |
|
unsigned int utemp; |
|
int temp; |
|
FAR char *str; |
|
int index; |
|
int ret; |
|
|
|
index = 0; |
|
while (index < 6) |
|
{ |
|
/* Get the next value from the comma-delimited string */ |
|
|
|
str = ftpd_strtok(true, ",", &session->param); |
|
if (*str == '\0') |
|
{ |
|
break; |
|
} |
|
|
|
/* ftpd_strtok differs from the real strtok in that it does not NUL- |
|
* terminate the strings. |
|
*/ |
|
|
|
if (session->param[0] != '\0') |
|
{ |
|
session->param[0] = '\0'; |
|
session->param++; |
|
} |
|
|
|
/* Get the next value from the list */ |
|
|
|
temp = atoi(str); |
|
if (temp < 0 || temp > 255) |
|
{ |
|
ret = ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 501, ' ', |
|
"Illegal PORT command"); |
|
if (ret < 0) |
|
{ |
|
ndbg("ftpd_response failed: %d\n", ret); |
|
return ret; |
|
} |
|
} |
|
|
|
value[index++] = (uint8_t)temp; |
|
} |
|
|
|
if (index < 6) |
|
{ |
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 501, ' ', "Illegal PORT command"); |
|
} |
|
|
|
(void)ftpd_dataclose(session); |
|
|
|
#if 1 /* Follow param */ |
|
|
|
memset(&session->data.addr, 0, sizeof(session->data.addr)); |
|
|
|
session->data.addr.in4.sin_family = AF_INET; |
|
|
|
utemp = (value[0] << 24) | (value[1] << 16) | (value[2] << 8) | (value[3]); |
|
session->data.addr.in4.sin_addr.s_addr = htonl((long)utemp); |
|
|
|
utemp = (value[4] << 8) | (value[5]); |
|
session->data.addr.in4.sin_port = htons((short)utemp); |
|
|
|
#else /* Follow command socket address */ |
|
|
|
session->data.addrlen = sizeof(session->data.addr); |
|
ret = getpeername(session->cmd.sd, (struct sockaddr *)&session->data.addr, |
|
&session->data.addrlen); |
|
if (ret >= 0) |
|
{ |
|
if (session->data.addr.ss.ss_family != AF_INET) |
|
{ |
|
memset(&session->data.addr, 0, sizeof(session->data.addr)); |
|
|
|
session->data.addr.in4.sin_family = AF_INET; |
|
|
|
utemp = (value[0] << 24) | (value[1] << 16) | (value[2] << 8) | (value[3]); |
|
session->data.addr.in4.sin_addr.s_addr = htonl(utemp); |
|
} |
|
|
|
utemp = (value[4] << 8) | (value[5]); |
|
session->data.addr.in4.sin_port = htons(utemp); |
|
} |
|
else |
|
{ |
|
memset(&session->data.addr, 0, sizeof(session->data.addr)); |
|
|
|
session->data.addr.in4.sin_family = AF_INET; |
|
|
|
utemp = (value[0] << 24) | (value[1] << 16) | (value[2] << 8) | (value[3]); |
|
session->data.addr.in4.sin_addr.s_addr = htonl(utemp); |
|
} |
|
|
|
utemp = (value[4] << 8) | (value[5]); |
|
session->data.addr.in4.sin_port = htons(utemp); |
|
#endif |
|
|
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 200, ' ', |
|
"PORT command successful"); |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_command_eprt |
|
****************************************************************************/ |
|
|
|
static int ftpd_command_eprt(FAR struct ftpd_session_s *session) |
|
{ |
|
FAR const char *str; |
|
FAR char *field[3]; |
|
sa_family_t family; |
|
size_t left; |
|
size_t right; |
|
int count; |
|
int index; |
|
|
|
left = 0; |
|
right = strlen(session->param); |
|
|
|
if (right <= 0) |
|
{ |
|
/* no message ? */ |
|
|
|
(void)ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 502, ' ', |
|
"EPRT command not implemented !"); |
|
return -EINVAL; |
|
} |
|
right--; |
|
|
|
while (session->param[left] != '\0') |
|
{ |
|
if (session->param[left] == '|') |
|
{ |
|
left++; |
|
break; |
|
} |
|
left++; |
|
} |
|
|
|
if (right <= 0 || left > right) |
|
{ |
|
/* Invalid format */ |
|
|
|
(void)ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 502, ' ', |
|
"EPRT command not implemented !"); |
|
return -EINVAL; |
|
} |
|
|
|
count = 3; |
|
for (index = 0; index < count; index++) |
|
{ |
|
field[index] = NULL; |
|
} |
|
|
|
str = (FAR const char *)&session->param[left]; |
|
for (index = 0; index < count && *str != '\0'; index++) |
|
{ |
|
field[index] = ftpd_strtok_alloc(true, ",|)", &str); |
|
if (!field[index]) |
|
{ |
|
break; |
|
} |
|
|
|
if (*str != '\0') |
|
{ |
|
str++; |
|
} |
|
} |
|
|
|
if (index < count) |
|
{ |
|
for (index = 0; index < count; index++) |
|
{ |
|
if (field[index]) |
|
{ |
|
free(field[index]); |
|
} |
|
} |
|
|
|
(void)ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 502, ' ', |
|
"EPRT command not implemented !"); |
|
return -EINVAL; |
|
} |
|
|
|
(void)ftpd_dataclose(session); |
|
|
|
memset(&session->data.addr, 0, sizeof(session->data.addr)); |
|
family = atoi(field[0]); |
|
#ifndef CONFIG_NET_IPv6 |
|
if (family == 1) |
|
{ |
|
family = AF_INET; |
|
|
|
session->data.addr.in4.sin_family = family; |
|
(void)inet_pton(family, field[1], &session->data.addr.in4.sin_addr); |
|
session->data.addr.in4.sin_port = htons((short)atoi(field[2])); |
|
} |
|
else |
|
#endif |
|
#ifdef CONFIG_NET_IPv6 |
|
if (family == 2) |
|
{ |
|
family = AF_INET6; |
|
|
|
session->data.addr.in6.sin6_family = family; |
|
(void)inet_pton(family, field[1], &session->data.addr.in6.sin6_addr); |
|
session->data.addr.in6.sin6_port = htons((short)atoi(field[2])); |
|
} |
|
else |
|
#endif |
|
{ |
|
ndbg("Unrecognized family: %d\n", family); |
|
family = AF_UNSPEC; |
|
} |
|
|
|
for (index = 0;index < count;index++) |
|
{ |
|
if (field[index]) |
|
{ |
|
free(field[index]); |
|
} |
|
} |
|
|
|
if (family == AF_UNSPEC) |
|
{ |
|
ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 502, ' ', |
|
"EPRT command not implemented !"); |
|
return -EINVAL; |
|
} |
|
|
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 200, ' ', "EPRT command successful"); |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_command_pwd |
|
****************************************************************************/ |
|
|
|
static int ftpd_command_pwd(FAR struct ftpd_session_s *session) |
|
{ |
|
FAR const char *workpath; |
|
|
|
if (!session->work) |
|
{ |
|
workpath = ""; |
|
} |
|
else |
|
{ |
|
workpath = session->work; |
|
} |
|
|
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
"%03u%c\"%s\" is current directory.\r\n", |
|
257, ' ', workpath); |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_command_cwd |
|
****************************************************************************/ |
|
|
|
static int ftpd_command_cwd(FAR struct ftpd_session_s *session) |
|
{ |
|
return ftpd_changedir(session, session->param); |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_command_cdup |
|
****************************************************************************/ |
|
|
|
static int ftpd_command_cdup(FAR struct ftpd_session_s *session) |
|
{ |
|
return ftpd_changedir(session, g_cdup); |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_command_rmd |
|
****************************************************************************/ |
|
|
|
static int ftpd_command_rmd(FAR struct ftpd_session_s *session) |
|
{ |
|
FAR char *abspath; |
|
FAR char *workpath; |
|
int ret; |
|
|
|
ret = ftpd_getpath(session, session->param, &abspath, &workpath); |
|
if (ret < 0) |
|
{ |
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 550, ' ', |
|
"Can not remove directory !"); |
|
} |
|
|
|
if (strcmp(session->home, abspath) == 0) |
|
{ |
|
free(abspath); |
|
free(workpath); |
|
|
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 550, ' ', |
|
"Can not remove home directory !"); |
|
} |
|
|
|
if (strcmp(session->work, workpath) == 0) |
|
{ |
|
free(abspath); |
|
free(workpath); |
|
|
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 550, ' ', |
|
"Can not remove current directory !"); |
|
} |
|
|
|
ret = rmdir(abspath); |
|
if (ret < 0) |
|
{ |
|
free(abspath); |
|
free(workpath); |
|
|
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 550, ' ', |
|
"Can not remove directory !"); |
|
} |
|
|
|
free(abspath); |
|
free(workpath); |
|
|
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 250, ' ', |
|
"RMD command successful"); |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_command_mkd |
|
****************************************************************************/ |
|
|
|
static int ftpd_command_mkd(FAR struct ftpd_session_s *session) |
|
{ |
|
FAR char *abspath; |
|
int ret; |
|
|
|
ret = ftpd_getpath(session, session->param, &abspath, NULL); |
|
if (ret < 0) |
|
{ |
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 550, ' ', |
|
"Can not make directory !"); |
|
} |
|
|
|
ret = mkdir(abspath, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); |
|
if (ret < 0) |
|
{ |
|
free(abspath); |
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 550, ' ', "Can not make directory !"); |
|
} |
|
|
|
free(abspath); |
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 250, ' ', "MKD command successful"); |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_command_dele |
|
****************************************************************************/ |
|
|
|
static int ftpd_command_dele(FAR struct ftpd_session_s *session) |
|
{ |
|
FAR char *abspath; |
|
FAR char *workpath; |
|
int ret; |
|
|
|
ret = ftpd_getpath(session, session->param, &abspath, &workpath); |
|
if (ret < 0) |
|
{ |
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 550, ' ', "Can not delete file !"); |
|
} |
|
|
|
if (strcmp(session->home, abspath) == 0) |
|
{ |
|
free(abspath); |
|
free(workpath); |
|
|
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 550, ' ', |
|
"Can not delete home directory !"); |
|
} |
|
|
|
if (strcmp(session->work, workpath) == 0) |
|
{ |
|
free(abspath); |
|
free(workpath); |
|
|
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 550, ' ', |
|
"Can not delete current directory !"); |
|
} |
|
|
|
ret = unlink(abspath); |
|
if (ret < 0) |
|
{ |
|
free(abspath); |
|
free(workpath); |
|
|
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 550, ' ', "Can not delete file !"); |
|
} |
|
|
|
free(abspath); |
|
free(workpath); |
|
|
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 250, ' ', "DELE command successful"); |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_command_pasv |
|
****************************************************************************/ |
|
|
|
static int ftpd_command_pasv(FAR struct ftpd_session_s *session) |
|
{ |
|
unsigned int value[6]; |
|
unsigned int temp; |
|
int ret; |
|
|
|
(void)ftpd_dataclose(session); |
|
|
|
session->data.addrlen = sizeof(session->data.addr); |
|
|
|
session->data.sd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); |
|
if (session->data.sd < 0) |
|
{ |
|
(void)ftpd_dataclose(session); |
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 425, ' ', "PASV socket create fail !"); |
|
} |
|
|
|
ret = getsockname(session->cmd.sd, (FAR struct sockaddr *)&session->data.addr, |
|
&session->data.addrlen); |
|
if (ret < 0) |
|
{ |
|
(void)ftpd_dataclose(session); |
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 425, ' ', "PASV getsockname fail !"); |
|
} |
|
|
|
#ifdef CONFIG_NET_IPv6 |
|
if (session->data.addr.ss.ss_family == AF_INET6) |
|
{ |
|
/* Convert ipv6 to ipv4 */ |
|
|
|
if ((IN6_IS_ADDR_V4MAPPED(&session->data.addr.in6.sin6_addr) != 0) || |
|
(IN6_IS_ADDR_V4COMPAT(&session->data.addr.in6.sin6_addr) != 0)) |
|
{ |
|
/* convert ipv6 to ipv4 */ |
|
|
|
in_addr in4addr; |
|
|
|
in4addr.s_addr = session->data.addr.in6.sin6_addr.s6_addr32[3]; |
|
|
|
memset(&session->data.addr, 0, sizeof(session->data.addr)); |
|
session->data.addr.in4.sin_family = AF_INET; |
|
session->data.addr.in4.sin_addr.s_addr = in4addr.s_addr; |
|
} |
|
} |
|
else |
|
#endif |
|
#ifndef CONFIG_NET_IPv6 |
|
if (session->data.addr.ss.ss_family == AF_INET) |
|
{ |
|
/* Fixed to ipv4 */ |
|
|
|
memset((FAR void *)(&session->data.addr), 0, sizeof(session->data.addr)); |
|
session->data.addr.in4.sin_family = AF_INET; |
|
session->data.addr.in4.sin_addr.s_addr = htonl(INADDR_ANY); |
|
} |
|
else |
|
#endif |
|
{ |
|
ndbg("Unsupported family\n"); |
|
} |
|
|
|
session->data.addr.in4.sin_port = 0; |
|
ret = bind(session->data.sd, (FAR const struct sockaddr *)&session->data.addr, |
|
session->data.addrlen); |
|
if (ret < 0) |
|
{ |
|
(void)ftpd_dataclose(session); |
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 425, ' ', "PASV bind fail !"); |
|
} |
|
|
|
ret = getsockname(session->data.sd, (FAR struct sockaddr *)&session->data.addr, |
|
&session->data.addrlen); |
|
if (ret < 0) |
|
{ |
|
(void)ftpd_dataclose(session); |
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 425, ' ', "PASV getsockname fail !"); |
|
} |
|
|
|
ret = listen(session->data.sd, 1); |
|
if (ret < 0) |
|
{ |
|
(void)ftpd_dataclose(session); |
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 425, ' ', "PASV listen fail !"); |
|
} |
|
|
|
if (ntohl(session->data.addr.in4.sin_addr.s_addr) == INADDR_ANY) |
|
{ |
|
(void)ftpd_dataclose(session); |
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 425, ' ', |
|
"Can not open passive connection"); |
|
} |
|
|
|
temp = ntohl(session->data.addr.in4.sin_addr.s_addr); |
|
value[0] = (temp >> 24) & 0xff; |
|
value[1] = (temp >> 16) & 0xff; |
|
value[2] = (temp >> 8) & 0xff; |
|
value[3] = (temp) & 0xff; |
|
|
|
temp = (unsigned int)ntohs(session->data.addr.in4.sin_port); |
|
value[4] = (temp >> 8) & 0xff; |
|
value[5] = (temp) & 0xff; |
|
|
|
ret = ftpd_response(session->cmd.sd, session->txtimeout, |
|
"%03u%cEntering passive mode (%u,%u,%u,%u,%u,%u).\r\n", |
|
227, ' ', |
|
value[0], value[1], value[2], |
|
value[3], value[4], value[5]); |
|
if (ret < 0) |
|
{ |
|
(void)ftpd_dataclose(session); |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_command_epsv |
|
****************************************************************************/ |
|
|
|
static int ftpd_command_epsv(FAR struct ftpd_session_s *session) |
|
{ |
|
int ret; |
|
|
|
(void)ftpd_dataclose(session); |
|
|
|
session->data.addrlen = sizeof(session->data.addr); |
|
|
|
#ifdef CONFIG_NET_IPv6 |
|
session->data.sd = socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP); |
|
if (session->data.sd < 0) |
|
{ |
|
session->data.sd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); |
|
} |
|
else |
|
{ |
|
#if defined(IPPROTO_IPV6) && defined(IPV6_V6ONLY) |
|
int ipv6only = 0; |
|
(void)setsockopt(session->data.sd, IPPROTO_IPV6, IPV6_V6ONLY, &ipv6only, sizeof(ipv6only)); |
|
#endif |
|
} |
|
#else |
|
session->data.sd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); |
|
#endif |
|
|
|
if (session->data.sd < 0) |
|
{ |
|
ret = ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 500, ' ', "EPSV socket create fail !"); |
|
(void)ftpd_dataclose(session); |
|
return ret; |
|
} |
|
|
|
ret = getsockname(session->cmd.sd, (FAR struct sockaddr *)&session->data.addr, |
|
&session->data.addrlen); |
|
if (ret < 0) |
|
{ |
|
ret = ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 500, ' ', "EPSV getsockname fail !"); |
|
(void)ftpd_dataclose(session); |
|
return ret; |
|
} |
|
|
|
#ifdef CONFIG_NET_IPv6 |
|
if (session->data.addr.ss.ss_family == AF_INET6) |
|
{ |
|
session->data.addr.in6.sin6_port = htons(0); |
|
} |
|
else if (session->data.addr.ss.ss_family == AF_INET) |
|
{ |
|
session->data.addr.in4.sin_port = htons(0); |
|
} |
|
#else |
|
session->data.addr.in4.sin_port = htons(0); |
|
#endif |
|
|
|
ret = bind(session->data.sd, (FAR const struct sockaddr *)&session->data.addr, |
|
session->data.addrlen); |
|
if (ret < 0) |
|
{ |
|
ret = ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 500, ' ', "EPSV bind fail !"); |
|
(void)ftpd_dataclose(session); |
|
return ret; |
|
} |
|
|
|
ret = getsockname(session->data.sd, (FAR struct sockaddr *)&session->data.addr, |
|
&session->data.addrlen); |
|
if (ret < 0) |
|
{ |
|
ret = ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 500, ' ', "EPSV getsockname fail !"); |
|
(void)ftpd_dataclose(session); |
|
return ret; |
|
} |
|
|
|
ret = listen(session->data.sd, 1); |
|
if (ret < 0) |
|
{ |
|
ret = ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 500, ' ', "EPSV listen fail !"); |
|
(void)ftpd_dataclose(session); |
|
return ret; |
|
} |
|
|
|
#ifdef CONFIG_NET_IPv6 |
|
if (session->data.addr.ss.ss_family == AF_INET6) |
|
{ |
|
ret = ftpd_response(session->cmd.sd, session->txtimeout, |
|
"%03u%cEntering Extended Passive Mode (|||%u|).\r\n", |
|
229, ' ', |
|
ntohs(session->data.addr.in6.sin6_port)); |
|
if (ret < 0) |
|
{ |
|
(void)ftpd_dataclose(session); |
|
return ret; |
|
} |
|
} |
|
else |
|
#else |
|
if (session->data.addr.ss.ss_family == AF_INET) |
|
{ |
|
ret = ftpd_response(session->cmd.sd, session->txtimeout, |
|
"%03u%cEntering Extended Passive Mode (|%u||%u|).\r\n", |
|
229, ' ', 1, |
|
ntohs(session->data.addr.in4.sin_port)); |
|
if (ret < 0) |
|
{ |
|
(void)ftpd_dataclose(session); |
|
return ret; |
|
} |
|
} |
|
else |
|
#endif |
|
{ |
|
ret = ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 502, ' ', |
|
"EPSV command not implemented !"); |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_command_list |
|
****************************************************************************/ |
|
|
|
static int ftpd_command_list(FAR struct ftpd_session_s *session) |
|
{ |
|
uint8_t opton = FTPD_LISTOPTION_L; |
|
int ret; |
|
|
|
ret = ftpd_dataopen(session); |
|
if (ret < 0) |
|
{ |
|
return 0; |
|
} |
|
|
|
ret = ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 150, ' ', |
|
"Opening ASCII mode data connection for file list"); |
|
if (ret < 0) |
|
{ |
|
(void)ftpd_dataclose(session); |
|
return ret; |
|
} |
|
|
|
opton |= ftpd_listoption((char **)(&session->param)); |
|
(void)ftpd_list(session, opton); |
|
|
|
ret = ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 226, ' ', "Transfer complete"); |
|
|
|
(void)ftpd_dataclose(session); |
|
return ret; |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_command_nlst |
|
****************************************************************************/ |
|
|
|
static int ftpd_command_nlst(FAR struct ftpd_session_s *session) |
|
{ |
|
uint8_t opton = 0; |
|
int ret; |
|
|
|
ret = ftpd_dataopen(session); |
|
if (ret < 0) |
|
{ |
|
return 0; |
|
} |
|
|
|
ret = ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 150, ' ', |
|
"Opening ASCII mode data connection for file list"); |
|
if (ret < 0) |
|
{ |
|
(void)ftpd_dataclose(session); |
|
return ret; |
|
} |
|
|
|
opton |= ftpd_listoption((char **)(&session->param)); |
|
(void)ftpd_list(session, opton); |
|
|
|
ret = ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 226, ' ', "Transfer complete"); |
|
|
|
(void)ftpd_dataclose(session); |
|
return ret; |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_command_acct |
|
****************************************************************************/ |
|
|
|
static int ftpd_command_acct(FAR struct ftpd_session_s *session) |
|
{ |
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 502, ' ', "ACCT command not implemented !"); |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_command_size |
|
****************************************************************************/ |
|
|
|
static int ftpd_command_size(FAR struct ftpd_session_s *session) |
|
{ |
|
FAR char *abspath; |
|
FAR char *path; |
|
struct stat st; |
|
FAR FILE *outstream; |
|
off_t offset; |
|
int ch; |
|
int status; |
|
int ret; |
|
|
|
ret = ftpd_getpath(session, session->param, &abspath, NULL); |
|
if (ret < 0) |
|
{ |
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 550, ' ', "Unknown size !"); |
|
} |
|
path = abspath; |
|
|
|
ret = 0; |
|
switch (session->type) |
|
{ |
|
case FTPD_SESSIONTYPE_NONE: |
|
case FTPD_SESSIONTYPE_L8: |
|
case FTPD_SESSIONTYPE_I: |
|
{ |
|
status = stat(path, &st); |
|
if (status < 0) |
|
{ |
|
ret = ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt2, 550, ' ', session->param, |
|
": not a regular file."); |
|
} |
|
else if (!S_ISREG(st.st_mode)) |
|
{ |
|
ret = ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt2, 550, ' ', session->param, |
|
": not a regular file."); |
|
} |
|
else |
|
{ |
|
ret = ftpd_response(session->cmd.sd, session->txtimeout, |
|
"%03u%c%llu\r\n", 213, ' ', (unsigned long long)st.st_size); |
|
} |
|
} |
|
break; |
|
|
|
case FTPD_SESSIONTYPE_A: |
|
{ |
|
status = stat(path, &st); |
|
if (status < 0) |
|
{ |
|
ret = ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt2, 550, ' ', session->param, |
|
": not a regular file."); |
|
if (ret < 0) |
|
{ |
|
return ret; |
|
} |
|
} |
|
else if (!S_ISREG(st.st_mode)) |
|
{ |
|
ret = ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt2, 550, ' ', session->param, |
|
": not a regular file."); |
|
if (ret < 0) |
|
{ |
|
return ret; |
|
} |
|
} |
|
|
|
|
|
outstream = fopen(path, "r"); |
|
if (!outstream) |
|
{ |
|
ret = ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt2, 550, ' ', session->param, |
|
": Can not open file !"); |
|
if (ret < 0) |
|
{ |
|
return ret; |
|
} |
|
} |
|
|
|
offset = 0; |
|
for (;;) |
|
{ |
|
ch = getc(outstream); |
|
if (ch == EOF) |
|
{ |
|
break; |
|
} |
|
else if (ch == 'c') |
|
{ |
|
offset++; |
|
} |
|
offset++; |
|
} |
|
|
|
(void)fclose(outstream); |
|
ret = ftpd_response(session->cmd.sd, session->txtimeout, |
|
"%03u%c%llu\r\n", 213, ' ', (unsigned long long)offset); |
|
} |
|
break; |
|
|
|
default: |
|
{ |
|
ret = ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 504, ' ', "SIZE not implemented for type"); |
|
} |
|
break; |
|
} |
|
|
|
free(abspath); |
|
return ret; |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_command_stru |
|
****************************************************************************/ |
|
|
|
static int ftpd_command_stru(FAR struct ftpd_session_s *session) |
|
{ |
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 502, ' ', "STRU command not implemented !"); |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: |
|
****************************************************************************/ |
|
|
|
static int ftpd_command_rnfr(FAR struct ftpd_session_s *session) |
|
{ |
|
FAR char *abspath; |
|
FAR char *path; |
|
struct stat st; |
|
int ret; |
|
|
|
if (session->renamefrom) |
|
{ |
|
free(session->renamefrom); |
|
session->renamefrom = NULL; |
|
} |
|
|
|
ret = ftpd_getpath(session, session->param, &abspath, NULL); |
|
if (ret < 0) |
|
{ |
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 550, ' ', "RNFR error !"); |
|
} |
|
path = abspath; |
|
|
|
ret = stat(path, &st); |
|
if (ret < 0) |
|
{ |
|
free(abspath); |
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt2, 550, ' ', session->param, |
|
": No such file or directory."); |
|
} |
|
|
|
session->renamefrom = abspath; |
|
session->flags |= FTPD_SESSIONFLAG_RENAMEFROM; |
|
|
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 350, ' ', "RNFR successful"); |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_command_rnto |
|
****************************************************************************/ |
|
|
|
static int ftpd_command_rnto(FAR struct ftpd_session_s *session) |
|
{ |
|
FAR char *abspath; |
|
int ret; |
|
|
|
if (!session->renamefrom) |
|
{ |
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 550, ' ', "RNTO error !"); |
|
} |
|
|
|
ret = ftpd_getpath(session, session->param, &abspath, NULL); |
|
if (ret < 0) |
|
{ |
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 550, ' ', "RNTO error !"); |
|
} |
|
|
|
ret = rename(session->renamefrom, abspath); |
|
if (ret < 0) |
|
{ |
|
free(abspath); |
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt2, 550, ' ', session->param, |
|
": Rename error."); |
|
} |
|
|
|
free(abspath); |
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 250, ' ', "Rename successful"); |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_command_retr |
|
****************************************************************************/ |
|
|
|
static int ftpd_command_retr(FAR struct ftpd_session_s *session) |
|
{ |
|
return ftpd_stream(session, 0); |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_command_stor |
|
****************************************************************************/ |
|
|
|
static int ftpd_command_stor(FAR struct ftpd_session_s *session) |
|
{ |
|
return ftpd_stream(session, 1); |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_command_appe |
|
****************************************************************************/ |
|
|
|
static int ftpd_command_appe(FAR struct ftpd_session_s *session) |
|
{ |
|
return ftpd_stream(session, 2); |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_command_rest |
|
****************************************************************************/ |
|
|
|
static int ftpd_command_rest(FAR struct ftpd_session_s *session) |
|
{ |
|
#ifdef CONFIG_HAVE_LONG_LONG |
|
session->restartpos = (off_t)atoll(session->param); |
|
#else |
|
session->restartpos = (off_t)atoi(session->param); |
|
#endif |
|
session->flags |= FTPD_SESSIONFLAG_RESTARTPOS; |
|
|
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 320, ' ', "Restart position ready"); |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_command_mdtm |
|
****************************************************************************/ |
|
|
|
static int ftpd_command_mdtm(FAR struct ftpd_session_s *session) |
|
{ |
|
FAR char *abspath; |
|
FAR char *path; |
|
struct stat st; |
|
struct tm tm; |
|
int ret; |
|
|
|
ret = ftpd_getpath(session, session->param, &abspath, NULL); |
|
if (ret <0) |
|
{ |
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 550, ' ', "Unknown size !"); |
|
} |
|
path = abspath; |
|
|
|
ret = stat(path, &st); |
|
if (ret < 0) |
|
{ |
|
free(abspath); |
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt2, 550, ' ', session->param, |
|
": not a plain file."); |
|
} |
|
|
|
if (!S_ISREG(st.st_mode)) |
|
{ |
|
free(abspath); |
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt2, 550, ' ', session->param, |
|
": not a plain file."); |
|
} |
|
|
|
free(abspath); |
|
|
|
memcpy(&tm, gmtime(&st.st_mtime), sizeof(tm)); |
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
"%03u%c%04u%02u%02u%02u%02u%02u\r\n", 213, ' ', |
|
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, |
|
tm.tm_hour, tm.tm_min, tm.tm_sec); |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_command_opts |
|
****************************************************************************/ |
|
|
|
static int ftpd_command_opts(FAR struct ftpd_session_s *session) |
|
{ |
|
FAR char *str; |
|
FAR char *option; |
|
FAR char *value; |
|
bool remote = false; |
|
bool local = false; |
|
|
|
/* token: name and value */ |
|
|
|
str = session->param; |
|
option = ftpd_strtok(true, " \t", &str); |
|
|
|
/* Unlike the "real" strtok, ftpd_strtok does not NUL-terminate |
|
* the returned string. |
|
*/ |
|
|
|
if (*str != '\0') |
|
{ |
|
*str = '\0'; |
|
str++; |
|
} |
|
value = str; |
|
|
|
if (strcasecmp(option, "UTF8") == 0 || strcasecmp(option, "UTF-8") == 0) |
|
{ |
|
FAR char *lang; |
|
|
|
if (value[0] == '\0' || strcasecmp(value, "ON") == 0 || |
|
strcasecmp(value, "ENABLE") == 0 || strcasecmp(value, "TRUE") == 0) |
|
{ |
|
remote = true; |
|
} |
|
else { |
|
remote = false; |
|
} |
|
|
|
lang = getenv("LANG"); |
|
if (lang) |
|
{ |
|
if (strcasestr(lang, "UTF8") || strcasestr(lang, "UTF-8")) |
|
{ |
|
local = true; |
|
} |
|
else |
|
{ |
|
local = false; |
|
} |
|
} |
|
#if 1 /* OPTION: UTF-8 is default */ |
|
else |
|
{ |
|
local = true; |
|
} |
|
#endif |
|
|
|
if (remote != local) |
|
{ |
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 504, ' ', "UIF-8 disabled"); |
|
} |
|
|
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 200, ' ', "OK, UTF-8 enabled"); |
|
} |
|
|
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
"%03u%c%s%s%s\r\n", 501, ' ', "OPTS: ", option, |
|
" not understood"); |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_command_site |
|
****************************************************************************/ |
|
|
|
static int ftpd_command_site(FAR struct ftpd_session_s *session) |
|
{ |
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 502, ' ', "SITE command not implemented !"); |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_command_help |
|
****************************************************************************/ |
|
|
|
static int ftpd_command_help(FAR struct ftpd_session_s *session) |
|
{ |
|
int index; |
|
int ret; |
|
|
|
index = 0; |
|
while (g_ftpdhelp[index]) |
|
{ |
|
if (index == 0 || !g_ftpdhelp[index + 1]) |
|
{ |
|
ret = ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 214, |
|
!g_ftpdhelp[index + 1] ? ' ' : '-', |
|
g_ftpdhelp[index]); |
|
} |
|
else |
|
{ |
|
ret = ftpd_response(session->cmd.sd, session->txtimeout, |
|
"%c%s\r\n", ' ', g_ftpdhelp[index]); |
|
} |
|
|
|
if (ret < 0) |
|
{ |
|
return ret; |
|
} |
|
|
|
index++; |
|
} |
|
|
|
return OK; |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_command |
|
****************************************************************************/ |
|
|
|
static int ftpd_command(FAR struct ftpd_session_s *session) |
|
{ |
|
int index = 0; |
|
|
|
/* Search the command table for a matching command */ |
|
|
|
for (index = 0; g_ftpdcmdtab[index].command; index++) |
|
{ |
|
/* Does the command string match this entry? */ |
|
|
|
if (strcmp(session->command, g_ftpdcmdtab[index].command) == 0) |
|
{ |
|
/* Yes.. is a login required to execute this command? */ |
|
|
|
if ((g_ftpdcmdtab[index].flags & FTPD_CMDFLAG_LOGIN) != 0) |
|
{ |
|
/* Yes... Check if the user is logged in */ |
|
|
|
if (!session->curr && session->head) |
|
{ |
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 530, ' ', |
|
"Please login with USER and PASS !"); |
|
} |
|
} |
|
|
|
/* Check if there is a handler for the command */ |
|
|
|
if (g_ftpdcmdtab[index].handler) |
|
{ |
|
/* Yess.. invoke the command handler. */ |
|
|
|
return g_ftpdcmdtab[index].handler(session); |
|
} |
|
|
|
/* No... this command is not in the command table. Break out of |
|
* the loop and send the 500 message. |
|
*/ |
|
|
|
break; |
|
} |
|
} |
|
|
|
/* There is nothing in the command table matching this command */ |
|
|
|
return ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt2, 500, ' ', session->command, |
|
" not understood"); |
|
} |
|
|
|
/**************************************************************************** |
|
* Worker Thread |
|
****************************************************************************/ |
|
/**************************************************************************** |
|
* Name: ftpd_startworker |
|
****************************************************************************/ |
|
|
|
static int ftpd_startworker(pthread_startroutine_t handler, FAR void *arg, |
|
size_t stacksize) |
|
{ |
|
pthread_t threadid; |
|
pthread_attr_t attr; |
|
int ret; |
|
|
|
/* Initialize the thread attributes */ |
|
|
|
ret = pthread_attr_init(&attr); |
|
if (ret != 0) |
|
{ |
|
ndbg("pthread_attr_init() failed: %d\n", ret); |
|
goto errout; |
|
} |
|
|
|
/* The set the thread stack size */ |
|
|
|
ret = pthread_attr_setstacksize(&attr, stacksize); |
|
if (ret != 0) |
|
{ |
|
ndbg("pthread_attr_setstacksize() failed: %d\n", ret); |
|
goto errout_with_attr; |
|
} |
|
|
|
/* And create the thread */ |
|
|
|
ret = pthread_create(&threadid, &attr, handler, arg); |
|
if (ret != 0) |
|
{ |
|
ndbg("pthread_create() failed: %d\n", ret); |
|
goto errout_with_attr; |
|
} |
|
|
|
/* Put the thread in the detached stated */ |
|
|
|
ret = pthread_detach(threadid); |
|
if (ret != 0) |
|
{ |
|
ndbg("pthread_detach() failed: %d\n", ret); |
|
} |
|
|
|
errout_with_attr: |
|
pthread_attr_destroy(&attr); |
|
errout: |
|
return -ret; |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_freesession |
|
****************************************************************************/ |
|
|
|
static void ftpd_freesession(FAR struct ftpd_session_s *session) |
|
{ |
|
/* Free resources */ |
|
|
|
if (session->renamefrom) |
|
{ |
|
free(session->renamefrom); |
|
} |
|
|
|
if (session->work) |
|
{ |
|
free(session->work); |
|
} |
|
|
|
if (session->home) |
|
{ |
|
free(session->home); |
|
} |
|
|
|
if (session->user) |
|
{ |
|
free(session->user); |
|
} |
|
|
|
if (session->fd < 0) |
|
{ |
|
close(session->fd); |
|
} |
|
|
|
if (session->data.buffer) |
|
{ |
|
free(session->data.buffer); |
|
} |
|
|
|
(void)ftpd_dataclose(session); |
|
|
|
if (session->cmd.buffer) |
|
{ |
|
free(session->cmd.buffer); |
|
} |
|
|
|
if (session->cmd.sd <0) |
|
{ |
|
close(session->cmd.sd); |
|
} |
|
|
|
free(session); |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_workersetup |
|
****************************************************************************/ |
|
|
|
static void ftpd_workersetup(FAR struct ftpd_session_s *session) |
|
{ |
|
#if defined(CONFIG_NET_HAVE_IPTOS) || defined(CONFIG_NET_HAVE_OOBINLINE) |
|
int temp; |
|
#endif |
|
#ifdef CONFIG_NET_HAVE_SOLINGER |
|
struct linger ling; |
|
#endif |
|
|
|
#ifdef CONFIG_NET_HAVE_IPTOS |
|
temp = IPTOS_LOWDELAY; |
|
(void)setsockopt(session->cmd.sd, IPPROTO_IP, IP_TOS, &temp, sizeof(temp)); |
|
#endif |
|
|
|
#ifdef CONFIG_NET_HAVE_OOBINLINE |
|
temp = 1; |
|
(void)setsockopt(session->cmd.sd, SOL_SOCKET, SO_OOBINLINE, &temp, sizeof(temp)); |
|
#endif |
|
|
|
#ifdef CONFIG_NET_HAVE_SOLINGER |
|
(void)memset(&ling, 0, sizeof(ling)); |
|
ling.l_onoff = 1; |
|
ling.l_linger = 4; |
|
(void)setsockopt(session->cmd.sd, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling)); |
|
#endif |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_worker |
|
****************************************************************************/ |
|
|
|
static FAR void *ftpd_worker(FAR void *arg) |
|
{ |
|
FAR struct ftpd_session_s *session = (FAR struct ftpd_session_s *)arg; |
|
ssize_t recvbytes; |
|
size_t offset; |
|
uint8_t ch; |
|
int ret; |
|
|
|
nvdbg("Worker started\n"); |
|
DEBUGASSERT(session); |
|
|
|
/* Configure the session sockets */ |
|
|
|
ftpd_workersetup(session); |
|
|
|
/* Send the welcoming message */ |
|
|
|
ret = ftpd_response(session->cmd.sd, session->txtimeout, |
|
g_respfmt1, 220, ' ', CONFIG_FTPD_SERVERID); |
|
if (ret < 0) |
|
{ |
|
ndbg("ftpd_response() failed: %d\n", ret); |
|
ftpd_freesession(session); |
|
return NULL; |
|
} |
|
|
|
/* Then loop processing FTP commands */ |
|
|
|
for (;;) |
|
{ |
|
/* Receive the next command */ |
|
|
|
recvbytes = ftpd_recv(session->cmd.sd, session->cmd.buffer, |
|
session->cmd.buflen - 1, session->rxtimeout); |
|
|
|
/* recbytes < 0 is a receive failure (posibily a timeout); |
|
* recbytes == 0 indicates that we have lost the connection. |
|
*/ |
|
|
|
if (recvbytes <= 0) |
|
{ |
|
/* Break out of the server loop */ |
|
|
|
break; |
|
} |
|
|
|
/* Make sure that the recevied string is NUL terminated */ |
|
|
|
session->cmd.buffer[recvbytes] = '\0'; |
|
|
|
/* TELNET protocol (RFC854) |
|
* IAC 255(FFH) interpret as command: |
|
* IP 244(F4H) interrupt process--permanently |
|
* DM 242(F2H) data mark--for connect. cleaning |
|
*/ |
|
|
|
offset = 0; |
|
while (recvbytes > 0) |
|
{ |
|
ch = session->cmd.buffer[offset]; |
|
if (ch != 0xff && ch != 0xf4 && ch != 0xf2) |
|
{ |
|
break; |
|
} |
|
|
|
(void)ftpd_send(session->cmd.sd, &session->cmd.buffer[offset], 1, session->txtimeout); |
|
|
|
offset++; |
|
recvbytes--; |
|
} |
|
|
|
/* Just continue if there was nothing of interest in the packet */ |
|
|
|
if (recvbytes <= 0) |
|
{ |
|
continue; |
|
} |
|
|
|
/* Make command message */ |
|
|
|
session->command = &session->cmd.buffer[offset]; |
|
while (session->cmd.buffer[offset] != '\0') |
|
{ |
|
if (session->cmd.buffer[offset] == '\r' && |
|
session->cmd.buffer[offset + ((ssize_t)1)] == '\n') |
|
{ |
|
session->cmd.buffer[offset] = '\0'; |
|
break; |
|
} |
|
offset++; |
|
} |
|
|
|
/* Parse command and param tokens */ |
|
|
|
session->param = session->command; |
|
session->command = ftpd_strtok(true, " \t", &session->param); |
|
|
|
/* Unlike the "real" strtok, ftpd_strtok does not NUL-terminate |
|
* the returned string. |
|
*/ |
|
|
|
if (session->param[0] != '\0') |
|
{ |
|
session->param[0] = '\0'; |
|
session->param++; |
|
} |
|
|
|
/* Dispatch the FTP command */ |
|
|
|
ret = ftpd_command(session); |
|
if (ret < 0) |
|
{ |
|
ndbg("Disconnected by the command handler: %d\n", ret); |
|
break; |
|
} |
|
} |
|
|
|
ftpd_freesession(session); |
|
return NULL; |
|
} |
|
|
|
/**************************************************************************** |
|
* Public Functions |
|
****************************************************************************/ |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_open |
|
* |
|
* Description: |
|
* Create an instance of the FTPD server and return a handle that can be |
|
* used to run the server. |
|
* |
|
* Input Parameters: |
|
* None |
|
* |
|
* Returned Value: |
|
* On success, a non-NULL handle is returned that can be used to reference |
|
* the server instance. |
|
* |
|
****************************************************************************/ |
|
|
|
FTPD_SESSION ftpd_open(void) |
|
{ |
|
FAR struct ftpd_server_s *server; |
|
|
|
server = ftpd_openserver(21); |
|
if (!server) |
|
{ |
|
server = ftpd_openserver(2211); |
|
} |
|
|
|
return (FTPD_SESSION)server; |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_adduser |
|
* |
|
* Description: |
|
* Add one FTP user. |
|
* |
|
* Input Parameters: |
|
* handle - A handle previously returned by ftpd_open |
|
* accountflags - The characteristics of this user (see FTPD_ACCOUNTFLAGS_* |
|
* definitions). |
|
* user - The user login name. May be NULL indicating that no login is |
|
* required. |
|
* passwd - The user password. May be NULL indicating that no password |
|
* is required. |
|
* home - The user home directory. May be NULL. |
|
* |
|
* Returned Value: |
|
* Zero is returned on success. A negated errno value is return on |
|
* failure. |
|
* |
|
****************************************************************************/ |
|
|
|
int ftpd_adduser(FTPD_SESSION handle, uint8_t accountflags, |
|
FAR const char *user, FAR const char *passwd, |
|
FAR const char *home) |
|
{ |
|
FAR struct ftpd_server_s *server; |
|
FAR struct ftpd_account_s *newaccount; |
|
int ret; |
|
|
|
DEBUGASSERT(handle); |
|
|
|
newaccount = ftpd_account_new(user, accountflags); |
|
if (!newaccount) |
|
{ |
|
ndbg("Failed to allocte memory to the account\n"); |
|
ret = -ENOMEM; |
|
goto errout; |
|
} |
|
|
|
ret = ftpd_account_setpassword(newaccount, passwd); |
|
if (ret < 0) |
|
{ |
|
ndbg("ftpd_account_setpassword failed: %d\n", ret); |
|
goto errout_with_account; |
|
} |
|
|
|
ret = ftpd_account_sethome(newaccount, home); |
|
if (ret < 0) |
|
{ |
|
ndbg("ftpd_account_sethome failed: %d\n", ret); |
|
goto errout_with_account; |
|
} |
|
|
|
server = (FAR struct ftpd_server_s *)handle; |
|
ret = ftpd_account_add(server, newaccount); |
|
if (ret < 0) |
|
{ |
|
ndbg("ftpd_account_add failed: %d\n", ret); |
|
goto errout_with_account; |
|
} |
|
|
|
return OK; |
|
|
|
errout_with_account: |
|
ftpd_account_free(newaccount); |
|
|
|
errout: |
|
return ret; |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_session |
|
* |
|
* Description: |
|
* Execute the FTPD server. This thread does not return until either (1) |
|
* the timeout expires with no connection, (2) some other error occurs, or |
|
* (2) a connection was accepted and an FTP worker thread was started to |
|
* service the session. |
|
* |
|
* Input Parameters: |
|
* handle - A handle previously returned by ftpd_open |
|
* timeout - A time in milliseconds to wait for a connection. If this |
|
* time elapses with no connected, the -ETIMEDOUT error will be returned. |
|
* |
|
* Returned Value: |
|
* Zero is returned if the FTP worker was started. On failure, a negated |
|
* errno value is returned to indicate why the servier terminated. |
|
* -ETIMEDOUT indicates that the user-provided timeout elapsed with no |
|
* connection. |
|
* |
|
****************************************************************************/ |
|
|
|
int ftpd_session(FTPD_SESSION handle, int timeout) |
|
{ |
|
FAR struct ftpd_server_s *server; |
|
FAR struct ftpd_session_s *session; |
|
int ret; |
|
|
|
DEBUGASSERT(handle); |
|
|
|
server = (FAR struct ftpd_server_s *)handle; |
|
|
|
/* Allocate a session */ |
|
|
|
session = (FAR struct ftpd_session_s *)zalloc(sizeof(struct ftpd_session_s)); |
|
if (!session) |
|
{ |
|
ndbg("Failed to allocate session\n"); |
|
ret = -ENOMEM; |
|
goto errout; |
|
} |
|
|
|
/* Initialize the session */ |
|
|
|
session->server = server; |
|
session->head = server->head; |
|
session->curr = NULL; |
|
session->flags = 0; |
|
session->txtimeout = -1; |
|
session->rxtimeout = -1; |
|
session->cmd.sd = (int)(-1); |
|
session->cmd.addrlen = (socklen_t)sizeof(session->cmd.addr); |
|
session->cmd.buflen = (size_t)CONFIG_FTPD_CMDBUFFERSIZE; |
|
session->cmd.buffer = NULL; |
|
session->command = NULL; |
|
session->param = NULL; |
|
session->data.sd = -1; |
|
session->data.addrlen = sizeof(session->data.addr); |
|
session->data.buflen = CONFIG_FTPD_DATABUFFERSIZE; |
|
session->data.buffer = NULL; |
|
session->restartpos = 0; |
|
session->fd = -1; |
|
session->user = NULL; |
|
session->type = FTPD_SESSIONTYPE_NONE; |
|
session->home = NULL; |
|
session->work = NULL; |
|
session->renamefrom = NULL; |
|
|
|
/* Allocate a command buffer */ |
|
|
|
session->cmd.buffer = (FAR char *)malloc(session->cmd.buflen); |
|
if (!session->cmd.buffer) |
|
{ |
|
ndbg("Failed to allocate command buffer\n"); |
|
ret = -ENOMEM; |
|
goto errout_with_session; |
|
} |
|
|
|
/* Allocate a data buffer */ |
|
|
|
session->data.buffer = (FAR char *)malloc(session->data.buflen); |
|
if (!session->data.buffer) |
|
{ |
|
ndbg("Failed to allocate data buffer\n"); |
|
ret = -ENOMEM; |
|
goto errout_with_session; |
|
} |
|
|
|
/* Accept a connection */ |
|
|
|
session->cmd.sd = ftpd_accept(server->sd, (FAR void *)&session->cmd.addr, |
|
&session->cmd.addrlen, timeout); |
|
if (session->cmd.sd < 0) |
|
{ |
|
/* Only report interesting, infrequent errors (not the common timeout) */ |
|
|
|
#ifdef CONFIG_DEBUG_NET |
|
if (session->cmd.sd != -ETIMEDOUT) |
|
{ |
|
ndbg("ftpd_accept() failed: %d\n", session->cmd.sd); |
|
} |
|
#endif |
|
ret = session->cmd.sd; |
|
goto errout_with_session; |
|
} |
|
|
|
/* And create a worker thread to service the session */ |
|
|
|
ret = ftpd_startworker(ftpd_worker, (FAR void *)session, |
|
CONFIG_FTPD_WORKERSTACKSIZE); |
|
if (ret < 0) |
|
{ |
|
ndbg("ftpd_startworker() failed: %d\n", ret); |
|
goto errout_with_session; |
|
} |
|
|
|
/* Successfully connected an launched the worker thread */ |
|
|
|
return 0; |
|
|
|
errout_with_session: |
|
ftpd_freesession(session); |
|
errout: |
|
return ret; |
|
} |
|
|
|
/**************************************************************************** |
|
* Name: ftpd_close |
|
* |
|
* Description: |
|
* Close and destroy the handle created by ftpd_open. |
|
* |
|
* Input Parameters: |
|
* handle - A handle previously returned by ftpd_open |
|
* |
|
* Returned Value: |
|
* None |
|
* |
|
****************************************************************************/ |
|
|
|
void ftpd_close(FTPD_SESSION handle) |
|
{ |
|
struct ftpd_server_s *server; |
|
DEBUGASSERT(handle); |
|
|
|
server = (struct ftpd_server_s *)handle; |
|
ftpd_account_free(server->head); |
|
|
|
if (server->sd >= 0) |
|
{ |
|
close(server->sd); |
|
server->sd = -1; |
|
} |
|
|
|
free(server); |
|
} |
|
|
|
|