nuttx-apps/netutils/ftpd/ftpd.c
2022-09-04 11:03:28 -04:00

4518 lines
118 KiB
C

/****************************************************************************
* apps/netutils/ftpd/ftpd.c
*
* Copyright (C) 2012, 2015, 2020 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 <stdint.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 <assert.h>
#include <errno.h>
#include <debug.h>
#include <arpa/inet.h>
#ifdef CONFIG_FTPD_LOGIN_PASSWD
#include "fsutils/passwd.h"
#endif
#include "netutils/ftpd.h"
#include "ftpd.h"
/****************************************************************************
* 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 bool 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, ...)
printflike(3, 4);
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);
/* Command 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
****************************************************************************/
/****************************************************************************
* 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 */
if (user == NULL)
{
usersize = 0;
}
else
{
usersize = strlen(user) + 1;
}
allocsize = sizeof(struct ftpd_account_s) + usersize;
/* Allocate the account and user string */
ret = (struct ftpd_account_s *)zalloc(allocsize);
if (ret == NULL)
{
nerr("ERROR: Failed to allocate account\n");
return NULL;
}
/* Initialize the account and user string */
ret->flags = accountflags;
if (user != NULL)
{
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 != NULL)
{
account = account->blink;
}
/* Then free the entire list */
while (account != NULL)
{
prev = account;
account = account->flink;
/* Free the home path and the password */
if (prev->home != NULL)
{
free(prev->home);
}
if (prev->password != NULL)
{
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 != NULL)
{
temp = strdup(passwd);
if (temp == NULL)
{
return -ENOMEM;
}
}
/* Free any existing password string */
if (account->password != NULL)
{
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 != NULL)
{
head = head->blink;
}
/* Find the tail of the list */
tail = account;
while (tail->flink != NULL)
{
tail = tail->flink;
}
/* Handle the case where the list is empty */
if (server->head == NULL)
{
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 != NULL);
/* Make a copy of the home path string (unless it is NULL) */
temp = NULL;
if (home != NULL)
{
temp = strdup(home);
if (temp == NULL)
{
return -ENOMEM;
}
}
/* Free any existing home path string */
if (account->home != NULL)
{
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 const struct ftpd_account_s *account;
uint8_t accountflags;
account = session->head;
while (account != NULL)
{
accountflags = account->flags;
/* Check if the account has a user */
if (account->user == NULL)
{
/* No.. The account has no user, was a user name provided? */
if (user == NULL)
{
/* No.. create the account */
newaccount = ftpd_account_new(NULL, accountflags);
if (newaccount != NULL)
{
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 != NULL)
{
/* 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 != NULL)
{
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 bool ftpd_account_login(FAR struct ftpd_session_s *session,
FAR const char *user, FAR const char *passwd)
{
FAR char *home = NULL;
uint8_t flags;
#if defined(CONFIG_FTPD_LOGIN_PASSWD)
if (user != NULL && passwd != NULL &&
PASSWORD_VERIFY_MATCH(passwd_verify(user, passwd)))
{
flags = FTPD_ACCOUNTFLAG_ADMIN;
}
else
#endif
{
FAR struct ftpd_account_s *account = NULL;
bool pwvalid;
account = ftpd_account_search_user(session, user);
if (account == NULL)
{
return false;
}
if (account->password == NULL)
{
if (passwd == NULL)
{
pwvalid = true;
}
else if (passwd[0] == '\0')
{
pwvalid = true;
}
else
{
pwvalid = false;
}
}
else if (passwd == NULL)
{
pwvalid = false;
}
else if (strcmp(passwd, (FAR const char *)account->password) == 0)
{
pwvalid = true;
}
else
{
pwvalid = false;
}
if (!pwvalid)
{
ftpd_account_free(account);
return false;
}
if (account->home != NULL)
{
home = strdup(account->home);
}
flags = account->flags;
ftpd_account_free(account);
}
if (home == NULL)
{
home = getenv("HOME");
if (home == NULL)
{
home = strdup("/");
}
else
{
home = strdup(home);
}
}
if ((flags & FTPD_ACCOUNTFLAG_ADMIN) != 0)
{
/* admin user */
session->home = strdup("/");
session->work = home;
}
else
{
/* normal user */
session->home = home;
session->work = strdup("/");
}
return true;
}
/****************************************************************************
* 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 != NULL)
{
if (tokenlen > 0)
{
memcpy(ret, left, tokenlen);
}
ret[tokenlen] = '\0';
}
/* Save the place where we will resuming searching */
*str = sptr;
return ret;
}
/****************************************************************************
* 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)
{
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 == NULL)
{
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 == NULL)
{
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;
}
{
int reuse = 1;
setsockopt(server->sd, SOL_SOCKET, SO_REUSEADDR,
&reuse, sizeof(reuse));
}
/* 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;
}
/****************************************************************************
* 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 == NULL ? 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 != NULL)
{
if (!node->ignore)
{
namelen = node->name == NULL ? 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 != NULL)
{
prev = node;
node = node->flink;
if (prev->name != NULL)
{
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 == NULL)
{
return NULL;
}
while (path[0] != '\0')
{
name = ftpd_strtok_alloc(false, "/\\", &path);
if (name == NULL)
{
break;
}
if (path[0] != '\0')
{
path++;
}
newnode = (FAR struct ftpd_pathnode_s *)
malloc(sizeof(struct ftpd_pathnode_s));
if (newnode == NULL)
{
free(name);
ftpd_nodefree(head);
return NULL;
}
newnode->blink = tail;
newnode->flink = NULL;
newnode->ignore = false;
newnode->name = name;
if (tail == NULL)
{
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 == NULL)
{
return NULL;
}
allocsize = 0;
node1 = node;
while (node1 != NULL)
{
if (strip)
{
if (node1->ignore)
{
node1 = node1->flink;
continue;
}
}
node2 = node1->flink;
while (strip && node2 != NULL)
{
if (!node2->ignore)
{
break;
}
node2 = node2->flink;
}
namelen = node1->name == NULL ? 0 : strlen(node1->name);
if (node2 == NULL)
{
if (namelen <= 0)
{
allocsize += 2;
}
else
{
allocsize += namelen +1;
}
}
else
{
allocsize += namelen + 1;
}
node1 = node1->flink;
}
path = (FAR char *)malloc(allocsize);
if (path == NULL)
{
return NULL;
}
allocsize = 0;
node1 = node;
while (node1 != NULL)
{
if (strip)
{
if (node1->ignore)
{
node1 = node1->flink;
continue;
}
}
node2 = node1->flink;
while (strip && node2 != NULL)
{
if (!node2->ignore)
{
break;
}
node2 = node2->flink;
}
namelen = node1->name == NULL ? 0 : strlen(node1->name);
if (node2 == NULL)
{
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 != NULL && node->name != NULL && strlen(node->name) <= 0)
{
ftpd_nodefree(head);
head = NULL;
}
}
if (head == NULL)
{
if (node != NULL)
{
node->blink = NULL;
}
head = node;
node = NULL;
}
if (node != NULL)
{
temp = head;
while (temp->flink)
{
temp = temp->flink;
}
node->blink = temp;
temp->flink = node;
}
/* clear ignore */
temp = head;
while (temp != NULL)
{
temp->ignore = false;
temp = temp->flink;
}
/* restrip */
temp = head;
while (temp != NULL)
{
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 != NULL)
{
*abspath = NULL;
}
if (workpath != NULL)
{
*workpath = NULL;
}
worknode = ftpd_path2node(session->work == NULL ? "" : session->work);
if (worknode == NULL)
{
return -ENOMEM;
}
appendnode = ftpd_path2node(path);
worknode = ftpd_nodeappend(worknode, appendnode, true);
workpath_local = ftpd_node2path(worknode, 1);
if (workpath_local == NULL)
{
ftpd_nodefree(worknode);
return -ENOMEM;
}
abspath_node = ftpd_path2node(!session->home ? "" : session->home);
if (abspath_node == NULL)
{
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 == NULL)
{
free(workpath_local);
ftpd_nodefree(abspath_node);
ftpd_nodefree(worknode);
return -ENOMEM;
}
if (workpath == NULL)
{
free(workpath_local);
}
else
{
*workpath = workpath_local;
}
if (abspath == NULL)
{
free(abspath_local);
}
else
{
*abspath = abspath_local;
}
ftpd_nodefree(abspath_node);
ftpd_nodefree(worknode);
return OK;
}
/****************************************************************************
* 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, (FAR char **)(&abspath),
(FAR char **)(&workpath));
if (ret < 0)
{
ftpd_response(session->cmd.sd, session->txtimeout,
g_respfmt1, 550, ' ',
"Can not change directory !");
return ret;
}
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 == NULL)
{
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
/* 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: %jd\n", (intmax_t)seekpos);
errval = -seekpos;
}
}
else
{
seekpos = session->restartpos;
if (seekpos < 0)
{
nerr("ERROR: Bad restartpos: %jd\n", (intmax_t)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. */
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 value 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)
{
UNUSED(session);
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,
" %8ju", (uintmax_t)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 != NULL)
{
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 == NULL)
{
int errval = errno;
nerr("ERROR: opendir() failed: %d\n", errval);
return -errval;
}
for (; ; )
{
entry = readdir(dir);
if (entry == NULL)
{
break;
}
if (entry->d_name[0] == '.')
{
if ((opton & FTPD_LISTOPTION_A) == 0)
{
continue;
}
}
asprintf(&temp, "%s/%s", path, entry->d_name);
if (temp == NULL)
{
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;
}
/****************************************************************************
* 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 != NULL)
{
free(session->user);
session->user = NULL;
}
if (session->renamefrom != NULL)
{
free(session->renamefrom);
session->renamefrom = NULL;
}
/* Set up the new user */
session->user = strdup(session->param);
if (session->user == NULL)
{
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 == NULL)
{
FAR char *home;
home = getenv("HOME");
session->loggedin = false;
session->home = strdup(home == NULL ? "/" : home);
session->work = strdup("/");
return ftpd_response(session->cmd.sd, session->txtimeout,
g_respfmt1, 230, ' ', "Login successful.");
}
/* Try to login with no password. This will work if no password is
* required for the account.
*/
session->loggedin = ftpd_account_login(session, session->param, NULL);
if (session->loggedin)
{
ret = ftpd_response(session->cmd.sd, session->txtimeout,
g_respfmt1, 230, ' ', "Login successful.");
if (ret < 0)
{
session->loggedin = false;
}
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 == NULL)
{
return ftpd_response(session->cmd.sd, session->txtimeout,
g_respfmt1, 530, ' ', "Please login with USER !");
}
session->loggedin = ftpd_account_login(session, session->user,
session->param);
if (session->loggedin)
{
ret = ftpd_response(session->cmd.sd, session->txtimeout,
g_respfmt1, 230, ' ', "Login successful.");
if (ret < 0)
{
session->loggedin = false;
}
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] == NULL)
{
break;
}
if (*str != '\0')
{
str++;
}
}
if (index < count)
{
for (index = 0; index < count; index++)
{
if (field[index] != NULL)
{
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] != NULL)
{
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;
workpath = session->work == NULL ? "" : 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)
{
ftpd_response(session->cmd.sd, session->txtimeout,
g_respfmt1, 550, ' ',
"Can not remove directory !");
return ret;
}
if (strcmp(session->home, abspath) == 0)
{
free(abspath);
free(workpath);
ftpd_response(session->cmd.sd, session->txtimeout,
g_respfmt1, 550, ' ',
"Can not remove home directory !");
return -EINVAL;
}
if (strcmp(session->work, workpath) == 0)
{
free(abspath);
free(workpath);
ftpd_response(session->cmd.sd, session->txtimeout,
g_respfmt1, 550, ' ',
"Can not remove current directory !");
return -EINVAL;
}
ret = rmdir(abspath);
if (ret < 0)
{
ret = -errno;
free(abspath);
free(workpath);
ftpd_response(session->cmd.sd, session->txtimeout,
g_respfmt1, 550, ' ',
"Can not remove directory !");
return ret;
}
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)
{
ftpd_response(session->cmd.sd, session->txtimeout,
g_respfmt1, 550, ' ',
"Can not make directory !");
return ret;
}
ret = mkdir(abspath, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP |
S_IXGRP | S_IROTH | S_IXOTH);
if (ret < 0)
{
ret = -errno;
free(abspath);
ftpd_response(session->cmd.sd, session->txtimeout,
g_respfmt1, 550, ' ', "Can not make directory !");
return ret;
}
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)
{
ftpd_response(session->cmd.sd, session->txtimeout,
g_respfmt1, 550, ' ', "Can not delete file !");
return ret;
}
if (strcmp(session->home, abspath) == 0)
{
free(abspath);
free(workpath);
ftpd_response(session->cmd.sd, session->txtimeout,
g_respfmt1, 550, ' ',
"Can not delete home directory !");
return -EINVAL;
}
if (strcmp(session->work, workpath) == 0)
{
free(abspath);
free(workpath);
ftpd_response(session->cmd.sd, session->txtimeout,
g_respfmt1, 550, ' ',
"Can not delete current directory !");
return -EINVAL;
}
ret = unlink(abspath);
if (ret < 0)
{
ret = -errno;
free(abspath);
free(workpath);
ftpd_response(session->cmd.sd, session->txtimeout,
g_respfmt1, 550, ' ', "Can not delete file !");
return ret;
}
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 */
struct 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|).\r\n",
229, ' ',
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)
{
ftpd_response(session->cmd.sd, session->txtimeout,
g_respfmt1, 550, ' ', "Unknown size !");
return ret;
}
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 = -errno;
ftpd_response(session->cmd.sd, session->txtimeout,
g_respfmt2, 550, ' ', session->param,
": not a regular file.");
goto errout_with_abspath;
}
else if (!S_ISREG(st.st_mode))
{
ret = -EPERM;
ftpd_response(session->cmd.sd, session->txtimeout,
g_respfmt2, 550, ' ', session->param,
": not a regular file.");
goto errout_with_abspath;
}
outstream = fopen(path, "r");
if (outstream == NULL)
{
ret = -errno;
ftpd_response(session->cmd.sd, session->txtimeout,
g_respfmt2, 550, ' ', session->param,
": Can not open file !");
goto errout_with_abspath;
}
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;
}
errout_with_abspath:
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;
struct stat st;
int ret;
if (session->renamefrom != NULL)
{
free(session->renamefrom);
session->renamefrom = NULL;
}
ret = ftpd_getpath(session, session->param, &abspath, NULL);
if (ret < 0)
{
ftpd_response(session->cmd.sd, session->txtimeout,
g_respfmt1, 550, ' ', "RNFR error !");
return ret;
}
ret = stat(abspath, &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 == NULL)
{
ftpd_response(session->cmd.sd, session->txtimeout,
g_respfmt1, 550, ' ', "RNTO error !");
return -EINVAL;
}
ret = ftpd_getpath(session, session->param, &abspath, NULL);
if (ret < 0)
{
ftpd_response(session->cmd.sd, session->txtimeout,
g_respfmt1, 550, ' ', "RNTO error !");
return ret;
}
ret = rename(session->renamefrom, abspath);
if (ret < 0)
{
ret = -errno;
free(abspath);
ftpd_response(session->cmd.sd, session->txtimeout,
g_respfmt2, 550, ' ', session->param,
": Rename error.");
return ret;
}
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)
{
ftpd_response(session->cmd.sd, session->txtimeout,
g_respfmt1, 550, ' ', "Unknown size !");
return ret;
}
path = abspath;
ret = stat(path, &st);
if (ret < 0)
{
ret = -errno;
free(abspath);
ftpd_response(session->cmd.sd, session->txtimeout,
g_respfmt2, 550, ' ', session->param,
": not a plain file.");
return ret;
}
if (!S_ISREG(st.st_mode))
{
free(abspath);
ftpd_response(session->cmd.sd, session->txtimeout,
g_respfmt2, 550, ' ', session->param,
": not a plain file.");
return -EISDIR;
}
free(abspath);
gmtime_r(&st.st_mtime, &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 != NULL)
{
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] != NULL)
{
if (index == 0 || g_ftpdhelp[index + 1] == NULL)
{
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 != NULL; 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->loggedin && (session->head != NULL))
{
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 != NULL)
{
/* 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 != NULL)
{
free(session->renamefrom);
}
if (session->work != NULL)
{
free(session->work);
}
if (session->home != NULL)
{
free(session->home);
}
if (session->user != NULL)
{
free(session->user);
}
if (session->fd >= 0)
{
close(session->fd);
}
if (session->data.buffer != NULL)
{
free(session->data.buffer);
}
ftpd_dataclose(session);
if (session->cmd.buffer != NULL)
{
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)
{
UNUSED(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 received 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:
* port - The port that the server will listen to.
* family - The type of INET family to use when opening the socket.
* AF_INET and AF_INET6 are supported.
*
* Returned Value:
* On success, a non-NULL handle is returned that can be used to reference
* the server instance.
*
****************************************************************************/
FTPD_SESSION ftpd_open(int port, sa_family_t family)
{
FAR struct ftpd_server_s *server;
server = ftpd_openserver(port, 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 == NULL)
{
nerr("ERROR: Failed to allocate 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 == NULL)
{
nerr("ERROR: Failed to allocate session\n");
ret = -ENOMEM;
goto errout;
}
/* Initialize the session */
session->server = server;
session->head = server->head;
session->loggedin = false;
session->flags = 0;
session->txtimeout = -1;
session->rxtimeout = -1;
session->cmd.sd = -1;
session->cmd.addrlen = sizeof(session->cmd.addr);
session->cmd.buflen = 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 == NULL)
{
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 == NULL)
{
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;
if (server->head != NULL)
{
ftpd_account_free(server->head);
}
if (server->sd >= 0)
{
close(server->sd);
server->sd = -1;
}
free(server);
}