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