Xiang Xiao 857158451b Unify the void cast usage
1.Remove void cast for function because many place ignore the returned value witout cast
2.Replace void cast for variable with UNUSED macro

Change-Id: Ie644129a563244a6397036789c4c3ea83c4e9b09
Signed-off-by: Xiang Xiao <xiaoxiang@xiaomi.com>
2020-01-02 23:21:01 +08:00

4428 lines
116 KiB
C

/****************************************************************************
* apps/n etutils/ftpd.c
*
* Copyright (C) 2012, 2015 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 <strings.h>
#include <ctype.h>
#include <fcntl.h>
#include <poll.h>
#include <libgen.h>
#include <errno.h>
#include <debug.h>
#include <arpa/inet.h>
#include "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, sa_family_t family);
/* 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)
{
nerr("ERROR: 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)
{
//ninfo("poll() timed out\n");
return -ETIMEDOUT;
}
else if (ret < 0)
{
int errval = errno;
ninfo("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)
{
ninfo("poll() timed out\n");
return -ETIMEDOUT;
}
else if (ret < 0)
{
int errval = errno;
ninfo("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)
{
nerr("ERROR: 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;
nerr("ERROR: 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)
{
ninfo("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;
nerr("ERROR: 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)
{
ninfo("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;
nerr("ERROR: 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);
vasprintf(&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;
nerr("ERROR: socket() failed: %d\n", errval);
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;
nerr("ERROR: connect() failed: %d\n", errval);
ftpd_response(session->cmd.sd, session->txtimeout,
g_respfmt1, 451, ' ', "Connect error !");
ftpd_dataclose(session);
return -errval;
}
#ifdef CONFIG_NET_SOLINGER
{
struct linger ling;
memset(&ling, 0, sizeof(ling));
ling.l_onoff = 1;
ling.l_linger = 4;
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)
{
nerr("ERROR: ftpd_accept() failed: %d\n", sd);
ftpd_response(session->cmd.sd, session->txtimeout,
g_respfmt1, 451, ' ', "Accept error !");
ftpd_dataclose(session);
return sd;
}
close(session->data.sd);
session->data.sd = sd;
#ifdef CONFIG_NET_SOLINGER
{
struct linger ling;
memset(&ling, 0, sizeof(ling));
ling.l_onoff = 1;
ling.l_linger = 4;
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, sa_family_t family)
{
FAR struct ftpd_server_s *server;
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)
{
nerr("ERROR: 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
if (family == AF_INET6)
{
server->addr.in6.sin6_family = family;
server->addr.in6.sin6_addr = in6addr_any;
server->addr.in6.sin6_port = htons(port);
addrlen = (socklen_t)sizeof(server->addr.in6);
addr = (FAR void *)(&server->addr.in6);
server->sd = socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP);
}
else
#endif
#ifdef CONFIG_NET_IPv4
if (family == AF_INET)
{
server->addr.in4.sin_family = family;
server->addr.in4.sin_addr.s_addr = htonl(INADDR_ANY);
server->addr.in4.sin_port = htons(port);
addrlen = (socklen_t)sizeof(server->addr.in4);
addr = (FAR void *)(&server->addr.in4);
server->sd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
}
else
#endif
{
nerr("ERROR: Unsupported family\n");
return NULL;
}
if (server->sd < 0)
{
ftpd_close((FTPD_SESSION)server);
return NULL;
}
#ifdef CONFIG_NET_HAVE_REUSEADDR
{
int reuse = 1;
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: ftpd_nodefree
****************************************************************************/
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;
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)
{
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;
nerr("ERROR: 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 */
}
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;
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)
{
nerr("ERROR: ftpd_offsatoi failed: %d\n", seekpos);
errval = -seekpos;
}
}
else
{
seekpos = session->restartpos;
if (seekpos < 0)
{
nerr("ERROR: 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;
nerr("ERROR: 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)
{
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)
{
nerr("ERROR: 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)
{
nerr("ERROR: Read failed: rdbytes=%d errval=%d\n", rdbytes, errval);
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 */
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;
nerr("ERROR: ftpd_send failed: %d\n", errval);
}
}
else
{
int remaining;
int nwritten;
FAR char *next;
remaining = buflen;
next = buffer;
/* Write to the file */
do
{
nwritten = write(session->fd, next, remaining);
if (nwritten < 0)
{
errval = errno;
nerr("ERROR: write() failed: %d\n", errval);
break;
}
remaining -= nwritten;
next += nwritten;
}
while (remaining > 0);
wrbytes = next - buffer;
}
/* 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))
{
nerr("ERROR: Write failed: wrbytes=%d errval=%d\n", wrbytes, errval);
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)
{
unlink(path);
}
errout_with_data:;
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;
nerr("ERROR: 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;
}
}
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 ret;
}
/****************************************************************************
* 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)
{
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)
{
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)
{
#ifdef CONFIG_NET_IPv4
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)
{
nerr("ERROR: 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");
}
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");
#else
return ftpd_response(session->cmd.sd, session->txtimeout,
g_respfmt1, 502, ' ',
"PORT command not implemented");
#endif
}
/****************************************************************************
* 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 < 1)
{
/* no message ? */
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 < 1 || left > right)
{
/* Invalid format */
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]);
}
}
ftpd_response(session->cmd.sd, session->txtimeout,
g_respfmt1, 502, ' ',
"EPRT command not implemented !");
return -EINVAL;
}
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;
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;
inet_pton(family, field[1], &session->data.addr.in6.sin6_addr);
session->data.addr.in6.sin6_port = htons((short)atoi(field[2]));
}
else
#endif
{
nerr("ERROR: 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)
{
#ifdef CONFIG_NET_IPv4
unsigned int value[6];
unsigned int temp;
int ret;
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)
{
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)
{
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;
}
}
#endif
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)
{
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)
{
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)
{
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)
{
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)
{
ftpd_dataclose(session);
}
return ret;
#else
return ftpd_response(session->cmd.sd, session->txtimeout,
g_respfmt1, 502, ' ',
"PASV command not implemented");
#endif
}
/****************************************************************************
* Name: ftpd_command_epsv
****************************************************************************/
static int ftpd_command_epsv(FAR struct ftpd_session_s *session)
{
int ret;
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)
{
#ifdef CONFIG_NET_IPv4
session->data.sd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
#endif
}
else
{
#if defined(IPPROTO_IPV6) && defined(IPV6_V6ONLY)
int ipv6only = 0;
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 !");
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 !");
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
#endif
#ifdef CONFIG_NET_IPv4
if (session->data.addr.ss.ss_family == AF_INET)
{
session->data.addr.in4.sin_port = htons(0);
}
else
#endif
{
ret = ftpd_response(session->cmd.sd, session->txtimeout,
g_respfmt1, 500, ' ', "EPSV family not supported!");
ftpd_dataclose(session);
return ret;
}
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 !");
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 !");
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 !");
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)
{
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)
{
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)
{
ftpd_dataclose(session);
return ret;
}
opton |= ftpd_listoption((char **)(&session->param));
ftpd_list(session, opton);
ret = ftpd_response(session->cmd.sd, session->txtimeout,
g_respfmt1, 226, ' ', "Transfer complete");
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)
{
ftpd_dataclose(session);
return ret;
}
opton |= ftpd_listoption((char **)(&session->param));
ftpd_list(session, opton);
ret = ftpd_response(session->cmd.sd, session->txtimeout,
g_respfmt1, 226, ' ', "Transfer complete");
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++;
}
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: ftpd_command_rnfr
****************************************************************************/
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)
{
nerr("ERROR: pthread_attr_init() failed: %d\n", ret);
goto errout;
}
/* The set the thread stack size */
ret = pthread_attr_setstacksize(&attr, stacksize);
if (ret != 0)
{
nerr("ERROR: 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)
{
nerr("ERROR: pthread_create() failed: %d\n", ret);
goto errout_with_attr;
}
/* Put the thread in the detached stated */
ret = pthread_detach(threadid);
if (ret != 0)
{
nerr("ERROR: 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);
}
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_SOLINGER
struct linger ling;
#endif
#ifdef CONFIG_NET_HAVE_IPTOS
temp = IPTOS_LOWDELAY;
setsockopt(session->cmd.sd, IPPROTO_IP, IP_TOS, &temp, sizeof(temp));
#endif
#ifdef CONFIG_NET_HAVE_OOBINLINE
temp = 1;
setsockopt(session->cmd.sd, SOL_SOCKET, SO_OOBINLINE, &temp, sizeof(temp));
#endif
#ifdef CONFIG_NET_SOLINGER
memset(&ling, 0, sizeof(ling));
ling.l_onoff = 1;
ling.l_linger = 4;
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;
ninfo("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)
{
nerr("ERROR: 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;
}
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)
{
nerr("ERROR: 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(sa_family_t family)
{
FAR struct ftpd_server_s *server;
server = ftpd_openserver(21, family);
if (!server)
{
server = ftpd_openserver(2211, family);
}
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)
{
nerr("ERROR: Failed to allocte memory to the account\n");
ret = -ENOMEM;
goto errout;
}
ret = ftpd_account_setpassword(newaccount, passwd);
if (ret < 0)
{
nerr("ERROR: ftpd_account_setpassword failed: %d\n", ret);
goto errout_with_account;
}
ret = ftpd_account_sethome(newaccount, home);
if (ret < 0)
{
nerr("ERROR: 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)
{
nerr("ERROR: 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)
{
nerr("ERROR: 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)
{
nerr("ERROR: 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)
{
nerr("ERROR: 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)
{
nerr("ERROR: 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)
{
nerr("ERROR: 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);
}