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