summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpatacongo <patacongo@42af7a65-404d-4744-a932-0658087f49c3>2012-02-04 21:02:45 +0000
committerpatacongo <patacongo@42af7a65-404d-4744-a932-0658087f49c3>2012-02-04 21:02:45 +0000
commite82f3f21bff1cf021e036ce3e67d5cc12eb41ebe (patch)
treef564a8e1329d499be76176d46618c76225e04b37
parent07944e1dd7c38a2de22861c695b295156ea8cafd (diff)
downloadnuttx-e82f3f21bff1cf021e036ce3e67d5cc12eb41ebe.tar.gz
nuttx-e82f3f21bff1cf021e036ce3e67d5cc12eb41ebe.tar.bz2
nuttx-e82f3f21bff1cf021e036ce3e67d5cc12eb41ebe.zip
Add the beginnings of an FTP server
git-svn-id: svn://svn.code.sf.net/p/nuttx/code/trunk@4368 42af7a65-404d-4744-a932-0658087f49c3
-rwxr-xr-xapps/ChangeLog.txt4
-rwxr-xr-xapps/examples/ftpd/ftpd_main.c72
-rwxr-xr-xapps/include/netutils/ftpd.h187
-rwxr-xr-xapps/netutils/ftpd/ftpd.c4523
-rwxr-xr-xapps/netutils/ftpd/ftpd.h204
-rw-r--r--nuttx/fs/fs_poll.c4
-rw-r--r--nuttx/include/sys/socket.h4
-rw-r--r--nuttx/lib/net/lib_inetntop.c5
-rw-r--r--nuttx/lib/net/lib_inetpton.c53
9 files changed, 5030 insertions, 26 deletions
diff --git a/apps/ChangeLog.txt b/apps/ChangeLog.txt
index 543fea929..78011ef84 100755
--- a/apps/ChangeLog.txt
+++ b/apps/ChangeLog.txt
@@ -180,4 +180,8 @@
* apps/system/readline: Moved the old nuttx/lib/stdio/lib_fgets.c here
and renamed it as readline(). The old fgets was simplied and the overloaded
readline functionality was removed.
+ * apps/netutils/ftpd: Add an FTPD server (does not even compile on initial
+ checkin).
+ * apps/examples/ftpd: Add a test for the FTPD server (untest on initial
+ check-in).
diff --git a/apps/examples/ftpd/ftpd_main.c b/apps/examples/ftpd/ftpd_main.c
new file mode 100755
index 000000000..b5f81f975
--- /dev/null
+++ b/apps/examples/ftpd/ftpd_main.c
@@ -0,0 +1,72 @@
+#include "ftpd.h"
+
+struct fptd_account_s
+{
+ uint8_t flags;
+ FAR const char *user;
+ FAR const char *password;
+ FAR const char *home;
+}
+
+static const struct fptd_account_s g_ftpdaccounts[] =
+{
+ { FTPD_ACCOUNTFLAG_SYSTEM, "root", "abc123", NULL) },
+ { FTPD_ACCOUNTFLAG_GUEST, "ftp", NULL, NULL },
+ { FTPD_ACCOUNTFLAG_GUEST, "anonymous", NULL, NULL },
+};
+#define NACCOUNTS (sizeof(g_ftpdaccounts) / sizeof(struct fptd_account_s))
+
+static void ftpd_accounts(FTPD_SESSION handle)
+{
+ FAR onst struct fptd_account_s *account;
+ int i;
+
+ for (i = 0; i < NACCOUNTS; i++)
+ {
+ account = &g_ftpdaccounts[i];
+ ftpd_add_user(handle, account->flags, account->user, account->password, account->home);
+ }
+}
+
+int ftpd_main(int s_argc, char **s_argv)
+{
+ FTPD_SESSION handle;
+ int ret;
+
+ /* Bring up the network */
+
+ ret = ftpd_netinit();
+ if (ret < 0)
+ {
+ ndbg("Failed to initialize the network\n");
+ return EXIT_FAILURE;
+ }
+
+ /* Open FTPD */
+
+ handle = ftpd_open();
+ if (!handle)
+ {
+ ndbg("Failed to open FTPD\n");
+ return EXIT_FAILURE;
+ }
+
+ /* Configure acounts */
+
+ (void)ftpd_accounts(handle);
+
+ /* Then drive the FTPD server */
+
+ while (g_ftpd_break == 0)
+ {
+ (void)ftpd_run(handle, 1000);
+ }
+
+ /* Close the FTPD server and exit */
+
+ ftpd_close(handle);
+ return EXIT_SUCCESS;
+}
+
+/* vim: set expandtab: */
+/* End of source */
diff --git a/apps/include/netutils/ftpd.h b/apps/include/netutils/ftpd.h
new file mode 100755
index 000000000..870b5cf94
--- /dev/null
+++ b/apps/include/netutils/ftpd.h
@@ -0,0 +1,187 @@
+/****************************************************************************
+ * apps/include/netutils/ftpd.h
+ *
+ * Copyright (C) 2012 Gregory Nutt. All rights reserved.
+ * Author: Gregory Nutt <spudmonkey@racsa.co.cr>
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+#ifndef __APPS_INCLUDE_NETUTILS_FTPD_H
+#define __APPS_INCLUDE_NETUTILS_FTPD_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <stdint.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+/* Configuration ************************************************************/
+
+#ifdef CONFIG_DISABLE_PTHREAD
+# error "pthread support is required (CONFIG_DISABLE_PTHREAD=n)"
+#endif
+
+#ifndef CONFIG_FTPD_VENDORID
+# define CONFIG_FTPD_VENDORID "NuttX"
+#endif
+
+#ifndef CONFIG_FTPD_SERVERID
+# define CONFIG_FTPD_SERVERID "NuttX FTP Server"
+#endif
+
+#ifndef CONFIG_FTPD_CMDBUFFERSIZE
+# define CONFIG_FTPD_CMDBUFFERSIZE 512
+#endif
+
+#ifndef CONFIG_FTPD_DATABUFFERSIZE
+# define CONFIG_FTPD_DATABUFFERSIZE 2048
+#endif
+
+#ifndef CONFIG_FTPD_WORKERSTACKSIZE
+# define CONFIG_FTPD_WORKERSTACKSIZE 2048
+#endif
+
+/****************************************************************************
+ * Public Types
+ ****************************************************************************/
+
+/* This "handle" describes the FTP session */
+
+typedef FAR void *FTPD_SESSION;
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+#ifdef __cplusplus
+#define EXTERN extern "C"
+extern "C" {
+#else
+#define EXTERN extern
+#endif
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: ftpd_open
+ *
+ * Description:
+ * Create an instance of the FTPD server and return a handle that can be
+ * used to run the server.
+ *
+ * Input Parameters:
+ * None
+ *
+ * Returned Value:
+ * On success, a non-NULL handle is returned that can be used to reference
+ * the server instance.
+ *
+ ****************************************************************************/
+
+EXTERN FTPD_SESSION ftpd_open(void);
+
+/****************************************************************************
+ * 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_*
+ * defintiions.
+ * 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.
+ *
+ ****************************************************************************/
+
+EXTERN int ftpd_adduser(FTPD_SESSION handle, uint8_t accountflags,
+ FAR const char *user, FAR const char *passwd,
+ FAR const char *home);
+
+/****************************************************************************
+ * 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. Each call to ftpd_session creates on 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.
+ *
+ ****************************************************************************/
+
+EXTERN int ftpd_session(FTPD_SESSION handle, int timeout);
+
+/****************************************************************************
+ * 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
+ *
+ ****************************************************************************/
+
+EXTERN void ftpd_close(FTPD_SESSION handle);
+
+#undef EXTERN
+#ifdef __cplusplus
+}
+#endif
+#endif /* __APPS_INCLUDE_NETUTILS_FTPD_H */
diff --git a/apps/netutils/ftpd/ftpd.c b/apps/netutils/ftpd/ftpd.c
new file mode 100755
index 000000000..fa15afeec
--- /dev/null
+++ b/apps/netutils/ftpd/ftpd.c
@@ -0,0 +1,4523 @@
+/****************************************************************************
+ * apps/n etutils/ftpd.c
+ *
+ * Copyright (C) 2012 Gregory Nutt. All rights reserved.
+ * Author: Gregory Nutt <gnutt@nuttx.org>
+ *
+ * Includes original code as well as logic adapted from hwport_ftpd, written
+ * by Jaehyuk Cho <minzkn@minzkn.com> which is released under a BSD license.
+ *
+ * Copyright (C) HWPORT.COM. All rights reserved.
+ * Author: JAEHYUK CHO <mailto:minzkn@minzkn.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * 3. Neither the name NuttX nor the names of its contributors may be
+ * used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <sys/socket.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include <arpa/inet.h>
+
+#include "ftpd.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/****************************************************************************
+ * 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_setpassord(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, FAR struct ftpd_account_s **dupaccount);
+static FAR struct ftpd_account_s *
+ ftpd_account_login(FAR struct ftpd_session_s *session,
+ FAR const char *user, FAR const char *passwd);
+
+/* Parsing functions */
+
+static FAR char *ftpd_strtok(bool skipspace, FAR const char *delimiters,
+ FAR char **str);
+static FAR char *ftpd_strtok_alloc(bool skipspace,
+ FAR const char *delimiters, FAR const char **str);
+static int ftpd_patternmatch(FAR const char *pattern, FAR const char *str);
+
+/* Socket helpers */
+
+static int ftpd_getprotocol(FAR const char *protocol)
+static int ftpd_rxpoll(int sd, int timeout);
+static int ftpd_txpoll(int sd, int timeout);
+static int ftpd_accept(int sd, FAR void *addr, FAR socklen_t *addrlen,
+ int timeout);
+static ssize_t ftpd_recv(int sd, FAR void *data, size_t size, int timeout);
+static ssize_t ftpd_send(int sd, FAR const void *data, size_t size,
+ int timeout);
+static ssize_t ftpd_response(int sd, int timeout, FAR const char *fmt, ...);
+
+static int ftpd_dataopen(FAR struct ftpd_session_s *session);
+static int ftpd_dataclose(FAR struct ftpd_session_s *session);
+static FAR struct ftpd_server_s *ftpd_openserver(int port);
+
+/* Path helpers */
+
+static int ftpd_pathignore(FAR struct ftpd_pathnode_s *currpath);
+static void ftpd_nodefree(FAR struct ftpd_pathnode_s *node);
+static FAR struct ftpd_pathnode_s *ftpd_path2node(FAR const char *path);
+static FAR char *ftpd_node2path(FAR struct ftpd_pathnode_s *node,
+ bool strip);
+static FAR struct ftpd_pathnode_s *
+ ftpd_nodeappend(FAR struct ftpd_pathnode_s *head,
+ FAR struct ftpd_pathnode_s *node, bool override);
+static int ftpd_getpath(FAR struct ftpd_session_s *session,
+ FAR const char *chdirectory, FAR char **abspath,
+ FAR char **workpath);
+
+/* Commmand helpers */
+
+static int ftpd_changedir(FAR struct ftpd_session_s *session,
+ FAR 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 0L /* TODO */
+ {"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_respfmt[] = "%03u%c%s\r\n";
+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
+};
+
+static const struct ftpd_protocol_s g_ftpdprotocols[] =
+{
+#if defined(IPPROTO_TCP)
+ {"tcp", IPPROTO_TCP},
+#endif
+#if defined(IPPROTO_UDP)
+ {"udp", IPPROTO_UDP},
+#endif
+#if defined(IPPROTO_ICMP)
+ {"icmp", IPPROTO_ICMP},
+#endif
+#if defined(IPPROTO_ICMPV6)
+ {"ipv6-icmp", IPPROTO_ICMPV6},
+#endif
+#if defined(IPPROTO_IP)
+ {"ip", IPPROTO_IP},
+#endif
+#if defined(IPPROTO_IPV6)
+ {"ipv6", IPPROTO_IPV6},
+#endif
+ {NULL, (int)0}
+};
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+ /****************************************************************************
+ * Account Functions
+ ****************************************************************************/
+/****************************************************************************
+ * Name: ftpd_account_new
+ ****************************************************************************/
+
+static FAR struct ftpd_account_s *ftpd_account_new(FAR const char *user,
+ uint8_t accountflags)
+{
+ FAR struct ftpd_account_s *ret;
+ size_t usersize;
+ size_t allocsize;
+
+ /* Get the size of the allocation */
+
+ allocsize = sizeof(struct ftpd_account_s);
+ if (user == NULL)
+ {
+ usersize = 0;
+ }
+ else
+ {
+ usersize = strlen(user);
+ allocsize += usersize + 1;
+ }
+
+ /* Allocate the account and user string */
+
+ ret = (struct ftpd_account_s *)zalloc(allocsize);
+ if (ret == NULL)
+ {
+ ndbg("Failed to allocate account\n");
+ return NULL;
+ }
+
+ /* Initialize the account and user string */
+
+ ret->flags = accountflags;
+
+ if (user)
+ {
+ ret->user = (FAR char *)&ret[1];
+ strcpy(ret->user, user);
+ }
+
+ return ret;
+}
+
+/****************************************************************************
+ * Name: ftpd_account_free
+ ****************************************************************************/
+
+static void ftpd_account_free(FAR struct ftpd_account_s *account)
+{
+ struct ftpd_account_s *prev;
+ DEBUGASSERT(account);
+
+ /* Back up to the first entry in the list */
+
+ while (account->blink != 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_setpassord
+ ****************************************************************************/
+
+static int ftpd_account_setpassord(FAR struct ftpd_account_s *account,
+ FAR const char *passwd)
+{
+ FAR char *temp;
+ DEBUGASSERT(account);
+
+ /* Make of copy of the password string (if it is non-null) */
+
+ temp = NULL;
+ if (passwd)
+ {
+ temp = strdup(passwd);
+ if (!temp)
+ {
+ return -ENOMEM;
+ }
+ }
+
+ /* Free any existing password string */
+
+ if (account->password)
+ {
+ free(account->password);
+ }
+
+ /* Set the new password */
+
+ account->password = temp;
+ return OK;
+}
+}
+
+/****************************************************************************
+ * Name: ftpd_account_add
+ ****************************************************************************/
+
+static int ftpd_account_add(FAR struct ftpd_server_s *server,
+ FAR struct ftpd_account_s *account)
+{
+ FAR struct ftpd_account_s *head;
+ FAR struct ftpd_account_s *tail;
+ DEBUGASSERT(server && account);
+
+ /* Find the beginning of the list */
+
+ head = account;
+ while (head->blink != 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->tail == 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);
+
+ /* Make a copy of the home path string (unless it is NULL) */
+
+ temp = NULL;
+ if (home != NULL)
+ {
+ temp = strdup(home);
+ if (!tmp)
+ {
+ return -ENOMEM;
+ }
+ }
+
+ /* Free any existing home path string */
+
+ if (account->home)
+ {
+ free(account->home);
+ }
+
+ /* And set the new home path string */
+
+ account->home = temp;
+ return OK;
+}
+
+/****************************************************************************
+ * Name: ftpd_account_search_user
+ ****************************************************************************/
+
+static FAR struct ftpd_account_s *
+ftpd_account_search_user(FAR struct ftpd_session_s *session,
+ FAR const char *user,
+ FAR struct ftpd_account_s **dupaccount)
+{
+ FAR struct ftpd_account_s *newaccount = NULL;
+ FAR struct ftpd_account_s *account;
+ uint8_t accountflags;
+
+ if (dupaccount)
+ {
+ *dupaccount = NULL;
+ }
+
+ account = session->head;
+ while (account)
+ {
+ accountflags = account->flags;
+
+ if ((accountflags & FTPD_ACCOUNTFLAG_SYSTEM) != 0)
+ {
+ /* If the user name was provided and it matches the account, then
+ * that is good enough.
+ */
+
+ if (user && (account->user || strcmp(account->user, user) == 0) && newacount)
+ {
+ break;
+ }
+ }
+
+ /* Check if the account has a user */
+
+ else if (!account->user)
+ {
+ /* No.. The account has no user, was a user name provided? */
+
+ if (!user)
+ {
+ /* Yes.. create the account */
+
+ newaccount = ftpd_account_new(NULL, accountflags);
+ if (newaccount)
+ {
+ if (ftpd_account_setpassord(newaccount, account->password) < 0)
+ {
+ ftpd_account_free(newaccount);
+ newaccount = NULL;
+ }
+ else if (ftpd_account_sethome(newaccount, account->home) < 0)
+ {
+ ftpd_account_free(newaccount);
+ newaccount = NULL;
+ }
+ }
+ break;
+ }
+ }
+
+ /* Was a user name provided? */
+
+ else if (user)
+ {
+ /* Check if matches the user name on the account */
+
+ if (strcmp(user, (const char *)account->user) == 0)
+ {
+ /* Yes.. create the account */
+
+ newaccount = ftpd_account_new(account->user, accountflags);
+ if (newaccount != NULL
+ {
+ if (ftpd_account_setpassord(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;
+ }
+
+ if (dupaccount)
+ {
+ *dupaccount = newaccount;
+ }
+ else
+ {
+ ftpd_account_free(newaccount);
+ newaccount = NULL;
+ }
+
+ return account;
+}
+
+/****************************************************************************
+ * Name: ftpd_account_login
+ ****************************************************************************/
+
+static FAR struct ftpd_account_s *
+ftpd_account_login(FAR struct ftpd_session_s *session,
+ FAR const char *user, FAR const char *passwd)
+{
+ FAR struct ftpd_account_s *account;
+ FAR struct ftpd_account_s *dupaccount;
+ bool pwvalid;
+ FAR char *home;
+
+ account = ftpd_account_search_user(session, user, &dupaccount);
+ if (!dupaccount)
+ {
+ return(NULL;
+ }
+
+ if (dupaccount->password == NULL)
+ {
+ if (!passwd)
+ {
+ pwvalid = true;
+ }
+ else if (passwd[0] == '\0')
+ {
+ pwvalid = true;
+ }
+ else
+ {
+ pwvalid = false;
+ }
+ }
+ else if (!passwd)
+ {
+ pwvalid = false;
+ }
+ else if (strcmp(passwd, (const char *)dupaccount->password) == 0)
+ {
+ pwvalid = true;
+ }
+ else
+ {
+ pwvalid = false;
+ }
+
+ if (!pwvalid)
+ {
+ ftpd_account_free(dupaccount);
+ return NULL;
+ }
+
+ home = account->home;
+ if (!home)
+ {
+ home = dupaccount->home;
+ }
+
+ if (!home)
+ {
+ home = getenv("HOME");
+ }
+
+ if ((dupaccount->flags & FTPD_ACCOUNTFLAG_ADMIN) != 0)
+ {
+ /* admin user */
+
+ session->home = strdup("/");
+ session->work = strdup((home == NULL) ? "/" : home);
+ }
+ else
+ {
+ /* normal user */
+
+ session->home = strdup((home == NULL) ? "/" : home);
+ session->work = strdup("/");
+ }
+
+ ftpd_account_free(dupaccount);
+ return account;
+}
+
+/****************************************************************************
+ * Parsing Functions
+ ****************************************************************************/
+/****************************************************************************
+ * Name: ftpd_strtok
+ ****************************************************************************/
+
+static FAR char *ftpd_strtok(bool skipspace, FAR const char *delimiters,
+ FAR char **str)
+{
+ FAR const char *dptr;
+ FAR char *sptr;
+ FAR char *ret;
+
+ sptr = *str;
+
+ /* Skip any leading spaces */
+
+ if (skipspace)
+ {
+ while (isspace(*sptr))
+ {
+ sptr++;
+ }
+ }
+
+ ret = sptr;
+
+ /* The following is an implementation of strtok. It does not modify the
+ * original string as strtok does, however.
+ */
+
+ while (*sptr != '\0')
+ {
+ dptr = delimiters;
+ while (*sptr |= *dptr && *dptr != '\0')
+ {
+ dptr++;
+ }
+
+ if (*sptr == *dptr)
+ {
+ break;
+ }
+
+ sptr++;
+ }
+
+ /* Save the place where we will resuming searching */
+
+ *str = sptr;
+ return ret;
+}
+
+/****************************************************************************
+ * Name: ftpd_strtok_alloc
+ ****************************************************************************/
+
+static FAR char *ftpd_strtok_alloc(bool skipspace, FAR const char *delimiters,
+ FAR const char **str)
+{
+ FAR const char *sptr;
+ FAR const char *left;
+ FAR const char *right;
+ FAR const char *dptr;
+ FAR char *ret;
+ size_t tokenlen;
+
+ sptr = *str;
+
+ if (skipspace)
+ {
+ while (isspace(*sptr))
+ {
+ sptr++;
+ }
+ }
+
+ right = sptr;
+ left = sptr;
+
+ /* The the following logic is similar to strtok(), but only bounds the
+ * token of interest between left (the first character of the substring)
+ * and right (the character after the end of the substring).
+ */
+
+ while (*sptr != '\0')
+ {
+ dptr = delimiters;
+ while (*sptr |= *dptr && *dptr != '\0')
+ {
+ dptr++;
+ }
+
+ if (*sptr == *dptr)
+ {
+ break;
+ }
+
+ sptr++;
+
+ /* Adjust the right pointer but ignoring any trailing spaces if
+ * 'skipspace' is selected.
+ */
+
+ if (!skipspace || !isspace(*sptr))
+ {
+ right = sptr;
+ }
+ }
+
+ /* Allocate memory large enough to hold the entire sub-string (including
+ * the NUL terminator.
+ */
+
+ tokenlen = (size_t)(right - left);
+ ret = (FAR char *)malloc(tokenlen + 1);
+ if (ret)
+ {
+ if (tokenlen > 0)
+ {
+ memcpy(ret, left, tokenlen);
+ }
+ ret[tokenlen] = '\0';
+ }
+
+ /* Save the place where we will resuming searching */
+
+ *str = sptr;
+ return ret;
+}
+
+/****************************************************************************
+ * Name: ftpd_strtok_alloc
+ ****************************************************************************/
+
+static int ftpd_patternmatch(FAR const char *pattern, FAR const char *str)
+{
+ size_t patoffset;
+ size_t stroffset;
+ char patbyte;
+ char strbyte;
+
+ patoffset = 0;
+ stroffset = 0;
+
+ for (;;)
+ {
+ patbyte = pattern[patoffset];
+ strbyte = str[stroffset];
+
+ if (patbyte == '/0')
+ {
+ break;
+ }
+
+ if (patbyte == '*')
+ {
+ patoffset++;
+ patbyte = pattern[patoffset];
+ if (patbyte == '\\')
+ {
+ patoffset++;
+ patbyte = pattern[patoffset];
+ }
+
+ while (strbyte != '/0')
+ {
+ if (patbyte == strbyte)
+ {
+ break;
+ }
+
+ stroffset++;
+ strbyte = str[stroffset];
+ }
+
+ if (patbyte == '/0')
+ {
+ break;
+ }
+ }
+ else if (patbyte == '?')
+ {
+ if (strbyte == '/0')
+ {
+ return -1;
+ }
+ }
+ else
+ {
+ if (patbyte == '\\')
+ {
+ patoffset++;
+ patbyte = pattern[patoffset];
+ if (patbyte == '/0')
+ {
+ break;
+ }
+ }
+
+ if (patbyte != strbyte)
+ {
+ return -1;
+ }
+ }
+
+ patoffset++;
+ if (strbyte != '/0')
+ {
+ stroffset++;
+ }
+ }
+
+ return (patbyte == strbyte) ? 0 : -1;
+}
+
+/****************************************************************************
+ * Socket Helpers
+ ****************************************************************************/
+/****************************************************************************
+ * Name: ftpd_rxpoll
+ *
+ * ip IP internet protocol, pseudo protocol number
+ * icmp ICMP internet control message protocol
+ * igmp IGMP Internet Group Management
+ * ggp GGP gateway-gateway protocol
+ * ipencap IP-ENCAP IP encapsulated in IP (officially ``IP'')
+ * st ST ST datagram mode
+ * tcp TCP transmission control protocol
+ * egp EGP exterior gateway protocol
+ * pup PUP PARC universal packet protocol
+ * udp UDP user datagram protocol
+ * hmp HMP host monitoring protocol
+ * xns-idp XNS-IDP Xerox NS IDP
+ * rdp RDP "reliable datagram" protocol
+ * iso-tp4 ISO-TP4 ISO Transport Protocol class 4
+ * xtp XTP Xpress Tranfer Protocol
+ * ddp DDP Datagram Delivery Protocol
+ * idpr-cmtp IDPR-CMTP IDPR Control Message Transport
+ * ipv6 IPv6 IPv6
+ * ipv6-route IPv6-Route Routing Header for IPv6
+ * ipv6-frag IPv6-Frag Fragment Header for IPv6
+ * idrp IDRP Inter-Domain Routing Protocol
+ * rsvp RSVP Reservation Protocol
+ * gre GRE General Routing Encapsulation
+ * esp ESP Encap Security Payload for IPv6
+ * ah AH Authentication Header for IPv6
+ * skip SKIP SKIP
+ * ipv6-icmp IPv6-ICMP ICMP for IPv6
+ * ipv6-nonxt IPv6-NoNxt No Next Header for IPv6
+ * ipv6-opts IPv6-Opts Destination Options for IPv6
+ * rspf RSPF Radio Shortest Path First.
+ * vmtp VMTP Versatile Message Transport
+ * ospf OSPFIGP Open Shortest Path First IGP
+ * ipip IPIP IP-within-IP Encapsulation Protocol
+ * encap ENCAP Yet Another IP encapsulation
+ * pim PIM Protocol Independent Multicast
+ *
+ ****************************************************************************/
+
+static int ftpd_getprotocol(FAR const char *protocol)
+{
+ static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
+ FAR struct protoent *entry;
+ int index;
+ int ret;
+
+ if (!protocol)
+ {
+ return 0;
+ }
+
+ for (index = 0; g_ftpdprotocols[index].name != NULL; index++)
+ {
+ if (strcmp(protocol, g_ftpdprotocols[index].name) == 0)
+ {
+ return g_ftpdprotocols[index].value;
+ }
+ }
+
+ ret = pthread_mutex_lock(&mutex);
+ if (ret != 0)
+ {
+ return 0;
+ }
+
+ entry = getprotobyname(protocol);
+ ret = (entry != NULL) ? entry->p_proto : 0);
+
+ (void)pthread_mutex_unlock(&mutex);
+ 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)
+ {
+ nvdbg("poll() timed out\n");
+ return -ETIMEDOUT;
+ }
+ else if (ret < 0)
+ {
+ int errval = errno;
+ nvdbg("poll() failed: %d\n", errval);
+ return -errval;
+ }
+ else
+ {
+ return OK;
+ }
+}
+
+/****************************************************************************
+ * Name: ftpd_txpoll
+ ****************************************************************************/
+
+static int ftpd_txpoll(int sd, int timeout)
+{
+ struct pollfd fds[1];
+ int ret;
+
+ /* Set up the poll */
+
+ fds[0].fd = sd;
+ fds[0].events = POLLOUT;
+ fds[0].revents = 0;
+
+ /* Perform the poll. */
+
+ ret = poll(fds, 1, timeout);
+
+ /* Handle the result of the poll. On success, poll returns the number
+ * of structures that have nonzero revents fields. A value of 0 indicates
+ * that the call timed out and no file descriptors were ready. On error,
+ * -1 is returned, and errno is set appropriately:
+ */
+
+ if (ret == 0)
+ {
+ nvdbg("poll() timed out\n");
+ return -ETIMEDOUT;
+ }
+ else if (ret < 0)
+ {
+ int errval = errno;
+ nvdbg("poll() failed: %d\n", errval);
+ return -errval;
+ }
+ else
+ {
+ return OK;
+ }
+}
+
+/****************************************************************************
+ * Name: ftpd_accept
+ ****************************************************************************/
+
+static int ftpd_accept(int sd, FAR void *addr, FAR socklen_t *addrlen,
+ int timeout)
+{
+ int sd;
+ int ret;
+
+ /* Handle any requested timeout */
+
+ if (timeout >= 0)
+ {
+ ret = ftpd_rxpoll(sd, timeout);
+ if (ret < 0)
+ {
+ nvdbg("ftpd_rxpoll() failed: %d\n", ret);
+ return ret;
+ }
+ }
+
+ /* Accept the connection -- waiting if necessary */
+
+ sd = accept(sd, (FAR struct sockaddr *)addr, addrlen);
+ if (sd < 0)
+ {
+ int errval = errno;
+ ndbg("accept() failed: %d\n", errval);
+ return -errval;
+ }
+
+ return sd;
+}
+
+/****************************************************************************
+ * Name: ftpd_recv
+ ****************************************************************************/
+
+static ssize_t ftpd_recv(int sd, FAR void *data, size_t size, int timeout)
+{
+ ssize_t ret;
+ int status;
+
+ /* Handle any requested timetout */
+
+ if (timeout >= 0)
+ {
+ status = ftpd_rxpoll(sd, timeout);
+ if (status < 0)
+ {
+ nvdbg("ftpd_rxpoll: %d\n", status);
+ return (ssize_t)status;
+ }
+ }
+
+ /* Receive the data... waiting if necessary */
+
+ ret = recv(sd, data, size, 0);
+ if (ret < 0)
+ {
+ ssize_t errval = errno;
+ ndbg("recv() failed: %d\n", errval);
+ return -errval;
+ }
+
+ return ret;
+}
+
+/****************************************************************************
+ * Name: ftpd_send
+ ****************************************************************************/
+
+static ssize_t ftpd_send(int sd, FAR const void *data, size_t size, int timeout)
+{
+ ssize_t ret;
+
+ /* Handle any requested timetout */
+
+ if (timeout >= 0)
+ {
+ status = ftpd_txpoll(sd, timeout);
+ if (status < 0)
+ {
+ nvdbg("ftpd_rxpoll: %d\n", status);
+ return (ssize_t)status;
+ }
+ }
+
+ /* Then send the data (waiting if necessary) */
+
+ ret = send(sd, data, size, 0);
+ if (ret < 0)
+ {
+ ssize_t errval = errno;
+ ndbg("send() failed: %d\n", errval);
+ return -errval;
+ }
+
+ return ret;
+}
+
+/****************************************************************************
+ * Name: ftpd_response
+ ****************************************************************************/
+
+static ssize_t ftpd_response(int sd, int timeout, FAR const char *fmt, ...)
+{
+ FAR void *buffer;
+ ssize_t bytessent;
+ va_list ap;
+
+ va_start(ap, fmt);
+ avsprintf(&buffer, fmt, ap);
+ va_end(ap);
+
+ if (buffer == NULL)
+ {
+ return -ENOMEM;
+ }
+
+ bytessent = ftpd_send(sd, buffer, strlen(buffer), timeout);
+ free(buffer);
+
+ return bytessent;
+}
+
+/****************************************************************************
+ * Name: ftpd_dataopen
+ ****************************************************************************/
+
+static int ftpd_dataopen(FAR struct ftpd_session_s *session)
+{
+ int sd;
+ int ret;
+
+ if (session->data.sd < 0)
+ {
+ /* PORT session */
+
+#ifdef CONFIG_NET_IPv6
+ if (session->data.addr.ss.ss_family == AF_INET6)
+ {
+ session->data.sd = socket(PF_INET6, SOCK_STREAM, ftpd_getprotocol("tcp"));
+ }
+ else
+ {
+ session->data.sd = socket(PF_INET, SOCK_STREAM, ftpd_getprotocol("tcp"));
+ }
+#else
+ session->data.sd = socket(PF_INET, SOCK_STREAM, ftpd_getprotocol("tcp"));
+#endif
+
+ if (session->data.sd < 0)
+ {
+ int errval = errno;
+ ndbg("socket() failed: %d\n", errval);
+ (void)ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 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, -1);
+ if (ret < 0)
+ {
+ int errval = errno;
+ ndbg("connect() failed: %d\n", errval);
+ (void)ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 451, ' ', "Connect error !");
+ (void)ftpd_dataclose(session);
+ return -errval;
+ }
+
+#ifdef CONFIG_NET_HAVE_SOLINGER
+ {
+ struct linger ling;
+
+ (void)memset(&ling, 0, sizeof(ling));
+ ling.l_onoff = 1;
+ ling.l_linger = 4;
+ (void)setsockopt(session->data.sd, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling));
+ }
+#endif
+
+ return OK;
+ }
+
+ /* PASV session */
+
+ session->data.addrlen = sizeof(session->data.addr);
+ sd = ftpd_accept(session->data.sd, (struct sockaddr *)(&session->data.addr),
+ &session->data.addrlen, -1);
+ if (sd < 0)
+ {
+ ndbg("ftpd_accept() failed: %d\n", sd);
+ (void)ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 451, ' ', "Accept error !");
+ (void)ftpd_dataclose(session);
+ return sd;
+ }
+
+ close(session->data.sd);
+ session->data.sd = sd;
+
+#ifdef CONFIG_NET_HAVE_SOLINGER
+ {
+ struct linger ling;
+
+ (void)memset(&ling, 0, sizeof(ling));
+ ling.l_onoff = 1;
+ ling.l_linger = 4;
+ (void)setsockopt(session->data.sd, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling));
+ }
+#endif
+
+ return OK;
+}
+
+/****************************************************************************
+ * Name: ftpd_dataclose
+ ****************************************************************************/
+
+static int ftpd_dataclose(FAR struct ftpd_session_s *session)
+{
+ if (session->data.sd >= 0)
+ {
+ close(session->data.sd);
+ session->data.sd = -1;
+ }
+
+ return OK;
+}
+
+/****************************************************************************
+ * Name: ftpd_openserver
+ ****************************************************************************/
+
+static FAR struct ftpd_server_s *ftpd_openserver(int port)
+{
+ FAR struct ftpd_server_s *server;
+ sa_family_t family;
+ socklen_t addrlen;
+ FAR const void *addr;
+#if defined(SOMAXCONN)
+ int backlog = SOMAXCONN;
+#else
+ int backlog = 5;
+#endif
+ int ret;
+
+ /* Allocate the server instance */
+
+ server = (FAR struct ftpd_server_s *)zalloc(sizeof(struct ftpd_server_s));
+ if (!server)
+ {
+ ndbg("Failed to allocate server\n");
+ return -ENOMEM;
+ }
+
+ /* Initialize the server instance */
+
+ server->sd = -1;
+ server->head = NULL;
+ server->tail = NULL;
+
+ /* Create the server listen socket */
+
+#ifdef CONFIG_NET_IPv6
+ server->sd = socket(PF_INET6, SOCK_STREAM, ftpd_getprotocol("tcp"));
+ if (server->sd < 0)
+ {
+ ftpd_close((FTPD_SESSION)server);
+ return NULL;
+ }
+
+ family = AF_INET6;
+ addrlen = (socklen_t)sizeof(server->addr.in6);
+ addr = (FAR void *)(&server->addr.in6);
+
+ server->addr.in6.sin6_family = family;
+ server->addr.in6.sin6_flowinfo = 0;
+ server->addr.in6.sin6_addr = in6addr_any;
+ server->addr.in6.sin6_port = htons(port);
+#else
+ server->sd = socket(PF_INET, SOCK_STREAM, ftpd_getprotocol("tcp"));
+ if (server->sd < 0)
+ {
+ ftpd_close((FTPD_SESSION)server);
+ return NULL;
+ }
+
+ family = AF_INET;
+ addrlen = (socklen_t)sizeof(server->addr.in4);
+ addr = (FAR void *)(&server->addr.in4);
+
+ server->addr.in4.sin_family = family;
+ server->addr.in4.sin_addr.s_addr = htonl(INADDR_ANY);
+ server->addr.in4.sin_port = htons(port);
+#endif
+
+#ifdef CONFIG_NET_HAVE_REUSEADDR
+ {
+ int reuse = 1;
+ (void)setsockopt(server->sd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
+ }
+#endif
+
+ /* Bind the socket to the address */
+
+ ret = bind(server->sd, (FAR const struct sockaddr *)addr, addrlen)
+ if (ret < 0)
+ {
+ ftpd_close((FTPD_SESSION)server);
+ return NULL;
+ }
+
+ /* Listen on the socket */
+
+ ret = listen(server->sd, backlog)
+ if (ret < 0)
+ {
+ ftpd_close((FTPD_SESSION)server);
+ return NULL;
+ }
+
+ return (FTPD_SESSION)server;
+}
+
+/****************************************************************************
+ * Path Helpers
+ ****************************************************************************/
+/****************************************************************************
+ * Name: ftpd_pathignore
+ ****************************************************************************/
+
+static int ftpd_pathignore(FAR struct ftpd_pathnode_s *currpath)
+{
+ FAR struct ftpd_pathnode_s *node;
+ size_t namelen;
+
+ namelen = (currpath->name == NULL) ? 0 : strlen(currpath->name);
+
+ if (namelen == 0)
+ {
+ if (currpath->blink != NULL)
+ {
+ currpath->ignore = true;
+ }
+
+ return OK;
+ }
+
+ if (strcmp(currpath->name, "..") == 0)
+ {
+ currpath->ignore = true;
+
+ node = currpath->blink;
+ while (node)
+ {
+ 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:
+ ****************************************************************************/
+
+static void ftpd_nodefree(FAR struct ftpd_pathnode_s *node)
+{
+ FAR struct ftpd_pathnode_s *prev;
+
+ while (node)
+ {
+ prev = node;
+ node = node->flink;
+
+ if (prev->name != NULL)
+ {
+ free(prev->name);
+ }
+ free(prev);
+ }
+}
+
+/****************************************************************************
+ * Name: ftpd_path2node
+ ****************************************************************************/
+
+static FAR struct ftpd_pathnode_s *ftpd_path2node(FAR const char *path)
+{
+struct ftpd_pathnode_s *ftpd_path2node(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;
+ int ret;
+
+ if (!path)
+ {
+ return NULL;
+ }
+
+ while (path[0] != '\0')
+ {
+ name = ftpd_strtok_alloc(false, "/\\", &path);
+ if (!name)
+ {
+ break;
+ }
+
+ if (path[0] != '\0')
+ {
+ path++;
+ }
+
+ newnode = (FAR struct ftpd_pathnode_s *)malloc(sizeof(struct ftpd_pathnode_s));
+ if (!newnode)
+ {
+ free(name);
+ ftpd_nodefree(head);
+ return NULL;
+ }
+
+ newnode->blink = tail;
+ newnode->flink = NULL;
+ newnode->ignore = false;
+ newnode->name = name;
+
+ if (!tail)
+ {
+ head = newnode;
+ }
+ else
+ {
+ tail->flink = newnode;
+ }
+
+ tail = newnode;
+
+ (void)ftpd_pathignore(newnode);
+ }
+
+ return head;
+}
+
+/****************************************************************************
+ * Name: ftpd_node2path
+ ****************************************************************************/
+
+static FAR char *ftpd_node2path(FAR struct ftpd_pathnode_s *node,
+ bool strip)
+{
+ FAR struct ftpd_pathnode_s *node1;
+ FAR struct ftpd_pathnode_s *node2;
+ FAR char *path;
+ FAR size_t allocsize;
+ FAR size_t namelen;
+ int ret;
+
+ if (!node)
+ {
+ 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)
+ {
+ return NULL;
+ }
+
+ allocsize = 0;
+ node1 = node;
+ while (node1 != NULL)
+ {
+ if (strip != 0)
+ {
+ if (node1->ignore)
+ {
+ node1 = node1->flink;
+ continue;
+ }
+ }
+
+ node2 = node1->flink;
+ while (strip && node2 != NULL)
+ {
+ if (!node2->ignore)
+ {
+ break;
+ }
+
+ node2 = node2->flink;
+ }
+
+ namelen = (node1->name == NULL) ? 0 : strlen(node1->name);
+
+ if (node2 == NULL)
+ {
+ if (namelen <= 0)
+ {
+ allocsize += sprintf(&path[allocsize], "/");
+ }
+ else
+ {
+ allocsize += sprintf(&path[allocsize], "%s", node1->name);
+ }
+ }
+ else
+ {
+ allocsize += sprintf(&path[allocsize], "%s%s", node1->name, "/");
+ }
+
+ node1 = node1->flink;
+ }
+
+ return path;
+}
+
+/****************************************************************************
+ * Name: ftpd_nodeappend
+ ****************************************************************************/
+
+static FAR struct ftpd_pathnode_s *
+ftpd_nodeappend(FAR struct ftpd_pathnode_s *head,
+ FAR struct ftpd_pathnode_s *node, bool override)
+{
+ FAR struct ftpd_pathnode_s *temp;
+
+ if (override)
+ {
+ if (node && node->name && strlen(node->name) <= 0)
+ {
+ ftpd_nodefree(head);
+ head = NULL;
+ }
+ }
+
+ if (!head)
+ {
+ if (node)
+ {
+ node->blink = NULL;
+ }
+
+ head = node;
+ node = NULL;
+ }
+
+ if (node)
+ {
+ temp = head;
+ while (temp->flink)
+ {
+ temp = temp->flink;
+ }
+
+ node->blink = temp;
+ temp->flink = node;
+ }
+
+ /* clear ignore */
+
+ temp = head;
+ while (temp)
+ {
+ temp->ignore = false;
+ temp = temp->flink;
+ }
+
+ /* restrip */
+
+ temp = head;
+ while (temp)
+ {
+ (void)ftpd_pathignore(temp);
+ temp = temp->flink;
+ }
+
+ return head;
+}
+
+/****************************************************************************
+ * Name:
+ ****************************************************************************/
+
+static int ftpd_getpath(FAR struct ftpd_session_s *session, FAR const char *chdirectory, 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;
+ int ret;
+
+ if (abspath)
+ {
+ *abspath = NULL;
+ }
+
+ if (workpath)
+ {
+ *workpath = NULL;
+ }
+
+ worknode = ftpd_path2node((session->work == NULL) ? "" : session->work);
+ if (!worknode)
+ {
+ return -ENOMEM;
+ }
+
+ appendnode = ftpd_path2node(chdirectory);
+ worknode = ftpd_nodeappend(worknode, appendnode, true);
+ workpath_local = ftpd_node2path(worknode, 1);
+
+ if (!workpath_local)
+ {
+ ftpd_nodefree(worknode);
+ return -ENOMEM;
+ }
+
+ abspath_node = ftpd_path2node((session->home == NULL) ? "" : session->home);
+ if (!abspath_node)
+ {
+ free(workpath_local);
+ ftpd_nodefree(worknode);
+ return -ENOMEM;
+ }
+
+ appendnode = ftpd_path2node(workpath_local);
+ abspath_node = ftpd_nodeappend(abspath_node, appendnode, false);
+ abspath_local = ftpd_node2path(abspath_node, 1);
+
+ if (!abspath_local)
+ {
+ free(workpath_local);
+ ftpd_nodefree(abspath_node);
+ ftpd_nodefree(worknode);
+ return -ENOMEM;
+ }
+
+ if (!workpath)
+ {
+ free(workpath_local);
+ }
+ else
+ {
+ *workpath = workpath_local;
+ }
+
+ if (!abspath)
+ {
+ free(abspath_local);
+ }
+ else
+ {
+ *abspath = abspath_local;
+ }
+
+ ftpd_nodefree(abspath_node);
+ ftpd_nodefree(worknode);
+ return OK;
+}
+}
+
+/****************************************************************************
+ * Command Helpers
+ ****************************************************************************/
+/****************************************************************************
+ * Name: ftpd_changedir
+ ****************************************************************************/
+
+static int ftpd_changedir(FAR struct ftpd_session_s *session,
+ FAR char *rempath)
+{
+ FAR char *abspath;
+ FAR char *workpath;
+ struct stat st;
+ int ret;
+
+ ret = ftpd_getpath(session, rempath, (char **)(&abspath), (char **)(&workpath));
+ if (ret < 0)
+ {
+ return ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 550, ' ',
+ "Can not change directory !");
+ }
+
+ ret = stat(abspath, &st);
+ if (ret < 0)
+ {
+ free(workpath);
+ free(abspath);
+ return ftpd_response(session->cmd.sd, session->txtimeout,
+ "%03u%c%s%s\r\n", 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,
+ "%03u%c%s%s\r\n", 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_respfmt, 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;
+ ndbg("Failed to open %s: %d\n", filename, errval);
+ return -errval;
+ }
+
+ ret = 0;
+ temp = 0;
+
+ if (offset == (off_t)(-1))
+ {
+ for (;;)
+ {
+ ch = getc(outstream);
+ if (ch == EOF)
+ {
+ break;
+ }
+ ret++;
+ if (ch == '\n')
+ {
+ ret++;
+ }
+ }
+ /* ret is ascii mode size */
+ }
+ else
+ {
+ while (offset < temp)
+ {
+ ch = getc(outstream);
+ if (ch == EOF)
+ {
+ ret = -errno;
+ break;
+ }
+
+ ret++;
+ temp++;
+
+ if (ch == '\n')
+ {
+ temp++;
+ }
+ }
+
+ /* ret is binary mode offset */
+ }
+
+ (void)fclose(outstream);
+ return ret;
+}
+
+/****************************************************************************
+ * Name: ftpd_stream
+ ****************************************************************************/
+
+static int ftpd_stream(FAR struct ftpd_session_s *session, int cmdtype)
+{
+ FAR char *abspath;
+ FAR char *path;
+ bool isnew;
+ int oflags;
+ uint8_t *buffer;
+ size_t buflen;
+ size_t wantsize;
+ ssize_t rdbytes;
+ ssize_t wrbytes;
+ off_t pos = 0;
+ int ret;
+
+ ret = ftpd_getpath(session, session->param, &abspath, NULL);
+ if (ret < 0)
+ {
+ ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 550, ' ', "Stream error !");
+ goto errout;
+ }
+ path = abspath;
+
+ ret = ftpd_dataopen(session);
+ if (ret < 0)
+ {
+ goto errout_with_path;
+ }
+
+ switch (cmdtype)
+ {
+ case 0: /* retr */
+ oflags = O_RDONLY;
+ break;
+
+ case 1: /* stor */
+ oflags = O_CREAT | O_WRONLY;
+ break;
+
+ case 2: /* appe */
+ oflags = O_CREAT | O_WRONLY | O_APPEND;
+ break;
+
+ default:
+ oflags = O_RDONLY;
+ break;
+ }
+
+#if defined(O_LARGEFILE)
+ oflags |= O_LARGEFILE;
+#endif
+#if defined(O_BINARY)
+ oflags |= O_BINARY;
+#endif
+ 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
+ {
+ isnew = false;
+ session->fd = open(path, oflags);
+ }
+
+ if (session->fd < 0)
+ {
+ ret = -errno;
+ (void)ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 550, ' ', "Can not open file !");
+ goto errout_with_data;
+ }
+
+ /* Restart position */
+
+ if (session->restartpos > 0)
+ {
+ off_t seekoffs;
+ off_t seekpos;
+
+ /* Get the seek position */
+
+ if (session->type == FTPD_SESSIONTYPE_A)
+ {
+ seekpos = ftpd_offsatoi(path, session->restartpos);
+ if (seekpos < 0)
+ {
+ ndbg("ftpd_offsatoi failed: %d\n", seekpos);
+ seekoffs = (off_t)-1;
+ ret = seekpos;
+ }
+ }
+ else
+ {
+ seekpos = session->restartpos;
+ if (seekpos < 0)
+ {
+ ndbg("Bad restartpos: %d\n", seekpos);
+ seekoffs = (off_t)-1;
+ ret = -EINVAL;
+ }
+ }
+
+ /* Seek to the request position */
+
+ if (seekpos >= 0)
+ {
+ seekoffs = lseek(session->fd, seekpos, SEEK_SET);
+ if (seekoffs < 0)
+ {
+ int errval = errno;
+ ndbg("lseek failed: %d\n", errval);
+ ret = -errno;
+ }
+ }
+
+ /* Report errors */
+
+ if (seekoffs < 0)
+ {
+ (void)ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 550, ' ', "Can not seek file !");
+ goto errout_with_session;
+ }
+
+ pos += (off_t)seekoffs;
+ }
+
+ /* Send success message */
+
+ ret = ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 150, ' ', "Opening data connection");
+ if (ret < 0)
+ {
+ ndbg("ftpd_response failed: %d\n", ret);
+ goto errout_with_session;
+ }
+
+ for (;;)
+ {
+ 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)
+ {
+ rdbytes = (ssize_t)read(session->fd, session->data.buffer,
+ wantsize);
+ }
+ else
+ {
+ rdbytes = ftpd_recv(session->data.sd, session->data.buffer,
+ wantsize, session->rxtimeout);
+ }
+
+ if (rdbytes < 0)
+ {
+ ndbg("ftp_recv failed: %d\n", rdbytes);
+ (void)ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 550, ' ', "Data read error !");
+ ret = redbytes;
+ break;
+ }
+
+ if (rdbytes == 0)
+ {
+ /* EOF */
+
+ ret = -ECONNRESET;
+ (void)ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 226, ' ', "Transfer complete");
+ break;
+ }
+
+ 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)
+ {
+ wrbytes = ftpd_send(session->data.sd, buffer, buflen, session->txtimeout);
+ if (wrbytes < 0)
+ {
+ ndbg("ftpd_send failed: %d\n", wrbytes);
+ ret = wrbytes;
+ }
+ }
+ else
+ {
+ wrbytes = (ssize_t)write(session->fd, (const void *)buffer, buflen);
+ if (wrbytes < 0)
+ {
+ int errval = errno;
+ ndbg("ftpd_send failed: %d\n", errval);
+ ret = -errval;
+ }
+ }
+
+ if (wrbytes != ((ssize_t)buflen))
+ {
+ (void)ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 550, ' ', "Data send error !");
+ break;
+ }
+
+ pos += (off_t)wrbytes;
+ }
+
+errout_with_session:;
+ close(session->fd);
+ session->fd = -1;
+
+ if (isnew && ret < 0)
+ {
+ (void)remove(path);
+ }
+
+errout_with_data:;
+ (void)ftpd_dataclose(session);
+
+errout_with_path:
+ free(abspath);
+
+errout:
+ return ret;
+}
+
+/****************************************************************************
+ * Name: ftpd_listoption
+ ****************************************************************************/
+
+static uint8_t ftpd_listoption(FAR char **param)
+{
+ FAR char *ptr = *param;
+ uint8_t ret = 0;
+
+ while (*ptr == '-')
+ {
+ while (*ptr != '\0' && !isspace(*ptr))
+ {
+ switch (*ptr)
+ {
+ case 'a':
+ case 'A':
+ ret |= FTPD_LISTOPTION_A;
+ break;
+
+ case 'l':
+ case 'L':
+ ret |= FTPD_LISTOPTION_L;
+ break;
+
+ case 'f':
+ case 'F':
+ ret |= FTPD_LISTOPTION_F;
+ break;
+
+ case 'r':
+ case 'R':
+ ret |= FTPD_LISTOPTION_R;
+ break;
+
+ default:
+ ret |= FTPD_LISTOPTION_UNKNOWN;
+ break;
+ }
+
+ ptr++;
+ }
+
+ if (*ptr != '\0')
+ {
+ while (*ptr != '\0' && isspace(*ptr))
+ {
+ ptr++;
+ }
+ }
+ }
+
+ *param = ptr;
+ return ret;
+}
+
+/****************************************************************************
+ * Name: fptd_listscan
+ ****************************************************************************/
+
+static int ftpd_listbuffer(FAR struct ftpd_session_s *session, FAR char *path,
+ FAR struct stat *st, FAR char *buffer, size_t buflen,
+ unsigned int opton)
+{
+ FAR char *name;
+ size_t offset = 0;
+
+ name = basename(path);
+
+ if ((opton & FTPD_LISTOPTION_L) != 0)
+ {
+ FAR const char *str;
+ struct tm tm;
+ time_t now;
+
+ if (S_ISREG(st->st_mode) != 0)
+ {
+ str = "-";
+ }
+ else if (S_ISDIR(st->st_mode) != 0)
+ {
+ str = "d";
+ }
+ else if (S_ISCHR(st->st_mode) != 0)
+ {
+ str = "c";
+ }
+ else if (S_ISBLK(st->st_mode) != 0)
+ {
+ str = "b";
+ }
+ else if (S_ISFIFO(st->st_mode) != 0)
+ {
+ str = "p";
+ }
+ else if (S_ISLNK(st->st_mode) != 0)
+ {
+ str = "l";
+ }
+ else if (S_ISSOCK(st->st_mode) != 0)
+ {
+ str = "s";
+ }
+ else
+ {
+ str = "-";
+ }
+
+ offset += snprint(&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);
+
+ /* nlink */
+
+ offset += snprintf(&buffer[offset], buflen - offset, "%4u", st->st_nlink);
+
+ /* username */
+
+ offset += snprintf(&buffer[offset], buflen - offset, " %8u", st->st_uid);
+
+ /* groupname */
+
+ offset += snprintf(&buffer[offset], buflen - offset, " %8u", st->st_gid);
+
+ /* size */
+
+ offset += snprintf(&buffer[offset], buflen - offset, " %8u", st->st_size);
+
+ /* time */
+
+ memcpy(&tm, localtime((GST 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 */
+
+ 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);
+ }
+ }
+
+ /* 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", (char *)session->data.buffer);
+ }
+
+ return ret;
+ }
+
+ dir = opendir(path);
+ if (!dir)
+ {
+ int errval = errno;
+ ndbg("dir() failed\n", errval);
+ return -errval;
+ }
+
+ for (;;)
+ {
+ entry = readdir(dir);
+ if (!entry)
+ {
+ break;
+ }
+
+ if (entry->d_name[0] == '.')
+ {
+ if ((opton & FTPD_LISTOPTION_A) == 0)
+ {
+ continue;
+ }
+ }
+
+ asprintf(temp, "%s/%s", path, entry->d_name);
+ if (!temp)
+ {
+ continue;
+ }
+
+ ret = stat(temp, &st);
+ if (ret < 0)
+ {
+ free(temp);
+ continue;
+ }
+
+ ret = ftpd_listbuffer(session, temp, &st, session->data.buffer,
+ session->data.buflen, opton);
+ if (ret >= 0)
+ {
+ ret = ftpd_response(session->data.sd, session->txtimeout,
+ "%s", session->data.buffer);
+ }
+
+ free(temp);
+ if (ret < 0)
+ {
+ break;
+ }
+ }
+
+ (void)closedir(dir);
+ return ret;
+}
+
+/****************************************************************************
+ * Name: ftpd_list
+ ****************************************************************************/
+
+static int ftpd_list(FAR struct ftpd_session_s *session, unsigned int opton)
+{
+ FAR char *abspath;
+ int ret;
+
+ ret = ftpd_getpath(session, session->param, &abspath, NULL);
+ if (ret >= 0)
+ {
+ ret = fptd_listscan(session, abspath, opton);
+ free(abspath);
+ }
+
+ return OK;
+}
+
+/****************************************************************************
+ * Command Handlers
+ ****************************************************************************/
+/****************************************************************************
+ * Name: ftpd_command_user
+ ****************************************************************************/
+
+static int ftpd_command_user(FAR struct ftpd_session_s *session)
+{
+ int ret;
+
+ if (session->user)
+ {
+ free(session->user);
+ session->user = NULL;
+ }
+
+ /* No account info */
+
+ if (!session->head)
+ {
+ FAR char *home;
+
+ home = getenv("HOME");
+ session->curr = NULL;
+ session->home = strdup((home == NULL) ? "/" : home);
+ session->work = strdup("/");
+
+ ret = ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 230, ' ', "Login successful.");
+ if (ret < 0)
+ {
+ session->curr = NULL;
+ }
+ return ret;
+ }
+
+ /* For no password user */
+
+ session->curr = ftpd_account_login(session, session->param, NULL);
+ if (session->curr)
+ {
+ ret = ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 230, ' ', "Login successful.");
+ if (ret < 0)
+ {
+ session->curr = NULL;
+ }
+ return ret;
+ }
+
+ /* Set up the user */
+
+ session->user = strdup(session->param);
+ if (!session->user)
+ {
+ return ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 451, ' ', "Memory exhausted !");
+ }
+ session->flags |= FTPD_SESSIONFLAG_USER;
+
+ return ftpd_response(session->cmd.sd, session->txtimeout,
+ "%03u%c%s%s\r\n", 331, ' ', "Password required for ",
+ session->user);
+}
+
+/****************************************************************************
+ * Name: ftpd_command_pass
+ ****************************************************************************/
+
+static int ftpd_command_pass(FAR struct ftpd_session_s *session)
+{
+ int ret;
+
+ if (!session->user)
+ {
+ return ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 530, ' ', "Please login with USER !");
+ }
+
+ session->curr = ftpd_account_login(session, session->user, session->param);
+ if (!session->curr != NULL
+ {
+ ret = ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 230, ' ', "Login successful.");
+ if (ret < 0)
+ {
+ session->curr = NULL;
+ }
+ return ret;
+ }
+
+ return ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 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_respfmt, 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_respfmt, 200, ' ', "Type set to A");
+ }
+
+ case 'I':
+ {
+ session->type = FTPD_SESSIONTYPE_I;
+ return ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 200, ' ', "Type set to I");
+ }
+
+ case 'L':
+ {
+ session->type = FTPD_SESSIONTYPE_L8;
+ return ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 200, ' ',
+ "Type set to L (byte size 8)");
+ }
+
+ default:
+ {
+ session->type = FTPD_SESSIONTYPE_NONE;
+ return ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 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_respfmt, 200, ' ', "Type set to L 8");
+ }
+ else
+ {
+ session->type = FTPD_SESSIONTYPE_NONE;
+ return ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 504, ' ', "Byte size must be 8 !");
+ }
+ }
+ }
+
+ session->type = FTPD_SESSIONTYPE_NONE;
+ return ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 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_respfmt, 502, ' ',
+ "MODE command not implemented !");
+}
+
+/****************************************************************************
+ * Name: ftpd_command_abor
+ ****************************************************************************/
+
+static int ftpd_command_abor(FAR struct ftpd_session_s *session)
+{
+ (void)ftpd_dataclose(session);
+ return ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 426, ' ',
+ "Transfer aborted. Data connection closed.");
+}
+
+/****************************************************************************
+ * Name: ftpd_command_quit
+ ****************************************************************************/
+
+static int ftpd_command_quit(FAR struct ftpd_session_s *session)
+{
+ int ret;
+
+ ret = ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 221, ' ', "Good-bye");
+ if (ret >= 0)
+ {
+ ret = 1; /* To disconnect */
+ }
+ return ret;
+}
+
+/****************************************************************************
+ * Name: ftpd_command_noop
+ ****************************************************************************/
+
+static int ftpd_command_noop(FAR struct ftpd_session_s *session)
+{
+ return ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 200, ' ',
+ "NOOP command successful");
+}
+
+/****************************************************************************
+ * Name: ftpd_command_port
+ ****************************************************************************/
+
+static int ftpd_command_port(FAR struct ftpd_session_s *session)
+{
+ uint8_t value[6];
+ unsigned int utemp;
+ int temp;
+ FAR char *str;
+ int index;
+ int ret;
+
+ index = 0;
+ while (index < 6)
+ {
+ /* Get the next value from the comma-delimited string */
+
+ str = ftpd_strtok(true, ",", &session->param);
+ if (*str == '\0')
+ {
+ break;
+ }
+
+ /* ftpd_strtok differs from the real strtok in that it does not NUL-
+ * terminate the strings.
+ */
+
+ if (session->param[0] != '\0')
+ {
+ session->param[0] = '\0';
+ session->param++;
+ }
+
+ /* Get the next value from the list */
+
+ temp = atoi(str);
+ if (temp < 0 || temp > 255)
+ {
+ ret = ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 501, ' ',
+ "Illegal PORT command");
+ if (ret < 0)
+ {
+ ndbg("ftpd_response failed: %d\n", ret);
+ return ret;
+ }
+ }
+
+ value[index++] = (uint8_t)temp;
+ }
+
+ if (index < 6)
+ {
+ return ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 501, ' ', "Illegal PORT command");
+ }
+
+ (void)ftpd_dataclose(session);
+
+#if 1 /* Follow param */
+
+ memset(session->data.addr, 0, sizeof(session->data.addr));
+
+ session->data.addr.in4.sin_family = AF_INET;
+
+ utemp = (value[0] << 24) | (value[1] << 16) | (value[2] << 8) | (value[3]);
+ session->data.addr.in4.sin_addr.s_addr = htonl((long)utemp);
+
+ utemp = (value[4] << 8) | (value[5]);
+ session->data.addr.in4.sin_port = htons((short)utemp);
+
+#else /* Follow command socket address */
+
+ session->data.addrlen = sizeof(session->data.addr);
+ ret = getpeername(session->cmd.sd, (struct sockaddr *)&session->data.addr,
+ &session->data.addrlen);
+ if (ret >= 0)
+ {
+ if (session->data.addr.ss.ss_family != AF_INET)
+ {
+ memset(&session->data.addr, 0, sizeof(session->data.addr));
+
+ session->data.addr.in4.sin_family = AF_INET;
+
+ utemp = (value[0] << 24) | (value[1] << 16) | (value[2] << 8) | (value[3]);
+ session->data.addr.in4.sin_addr.s_addr = htonl(utemp);
+ }
+
+ utemp = (value[4] << 8) | (value[5]);
+ session->data.addr.in4.sin_port = htons(utemp);
+ }
+ else
+ {
+ memset(&session->data.addr, 0, sizeof(session->data.addr));
+
+ session->data.addr.in4.sin_family = AF_INET;
+
+ utemp = (value[0] << 24) | (value[1] << 16) | (value[2] << 8) | (value[3]);
+ session->data.addr.in4.sin_addr.s_addr = htonl(utemp);
+ }
+
+ utemp = (value[4] << 8) | (value[5]);
+ session->data.addr.in4.sin_port = htons(utemp);
+#endif
+
+ return ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 200, ' ',
+ "PORT command successful");
+}
+
+/****************************************************************************
+ * Name: ftpd_command_eprt
+ ****************************************************************************/
+
+static int ftpd_command_eprt(FAR struct ftpd_session_s *session)
+{
+ FAR const char *str;
+ FAR char *field[3];
+ sa_family_t family;
+ size_t left;
+ size_t right;
+ int count;
+ int index;
+
+ left = 0;
+ right = strlen(session->param);
+
+ if (right <= 0)
+ {
+ /* no message ? */
+
+ (void)ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 502, ' ',
+ "EPRT command not implemented !");
+ return -EINVAL;
+ }
+ right--;
+
+ while (session->param[left] != '\0')
+ {
+ if (session->param[left] == '|')
+ {
+ left++;
+ break;
+ }
+ left++;
+ }
+
+ if (right <= 0 || left > right)
+ {
+ /* Invalid format */
+
+ (void)ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 502, ' ',
+ "EPRT command not implemented !");
+ return -EINVAL;
+ }
+
+ count = 3;
+ for (index = 0; index < count; index++)
+ {
+ field[index] = NULL;
+ }
+
+ str = (FAR const char *)&session->param[left];
+ for (index = 0; index < count && *str != '\0'; index++)
+ {
+ field[index] = ftpd_strtok_alloc(true, ",|)", &str);
+ if (!field[index])
+ {
+ break;
+ }
+
+ if (*str != '\0')
+ {
+ str++;
+ }
+ }
+
+ if (index < count)
+ {
+ for (index = 0; index < count; index++)
+ {
+ if (field[index])
+ {
+ free(field[index]);
+ }
+ }
+
+ (void)ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 502, ' ',
+ "EPRT command not implemented !");
+ return -EINVAL;
+ }
+
+ (void)ftpd_dataclose(session);
+
+ memset(&session->data.addr, 0, sizeof(session->data.addr));
+ family = atoi(field[0]);
+#ifndef CONFIG_NET_IPv6
+ if (family == 1)
+ {
+ family = AF_INET;
+
+ session->data.addr.in4.sin_family = family;
+ (void)inet_pton(family, field[1], &session->data.addr.in4.sin_addr);
+ session->data.addr.in4.sin_port = htons((short)atoi(field[2]));
+ }
+ else
+#endif
+#ifdef CONFIG_NET_IPv6
+ if (family == 2)
+ {
+ family = AF_INET6;
+
+ session->data.addr.in6.sin6_family = family;
+ (void)inet_pton(family, field[1], &session->data.addr.in6.sin6_addr);
+ session->data.addr.in6.sin6_port = htons((short)atoi(field[2]));
+ }
+ else
+#endif
+ {
+ ndbg("Unrecognized family: %d\n", family);
+ family = AF_UNSPEC;
+ }
+
+ for (index = 0;index < count;index++)
+ {
+ if (field[index])
+ {
+ free(field[index]);
+ }
+ }
+
+ if (family == AF_UNSPEC)
+ {
+ ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 502, ' ',
+ "EPRT command not implemented !");
+ return -EINVAL;
+ }
+
+ return ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 200, ' ', "EPRT command successful");
+}
+
+/****************************************************************************
+ * Name: ftpd_command_pwd
+ ****************************************************************************/
+
+static int ftpd_command_pwd(FAR struct ftpd_session_s *session)
+{
+ FAR const char *workpath;
+
+ if (!session->work)
+ {
+ workpath = "";
+ }
+ else
+ {
+ workpath = session->work;
+ }
+
+ return ftpd_response(session->cmd.sd, session->txtimeout,
+ "%03u%c\"%s\" is current directory.\r\n",
+ 257, ' ', workpath);
+}
+
+/****************************************************************************
+ * Name: ftpd_command_cwd
+ ****************************************************************************/
+
+static int ftpd_command_cwd(FAR struct ftpd_session_s *session)
+{
+ return ftpd_changedir(session, session->param);
+}
+
+/****************************************************************************
+ * Name: ftpd_command_cdup
+ ****************************************************************************/
+
+static int ftpd_command_cdup(FAR struct ftpd_session_s *session)
+{
+ return ftpd_changedir(session, g_cdup);
+}
+
+/****************************************************************************
+ * Name: ftpd_command_rmd
+ ****************************************************************************/
+
+static int ftpd_command_rmd(FAR struct ftpd_session_s *session)
+{
+ FAR char *abspath;
+ FAR char *workpath;
+ int ret;
+
+ ret = ftpd_getpath(session, session->param, &abspath, &workpath);
+ if (ret < 0)
+ {
+ return ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 550, ' ',
+ "Can not remove directory !");
+ }
+
+ if (strcmp(session->home, abspath) == 0)
+ {
+ free(abspath);
+ free(workpath);
+
+ return ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 550, ' ',
+ "Can not remove home directory !");
+ }
+
+ if (strcmp(session->work, workpath) == 0)
+ {
+ free(abspath);
+ free(workpath);
+
+ return ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 550, ' ',
+ "Can not remove current directory !");
+ }
+
+ ret = rmdir(abspath)
+ if (ret < 0)
+ {
+ free(abspath);
+ free(workpath);
+
+ return ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 550, ' ',
+ "Can not remove directory !");
+ }
+
+ free(abspath);
+ free(workpath);
+
+ return ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 250, ' ',
+ "RMD command successful");
+}
+
+/****************************************************************************
+ * Name: ftpd_command_mkd
+ ****************************************************************************/
+
+static int ftpd_command_mkd(FAR struct ftpd_session_s *session)
+{
+ FAR char *abspath;
+ int ret;
+
+ ret = ftpd_getpath(session, session->param, &abspath, NULL);
+ if (ret < 0)
+ {
+ return ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 550, ' ',
+ "Can not make directory !");
+ }
+
+ ret = mkdir(abspath, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
+ if (ret < 0)
+ {
+ free(abspath);
+ return ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 550, ' ', "Can not make directory !");
+ }
+
+ free(abspath);
+ return ftpd_response(session->cmd.sd, session->txtimeout,
+ "g_respfmt, 250, ' ', "MKD command successful");
+}
+
+/****************************************************************************
+ * Name: ftpd_command_dele
+ ****************************************************************************/
+
+static int ftpd_command_dele(FAR struct ftpd_session_s *session)
+{
+ FAR char *abspath;
+ FAR char *workpath;
+ int ret;
+
+ ret = ftpd_getpath(session, session->param, &abspath, &workpath);
+ if (ret < 0)
+ {
+ return ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 550, ' ', "Can not delete file !");
+ }
+
+ if (strcmp(session->home, abspath) == 0)
+ {
+ free(abspath);
+ free(workpath);
+
+ return ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 550, ' ',
+ "Can not delete home directory !");
+ }
+
+ if (strcmp(session->work, workpath) == 0)
+ {
+ free(abspath);
+ free(workpath);
+
+ return ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 550, ' ',
+ "Can not delete current directory !");
+ }
+
+ ret = remove(abspath);
+ if (ret < 0)
+ {
+ free(abspath);
+ free(workpath);
+
+ return ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 550, ' ', "Can not delete file !");
+ }
+
+ free(abspath);
+ free(workpath);
+
+ return ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 250, ' ', "DELE command successful");
+}
+
+/****************************************************************************
+ * Name: ftpd_command_pasv
+ ****************************************************************************/
+
+static int ftpd_command_pasv(FAR struct ftpd_session_s *session)
+{
+ unsigned int value[6];
+ unsigned int temp;
+
+ (void)ftpd_dataclose(session);
+
+ session->data.addrlen = sizeof(session->data.addr);
+
+ session->data.sd = socket(PF_INET, SOCK_STREAM, ftpd_getprotocol("tcp"));
+ if (session->data.sd < 0)
+ {
+ (void)ftpd_dataclose(session);
+ return ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 425, ' ', "PASV socket create fail !");
+ }
+
+ ret = getsockname(session->cmd.sd, (FAR struct sockaddr *)&session->data.addr,
+ &session->data.addrlen);
+ if (ret < 0)
+ {
+ (void)ftpd_dataclose(session);
+ return ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 425, ' ', "PASV getsockname fail !");
+ }
+
+#ifdef CONFIG_NET_IPv6
+ if (session->data.addr.ss.ss_family == AF_INET6)
+ {
+ /* Convert ipv6 to ipv4 */
+
+ if ((IN6_IS_ADDR_V4MAPPED(&session->data.addr.in6.sin6_addr) != 0) ||
+ (IN6_IS_ADDR_V4COMPAT(&session->data.addr.in6.sin6_addr) != 0))
+ {
+ /* convert ipv6 to ipv4 */
+
+ in_addr in4addr;
+
+ in4addr.s_addr = session->data.addr.in6.sin6_addr.s6_addr32[3];
+
+ memset(&session->data.addr, 0, sizeof(session->data.addr));
+ session->data.addr.in4.sin_family = AF_INET;
+ session->data.addr.in4.sin_addr.s_addr = in4addr.s_addr;
+ }
+ }
+ else
+#endif
+#ifndef CONFIG_NET_IPv6
+ if (session->data.addr.ss.ss_family != AF_INET)
+ {
+ /* Fixed to ipv4 */
+
+ memset((FAR void *)(&session->data.addr), 0, sizeof(session->data.addr));
+ session->data.addr.in4.sin_family = AF_INET;
+ session->data.addr.in4.sin_addr.s_addr = htonl(INADDR_ANY);
+ }
+ else
+#endif
+ {
+ ndbg("Unsupported family\n");
+ }
+
+ session->data.addr.in4.sin_port = 0;
+ ret = bind(session->data.sd, (FAR const struct sockaddr *)&session->data.addr,
+ session->data.addrlen)
+ if (ret < 0)
+ {
+ (void)ftpd_dataclose(session);
+ return ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 425, ' ', "PASV bind fail !");
+ }
+
+ ret = getsockname(session->data.sd, (FAR struct sockaddr *)&session->data.addr,
+ &session->data.addrlen);
+ if (ret < 0)
+ {
+ (void)ftpd_dataclose(session);
+ return ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 425, ' ', "PASV getsockname fail !");
+ }
+
+ ret = listen(session->data.sd, 1);
+ if (ret < 0)
+ {
+ (void)ftpd_dataclose(session);
+ return ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 425, ' ', "PASV listen fail !");
+ }
+
+ if (ntohl(session->data.addr.in4.sin_addr.s_addr) == INADDR_ANY)
+ {
+ (void)ftpd_dataclose(session);
+ return ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 425, ' ',
+ "Can not open passive connection");
+ }
+
+ temp = ntohl(session->data.addr.in4.sin_addr.s_addr);
+ value[0] = (temp >> 24) & 0xff;
+ value[1] = (temp >> 16) & 0xff;
+ value[2] = (temp >> 8) & 0xff;
+ value[3] = (temp) & 0xff;
+
+ temp = (unsigned int)ntohs(session->data.addr.in4.sin_port);
+ value[4] = (temp >> 8) & 0xff;
+ value[5] = (temp) & 0xff;
+
+ ret = ftpd_response(session->cmd.sd, session->txtimeout,
+ "%03u%cEntering passive mode (%u,%u,%u,%u,%u,%u).\r\n",
+ 227, ' ',
+ value[0], value[1], value[2],
+ value[3], value[4], value[5]);
+ if (ret < 0)
+ {
+ (void)ftpd_dataclose(session);
+ }
+
+ return ret;
+}
+
+/****************************************************************************
+ * Name: ftpd_command_epsv
+ ****************************************************************************/
+
+static int ftpd_command_epsv(fAR struct ftpd_session_s *session)
+{
+ (void)ftpd_dataclose(session);
+
+ session->data.addrlen = sizeof(session->data.addr);
+
+#ifdef CONFIG_NET_IPv6
+ session->data.sd = socket(PF_INET6, SOCK_STREAM, ftpd_getprotocol("tcp"));
+ if (session->data.sd < 0)
+ {
+ session->data.sd = socket(PF_INET, SOCK_STREAM, ftpd_getprotocol("tcp"));
+ }
+ else
+ {
+#if defined(IPPROTO_IPV6) && defined(IPV6_V6ONLY)
+ int ipv6only = 0;
+ (void)setsockopt(session->data.sd, IPPROTO_IPV6, IPV6_V6ONLY, &ipv6only, sizeof(ipv6only));
+#endif
+ }
+#else
+ session->data.sd = socket(PF_INET, SOCK_STREAM, ftpd_getprotocol("tcp"));
+#endif
+
+ if (session->data.sd < 0)
+ {
+ ret = ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 500, ' ', "EPSV socket create fail !");
+ (void)ftpd_dataclose(session);
+ return ret;
+ }
+
+ ret = getsockname(session->cmd.sd, (FAR struct sockaddr *)&session->data.addr,
+ &session->data.addrlen);
+ if (ret < 0)
+ {
+ ret = ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 500, ' ', "EPSV getsockname fail !");
+ (void)ftpd_dataclose(session);
+ return ret;
+ }
+
+#ifdef CONFIG_NET_IPv6
+ if (session->data.addr.ss.ss_family == AF_INET6)
+ {
+ session->data.addr.in6.sin6_port = htons(0);
+ }
+ else if (session->data.addr.ss.ss_family == AF_INET)
+ {
+ session->data.addr.in4.sin_port = htons(0);
+ }
+#else
+ session->data.addr.in4.sin_port = htons(0);
+#endif
+
+ ret = bind(session->data.sd, (FAR const struct sockaddr *)&session->data.addr,
+ session->data.addrlen);
+ if (ret < 0)
+ {
+ ret = ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 500, ' ', "EPSV bind fail !");
+ (void)ftpd_dataclose(session);
+ return ret;
+ }
+
+ ret = getsockname(session->data.sd, (FAR struct sockaddr *)&session->data.addr,
+ &session->data.addrlen);
+ if (ret < 0)
+ {
+ ret = ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 500, ' ', "EPSV getsockname fail !");
+ (void)ftpd_dataclose(session);
+ return ret;
+ }
+
+ ret = listen(session->data.sd, 1);
+ if (ret < 0)
+ {
+ ret = ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 500, ' ', "EPSV listen fail !");
+ (void)ftpd_dataclose(session);
+ return ret;
+ }
+
+#ifdef CONFIG_NET_IPv6
+ if (session->data.addr.ss.ss_family == AF_INET6)
+ {
+ ret = ftpd_response(session->cmd.sd, session->txtimeout,
+ "%03u%cEntering Extended Passive Mode (|||%u|).\r\n",
+ 229, ' ',
+ ntohs(session->data.addr.in6.sin6_port));
+ if (ret < 0)
+ {
+ (void)ftpd_dataclose(session);
+ return ret;
+ }
+ }
+ else
+#else
+ if (session->data.addr.ss.ss_family == AF_INET)
+ {
+ ret = ftpd_response(session->cmd.sd, session->txtimeout,
+ "%03u%cEntering Extended Passive Mode (|%u||%u|).\r\n",
+ 229, ' ', 1,
+ ntohs(session->data.addr.in4.sin_port));
+ if (ret < 0)
+ {
+ (void)ftpd_dataclose(session);
+ return ret;
+ }
+ }
+ else
+#endif
+ {
+ return ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 502, ' ', "EPSV command not implemented !");
+ }
+}
+
+/****************************************************************************
+ * 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_respfmt, 150, ' ',
+ "Opening ASCII mode data connection for file list");
+ if (ret < 0)
+ {
+ (void)ftpd_dataclose(session);
+ return ret;
+ }
+
+ opton |= ftpd_listoption((char **)(&session->param));
+ (void)ftpd_list(session, opton);
+
+ ret = ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 226, ' ', "Transfer complete");
+
+ (void)ftpd_dataclose(session);
+ return ret;
+}
+
+/****************************************************************************
+ * Name: ftpd_command_nlst
+ ****************************************************************************/
+
+static int ftpd_command_nlst(FAR struct ftpd_session_s *session)
+{
+ uint8_t opton = 0;
+ int ret;
+
+ ret = ftpd_dataopen(session);
+ if (ret < 0)
+ {
+ return 0;
+ }
+
+ ret = ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 150, ' ',
+ "Opening ASCII mode data connection for file list");
+ if (ret < 0)
+ {
+ (void)ftpd_dataclose(session);
+ return ret;
+ }
+
+ opton |= ftpd_listoption((char **)(&session->param));
+ (void)ftpd_list(session, opton);
+
+ ret = ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 226, ' ', "Transfer complete");
+
+ (void)ftpd_dataclose(session);
+ return ret;
+}
+
+/****************************************************************************
+ * Name: ftpd_command_acct
+ ****************************************************************************/
+
+static int ftpd_command_acct(FAR struct ftpd_session_s *session)
+{
+ return ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 502, ' ', "ACCT command not implemented !");
+}
+
+/****************************************************************************
+ * Name: ftpd_command_size
+ ****************************************************************************/
+
+static int ftpd_command_size(FAR struct ftpd_session_s *session)
+{
+ FAR char *abspath;
+ FAR char *path;
+ struct stat st;
+ FAR FILE *outstream;
+ off_t offset;
+ int ch;
+ int status
+ int ret;
+
+ ret = ftpd_getpath(session, session->param, &abspath, NULL);
+ if (ret < 0)
+ {
+ return ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 550, ' ', "Unknown size !");
+ }
+ path = abspath;
+
+ ret = 0;
+ switch (session->type)
+ {
+ case FTPD_SESSIONTYPE_NONE:
+ case FTPD_SESSIONTYPE_L8:
+ case FTPD_SESSIONTYPE_I:
+ {
+ status = stat(path, &st);
+ if (status < 0)
+ {
+ ret = ftpd_response(session->cmd.sd, session->txtimeout,
+ "%03u%c%s%s\r\n", 550, ' ', session->param,
+ ": not a regular file.");
+ }
+ else if (!S_ISREG(st.st_mode))
+ {
+ ret = ftpd_response(session->cmd.sd, session->txtimeout,
+ "%03u%c%s%s\r\n", 550, ' ', session->param,
+ ": not a regular file.");
+ }
+ else
+ {
+ ret = ftpd_response(session->cmd.sd, session->txtimeout,
+ "%03u%c%llu\r\n", 213, ' ', (unsigned long long)st.st_size);
+ }
+ }
+ break;
+
+ case FTPD_SESSIONTYPE_A:
+ {
+ status = stat(path, &st);
+ if (status < 0)
+ {
+ ret = ftpd_response(session->cmd.sd, session->txtimeout,
+ "%03u%c%s%s\r\n", 550, ' ', session->param,
+ ": not a regular file.");
+ if (ret < 0)
+ {
+ return ret;
+ }
+ }
+ else if (!S_ISREG(st.st_mode))
+ {
+ ret = ftpd_response(session->cmd.sd, session->txtimeout,
+ "%03u%c%s%s\r\n", 550, ' ', session->param,
+ ": not a regular file.");
+ if (ret < 0)
+ {
+ return ret;
+ }
+ }
+
+
+ outstream = fopen(path, "r");
+ if (!outstream)
+ {
+ ret = ftpd_response(session->cmd.sd, session->txtimeout,
+ "%03u%c%s%s\r\n", 550, ' ', session->param,
+ ": Can not open file !");
+ if (ret < 0)
+ {
+ return ret;
+ }
+ }
+
+ offset = 0;
+ for (;;)
+ {
+ ch = getc(outstream);
+ if (ch == EOF)
+ {
+ break;
+ }
+ else if (ch == 'c')
+ {
+ offset++;
+ }
+ offset++;
+ }
+
+ (void)fclose(outstream);
+ ret = ftpd_response(session->cmd.sd, session->txtimeout,
+ "%03u%c%llu\r\n", 213, ' ', (unsigned long long)offset);
+ }
+ break;
+
+ default:
+ {
+ ret = ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 504, ' ', "SIZE not implemented for type");
+ }
+ break;
+ }
+
+ free(abspath);
+ return ret;
+}
+
+/****************************************************************************
+ * Name: ftpd_command_stru
+ ****************************************************************************/
+
+static int ftpd_command_stru(FAR struct ftpd_session_s *session)
+{
+ return ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 502, ' ', "STRU command not implemented !");
+}
+
+/****************************************************************************
+ * Name:
+ ****************************************************************************/
+
+static int ftpd_command_rnfr(FAR struct ftpd_session_s *session)
+{
+ FAR char *abspath;
+ FAR char *path;
+ struct stat st;
+ int ret;
+
+ if (session->renamefrom != NULL)
+ {
+ free(session->renamefrom);
+ session->renamefrom = NULL;
+ }
+
+ ret = ftpd_getpath(session, session->param, &abspath, NULL);
+ if (ret < 0)
+ {
+ return ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 550, ' ', "RNFR error !");
+ }
+ path = abspath;
+
+ ret = stat(path, &st);
+ if (ret < 0)
+ {
+ free(abspath);
+ return ftpd_response(session->cmd.sd, session->txtimeout,
+ "%03u%c%s%s\r\n", 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_respfmt, 350, ' ', "RNFR successful");
+}
+
+/****************************************************************************
+ * Name: ftpd_command_rnto
+ ****************************************************************************/
+
+static int ftpd_command_rnto(FAR struct ftpd_session_s *session)
+{
+ FAR char *abspath;
+ int ret;
+
+ if (!session->renamefrom)
+ {
+ return ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 550, ' ', "RNTO error !");
+ }
+
+ ret = ftpd_getpath(session, session->param, &abspath, NULL);
+ if (ret < 0)
+ {
+ return ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 550, ' ', "RNTO error !");
+ }
+
+ ret = rename(session->renamefrom, abspath);
+ if (ret < 0)
+ {
+ free(abspath);
+ return ftpd_response(session->cmd.sd, session->txtimeout,
+ "%03u%c%s%s\r\n", 550, ' ', session->param,
+ ": Rename error.");
+ }
+
+ free(abspath);
+ return ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 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_respfmt, 320, ' ', "Restart position ready");
+}
+
+/****************************************************************************
+ * Name: ftpd_command_mdtm
+ ****************************************************************************/
+
+static int ftpd_command_mdtm(FAR struct ftpd_session_s *session)
+{
+ FAR char *abspath;
+ FAR char *path;
+ struct stat st;
+ struct tm tm;
+ int ret;
+
+ ret = ftpd_getpath(session, session->param, &abspath, NULL);
+ if (ret <0)
+ {
+ return ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 550, ' ', "Unknown size !");
+ }
+ path = abspath;
+
+ ret = stat(path, &st);
+ if (ret < 0)
+ {
+ free(abspath);
+ return ftpd_response(session->cmd.sd, session->txtimeout,
+ "%03u%c%s%s\r\n", 550, ' ', session->param,
+ ": not a plain file.");
+ }
+
+ if (!S_ISREG(st.st_mode))
+ {
+ free(abspath);
+ return ftpd_response(session->cmd.sd, session->txtimeout,
+ "%03u%c%s%s\r\n", 550, ' ', session->param,
+ ": not a plain file.");
+ }
+
+ free(abspath);
+
+ memcpy(&tm, gmtime(&st.st_mtime), sizeof(tm));
+ return ftpd_response(session->cmd.sd, session->txtimeout,
+ "%03u%c%04u%02u%02u%02u%02u%02u\r\n", 213, ' ',
+ tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
+ tm.tm_hour, tm.tm_min, tm.tm_sec);
+}
+
+/****************************************************************************
+ * Name: ftpd_command_opts
+ ****************************************************************************/
+
+static int ftpd_command_opts(FAR struct ftpd_session_s *session)
+{
+ FAR char *str;
+ FAR char *option;
+ FAR char *value;
+ bool remote = false;
+ bool local = false;
+
+ /* token: name and value */
+
+ str = session->param;
+ option = ftpd_strtok(true, " \t", &str);
+
+ /* Unlike the "real" strtok, ftpd_strtok does not NUL-terminate
+ * the returned string.
+ */
+
+ if (*str != '\0')
+ {
+ *str = '\0';
+ str++;
+ }
+ value = str;
+
+ if (strcasecmp(option, "UTF8") == 0 || strcasecmp(option, "UTF-8") == 0)
+ {
+ FAR char *lang;
+
+ if (value[0] == '\0' || strcasecmp(value, "ON") == 0 ||
+ strcasecmp(value, "ENABLE") == 0 || strcasecmp(value, "TRUE") == 0)
+ {
+ remote = true;
+ }
+ else {
+ remote = false;
+ }
+
+ lang = getenv("LANG");
+ if (lang)
+ {
+ if (ftpd_strcasestr(lang, "UTF8") != NULL ||
+ ftpd_strcasestr(lang, "UTF-8") != NULL)
+ {
+ 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_respfmt, 504, ' ', "UIF-8 disabled");
+ }
+
+ return ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 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_respfmt, 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_respfmt, 214,
+ (g_ftpdhelp[index + 1] == NULL) ? ' ' : '-',
+ 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;
+ int ret;
+
+ /* Clear immediately status (USER, REST, RNFR) */
+
+ session->flags &= ~(FTPD_SESSIONFLAG_USER|FTPD_SESSIONFLAG_RESTARTPOS|FTPD_SESSIONFLAG_RENAMEFROM);
+ if (session->user != NULL)
+ {
+ free(session->user);
+ session->user = NULL;
+ }
+
+ if (session->renamefrom != NULL)
+ {
+ free(session->renamefrom);
+ session->renamefrom = NULL;
+ }
+
+ session->restartpos = 0;
+
+ /* Search the command table for a matching command */
+
+ for (index = 0; g_ftpdcmdtab[index].cmd != NULL; index++)
+ {
+ /* Does the command string match this entry? */
+
+ if (strcmp(session->cmd, g_ftpdcmdtab[index].cmd) == 0)
+ {
+ /* Yes.. is a login required to execute this command? */
+
+ if ((g_ftpdcmdtab[index].flags & FTPD_CMDFLAG_LOGIN) != 0)
+ {
+ /* Yes... Check if the user is logged in */
+
+ if (session->curr == NULL && session->head != NULL)
+ {
+ return ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 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... that is bad 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,
+ "%03u%c%s%s\r\n", 500, ' ', session->cmd,
+ " not understood");
+}
+
+/****************************************************************************
+ * Worker Thread
+ ****************************************************************************/
+/****************************************************************************
+ * Name: ftpd_startworker
+ ****************************************************************************/
+
+static int ftpd_startworker(pthread_startroutine_t handler, FAR void *arg,
+ size_t stacksize)
+{
+ pthread_t threadid;
+ pthread_attr_t attr;
+ int ret;
+
+ /* Initialize the thread attributes */
+
+ ret = pthread_attr_init(&attr);
+ if (ret != 0)
+ {
+ ndbg("pthread_attr_init() failed: %d\n", ret);
+ goto errout;
+ }
+
+ /* The the thread stack size */
+
+ ret = pthread_attr_setstacksize(&attr, stacksize);
+ if (ret != 0)
+ {
+ ndbg("pthread_attr_setstacksize() failed: %d\n", ret);
+ goto errout_with_attr;
+ }
+
+ /* Start the thread in the detached state */
+
+ ret = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED)
+ if (ret != 0)
+ {
+ ndbg("pthread_attr_setdetachstate() failed: %d\n", ret);
+ goto errout_with_attr;
+ }
+
+ /* And create the thread */
+
+ ret = pthread_create(&threadid, &attr, handler, arg);
+ if (ret != 0)
+ {
+ ndbg("pthread_create() failed: %d\n", ret);
+ }
+
+errout_with_attr:
+ pthread_attr_destroy(&attr);
+errout:
+ return -ret;
+}
+
+/****************************************************************************
+ * Name: ftpd_freesession
+ ****************************************************************************/
+
+static void ftpd_freesession(FAR struct ftpd_session_s *session)
+{
+ /* Free resources */
+
+ if (session->renamefrom)
+ {
+ free(session->renamefrom);
+ }
+
+ if (session->work)
+ {
+ free(session->work);
+ }
+
+ if (session->home)
+ {
+ free(session->home);
+ }
+
+ if (session->user)
+ {
+ free((session->user);
+ }
+
+ if (session->fd < 0)
+ {
+ close(session->fd);
+ }
+
+ if (session->data.buffer)
+ {
+ free(session->data.buffer);
+ }
+
+ void)ftpd_dataclose(session);
+
+ if (session->cmd.buffer)
+ {
+ free((session->cmd.buffer);
+ }
+
+ if (session->cmd.sd <0)
+ {
+ close(session->cmd.sd);
+ }
+
+ free(session);
+}
+
+/****************************************************************************
+ * Name: ftpd_workersetup
+ ****************************************************************************/
+
+static void ftpd_workersetup(FAR struct ftpd_session_s *session)
+{
+#if defined(CONFIG_NET_HAVE_IPTOS) || defined(CONFIG_NET_HAVE_OOBINLINE)
+ int temp;
+#ifdef CONFIG_NET_HAVE_SOLINGER
+ struct linger ling;
+#endif
+
+#ifdef CONFIG_NET_HAVE_IPTOS
+ temp = IPTOS_LOWDELAY;
+ (void)setsockopt(session->cmd.sd, IPPROTO_IP, IP_TOS, &temp, sizeof(temp));
+#endif
+
+#ifdef CONFIG_NET_HAVE_OOBINLINE
+ temp = 1;
+ (void)setsockopt(session->cmd.sd, SOL_SOCKET, SO_OOBINLINE, &temp, sizeof(temp));
+#endif
+
+#ifdef CONFIG_NET_HAVE_SOLINGER
+ (void)memset(&ling, 0, sizeof(ling));
+ ling.l_onoff = 1;
+ ling.l_linger = 4;
+ (void)setsockopt(session->cmd.sd, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling));
+#endif
+}
+
+/****************************************************************************
+ * Name: ftpd_worker
+ ****************************************************************************/
+
+static FAR void *ftpd_worker(FAR void *arg)
+{
+ FAR struct ftpd_session_s *session = (FAR struct ftpd_session_s *)arg;
+ ssize_t recvbytes;
+ size_t offset;
+ uint8_t ch;
+ int ret;
+
+ nvdbg("Worker started\n");
+ DEBUGASSERT(session);
+
+ /* Configure the session sockets */
+
+ ftpd_workersetup(session);
+
+ /* Send the welcoming message */
+
+ ret = ftpd_response(session->cmd.sd, session->txtimeout,
+ g_respfmt, 220, ' ', CONFIG_FTPD_SERVERID);
+ if (ret < 0)
+ {
+ ndbg("ftpd_response() failed: %d\n", ret);
+ ftpd_freesession(session);
+ return NULL;
+ }
+
+ /* Then loop processing FTP commands */
+
+ for (;;)
+ {
+ /* Receive the next command */
+
+ recvbytes = ftpd_recv(session->cmd.sd, session->cmd.buffer,
+ session->cmd.buflen - 1), session->rxtimeout);
+
+ /* recbytes < 0 is a receive failure (posibily a timeout);
+ * recbytes == 0 indicates that we have lost the connection.
+ */
+
+ if (recvbytes <= 0)
+ {
+ /* Break out of the server loop */
+
+ break;
+ }
+
+ /* Make sure that the recevied string is NUL terminated */
+
+ session->cmd.buffer[recvbytes] = '\0';
+
+ /* TELNET protocol (RFC854)
+ * IAC 255(FFH) interpret as command:
+ * IP 244(F4H) interrupt process--permanently
+ * DM 242(F2H) data mark--for connect. cleaning
+ */
+
+ offset = 0;
+ while (recvbytes > 0)
+ {
+ ch = session->cmd.buffer[offset];
+ if (ch != 0xff && ch != 0xf4 && ch != 0xf2)
+ {
+ break;
+ }
+
+ (void)ftpd_send(session->cmd.sd, &session->cmd.buffer[offset], 1, session->txtimeout);
+
+ offset++;
+ recvbytes--;
+ }
+
+ /* Just continue if there was nothing of interest in the packet */
+
+ if (recvbytes <= 0)
+ {
+ continue;
+ }
+
+ /* Make command message */
+
+ session->cmd = &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->cmd;
+ session->cmd = ftpd_strtok(true, " \t", &session->param);
+
+ /* Unlike the "real" strtok, ftpd_strtok does not NUL-terminate
+ * the returned string.
+ */
+
+ if (session->param[0] != '\0')
+ {
+ session->param[0] = '\0';
+ session->param++;
+ }
+
+ /* Dispatch the FTP command */
+
+ ret = ftpd_command(session);
+ if (ret < 0)
+ {
+ ndbg("Disconnected by the command handler: %s\n", ret);
+ break;
+ }
+ }
+
+ ftpd_freesession(session);
+ return NULL;
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: ftpd_open
+ *
+ * Description:
+ * Create an instance of the FTPD server and return a handle that can be
+ * used to run the server.
+ *
+ * Input Parameters:
+ * None
+ *
+ * Returned Value:
+ * On success, a non-NULL handle is returned that can be used to reference
+ * the server instance.
+ *
+ ****************************************************************************/
+
+FTPD_SESSION ftpd_open(void)
+{
+ FAR struct ftpd_server_s *server;
+
+ server = ftpd_openserver(21);
+ if (server == NULL)
+ {
+ server = ftpd_openserver(2211);
+ }
+
+ return (FTPD_SESSION)server;
+}
+
+/****************************************************************************
+ * Name: ftpd_adduser
+ *
+ * Description:
+ * Add one FTP user.
+ *
+ * Input Parameters:
+ * handle - A handle previously returned by ftpd_open
+ * accountflags - The characteristics of this user (see FTPD_ACCOUNTFLAGS_*
+ * defintiions.
+ * 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)
+ {
+ ndbg("Failed to allocte memory to the account\n");
+ ret = -ENOMEM;
+ goto errout;
+ }
+
+ ret = ftpd_account_setpassord(newaccount, passwd);
+ if (ret < 0)
+ {
+ ndbg("ftpd_account_setpassord failed: %d\n", ret);
+ goto errout_with_account;
+ }
+
+ ret = ftpd_account_sethome(newaccount, home);
+ if (ret < 0)
+ {
+ ndbg("ftpd_account_sethome failed: %d\n", ret);
+ goto errout_with_account;
+ }
+
+ server = (FAR struct ftpd_server_s *)handle;
+ ret = ftpd_account_add(server, newaccount);
+ if (ret < 0)
+ {
+ ndbg("ftpd_account_add failed: %d\n", ret);
+ goto errout_with_account;
+ }
+
+ return OK;
+
+errout_with_account:
+ ftpd_account_free(newaccount);
+
+errout:
+ return ret;
+}
+
+/****************************************************************************
+ * Name: ftpd_session
+ *
+ * Description:
+ * Execute the FTPD server. This thread does not return until either (1)
+ * the timeout expires with no connection, (2) some other error occurs, or
+ * (2) a connection was accepted and an FTP worker thread was started to
+ * service the session.
+ *
+ * Input Parameters:
+ * handle - A handle previously returned by ftpd_open
+ * timeout - A time in milliseconds to wait for a connection. If this
+ * time elapses with no connected, the -ETIMEDOUT error will be returned.
+ *
+ * Returned Value:
+ * Zero is returned if the FTP worker was started. On failure, a negated
+ * errno value is returned to indicate why the servier terminated.
+ * -ETIMEDOUT indicates that the user-provided timeout elapsed with no
+ * connection.
+ *
+ ****************************************************************************/
+
+int ftpd_session(FTPD_SESSION handle, int timeout)
+{
+ FAR struct ftpd_server_s *server;
+ FAR struct ftpd_session_s *session;
+ int ret;
+
+ DEBUGASSERT(handle);
+
+ server = (FAR struct ftpd_server_s *)handle;
+
+ /* Allocate a session */
+
+ session = (FAR struct ftpd_session_s *)zalloc(sizeof(struct ftpd_session_s));
+ if (session == NULL)
+ {
+ ret = -ENOMEM;
+ goto errout;
+ }
+
+ /* Initialize the session */
+
+ session->server = server;
+ session->head = server->head;
+ session->curr = NULL;
+ session->flags = 0;
+ session->txtimeout = -1;
+ session->rxtimeout = -1;
+ session->cmd.sd = (int)(-1);
+ session->cmd.addrlen = (socklen_t)sizeof(session->cmd.addr);
+ session->cmd.buflen = (size_t)CONFIG_FTPD_CMDBUFFERSIZE;
+ session->cmd.buffer = NULL;
+ session->cmd = 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 = (uint8_t *)malloc(session->cmd.buflen);
+ if (session->cmd.buffer == NULL)
+ {
+ ret = -ENOMEM;
+ goto errout_with_session;
+ }
+
+ /* Allocate a data buffer */
+
+ session->data.buffer = (uint8_t *)malloc(session->data.buflen);
+ if (session->data.buffer == NULL)
+ {
+ 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))
+ {
+ ret = -errno;
+ 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)
+ {
+ goto errout_with_session;
+ }
+
+ /* Successfully connected an launched the worker thread */
+
+ return 0;
+
+errout_with_session:
+ ftpd_freesession(session);
+errout:
+ return ret;
+}
+
+/****************************************************************************
+ * Name: ftpd_close
+ *
+ * Description:
+ * Close and destroy the handle created by ftpd_open.
+ *
+ * Input Parameters:
+ * handle - A handle previously returned by ftpd_open
+ *
+ * Returned Value:
+ * None
+ *
+ ****************************************************************************/
+
+void ftpd_close(FTPD_SESSION handle)
+{
+ struct ftpd_server_s *server;
+ DEBUGASSERT(handle);
+
+ server = (struct ftpd_server_s *)handle;
+ ftpd_account_free(server->head);
+
+ if (server->sd != -1))
+ {
+ close(server->sd);
+ server->sd = -1;
+ }
+
+ free(server);
+}
+
diff --git a/apps/netutils/ftpd/ftpd.h b/apps/netutils/ftpd/ftpd.h
new file mode 100755
index 000000000..938ca4b89
--- /dev/null
+++ b/apps/netutils/ftpd/ftpd.h
@@ -0,0 +1,204 @@
+/****************************************************************************
+ * apps/include/ftpd.h
+ *
+ * Copyright (C) 2012 Gregory Nutt. All rights reserved.
+ * Author: Gregory Nutt <spudmonkey@racsa.co.cr>
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+#ifndef __APPS_NETUTILS_FTPD_FTPD_H
+#define __APPS_NETUTILS_FTPD_FTPD_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <sys/types.h>
+#include <stdbool.h>
+
+#include <netinet/in.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+/* FPTD Definitions *********************************************************/
+
+# define FTPD_ACCOUNTFLAG_NONE (0)
+# define FTPD_ACCOUNTFLAG_ADMIN (1 << 0)
+# define FTPD_ACCOUNTFLAG_SYSTEM (1 << 1)
+# define FTPD_ACCOUNTFLAG_GUEST (1 << 2)
+
+# define FTPD_SESSIONFLAG_USER (1 << 0)
+# define FTPD_SESSIONFLAG_RESTARTPOS (1 << 1)
+# define FTPD_SESSIONFLAG_RENAMEFROM (1 << 2)
+
+# define FTPD_LISTOPTION_A (1 << 0)
+# define FTPD_LISTOPTION_L (1 << 1)
+# define FTPD_LISTOPTION_F (1 << 2)
+# define FTPD_LISTOPTION_R (1 << 3)
+# define FTPD_LISTOPTION_UNKNOWN (1 << 7)
+
+# define FTPD_CMDFLAG_LOGIN (1 << 0)
+
+/****************************************************************************
+ * Public Types
+ ****************************************************************************/
+/* This enumerates the type of each session */
+
+enum ftpd_sessiontype_e
+{
+ FTPD_SESSIONTYPE_NONE = 0
+ FTPD_SESSIONTYPE_A,
+ FTPD_SESSIONTYPE_I,
+ FTPD_SESSIONTYPE_L8
+};
+
+typedef struct ftpd_pathnode_s
+{
+ struct ftpd_pathnode_s *flink;
+ struct ftpd_pathnode_s *blink;
+ bool ignore;
+ FAR char *name;
+};
+
+union ftpd_sockaddr_u
+{
+ uint8_t raw[sizeof(struct sockaddr_storage)];
+ struct sockaddr_storage ss;
+ struct sockaddr sa;
+#ifdef CONFIG_NET_IPv6
+ struct sockaddr_in6 in6;
+#else
+ struct sockaddr_in in4;
+#endif
+};
+
+/* This structure describes on account */
+
+struct ftpd_account_s
+{
+ struct ftpd_account_s *blink;
+ struct ftpd_account_s *flink;
+ uint8_t flags; /* See FTPD_ACCOUNTFLAG_* definitions */
+ FAR char *user; /* User name */
+ FAR char *password; /* Un-encrypted password */
+ FAR char *home; /* Home directory path */
+};
+
+/* This structures describes an FTP session a list of associated accounts */
+
+struct ftpd_server_s
+{
+ int sd; /* Listen socket descriptor */
+ union ftpd_sockaddr_u addr; /* Listen address */
+ struct ftpd_account_s *head; /* Head of a list of accounts */
+ struct ftpd_account_s *tail; /* Tail of a list of accounts */
+};
+
+struct ftpd_stream_s
+{
+ int sd; /* Socket descriptor */
+ union ftpd_sockaddr_u addr; /* Network address */
+ socklen_t addrlen; /* Length of the address */
+ size_t buflen; /* Length of the buffer */
+ uint8_t *buffer; /* Pointer to the buffer */
+};
+
+struct ftpd_session_s
+{
+ FAR struct ftpd_server_s shadow;
+ FAR struct ftpd_account_s *head;
+ FAR struct ftpd_account_s *curr;
+ uint8_t flags; /* See TPD_SESSIONFLAG_* definitions */
+ int txtimeout;
+ int txtimeout;
+
+ /* Command */
+
+ struct ftpd_stream_s cmd;
+ FAR char *cmd;
+ FAR char *param;
+
+ /* Data */
+
+ struct ftpd_stream_s data;
+ off_t restartpos;
+
+ /* File */
+
+ int fd;
+
+ /* Current user */
+
+ FAR char *user;
+ uint8_t m_type; /* See enum ftpd_sessiontype_e */
+ FAR char *home;
+ FAR char *work;
+ FAR char *renamefrom;
+};
+
+typedef int (*ftpd_cmdhandler_t)(struct ftpd_session_s *);
+
+struct ftpd_cmd_s
+{
+ FAR const char *cmd; /* The command string */
+ ftpd_cmdhandler_t handler; /* The function that handles the command */
+ uint8_t flags; /* See FTPD_CMDFLAGS_* definitions */
+};
+
+/* Used to maintain a list of protocol names */
+
+struct ftpd_protocol_s
+{
+ FAR const char *name;
+ int value;
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+#ifdef __cplusplus
+#define EXTERN extern "C"
+extern "C" {
+#else
+#define EXTERN extern
+#endif
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+#undef EXTERN
+#ifdef __cplusplus
+}
+#endif
+#endif /* __APPS_NETUTILS_FTPD_FTPD_H */
diff --git a/nuttx/fs/fs_poll.c b/nuttx/fs/fs_poll.c
index f5aa9dcf0..b81a4d3c6 100644
--- a/nuttx/fs/fs_poll.c
+++ b/nuttx/fs/fs_poll.c
@@ -1,8 +1,8 @@
/****************************************************************************
* fs/fs_poll.c
*
- * Copyright (C) 2008-2009 Gregory Nutt. All rights reserved.
- * Author: Gregory Nutt <spudmonkey@racsa.co.cr>
+ * Copyright (C) 2008-2009, 2012 Gregory Nutt. All rights reserved.
+ * Author: Gregory Nutt <gnutt@nuttx.org>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
diff --git a/nuttx/include/sys/socket.h b/nuttx/include/sys/socket.h
index 68888eaa7..7613d00cd 100644
--- a/nuttx/include/sys/socket.h
+++ b/nuttx/include/sys/socket.h
@@ -52,7 +52,8 @@
/* Protocol families */
-#define PF_UNIX 0 /* Local communication */
+#define PF_UNSPEC 0 /* Protocol family unspecified */
+#define PF_UNIX 1 /* Local communication */
#define PF_LOCAL 1 /* Local communication */
#define PF_INET 2 /* IPv4 Internet protocols */
#define PF_INET6 3 /* IPv6 Internet protocols */
@@ -66,6 +67,7 @@
/* Address families */
+#define AF_UNSPEC PF_UNSPEC
#define AF_UNIX PF_UNIX
#define AF_LOCAL PF_LOCAL
#define AF_INET PF_INET
diff --git a/nuttx/lib/net/lib_inetntop.c b/nuttx/lib/net/lib_inetntop.c
index b48cd5d11..3c8eb936a 100644
--- a/nuttx/lib/net/lib_inetntop.c
+++ b/nuttx/lib/net/lib_inetntop.c
@@ -5,7 +5,10 @@
* Author: Gregory Nutt <gnutt@nuttx.org>
*
* Includes some logic extracted from hwport_ftpd, written by Jaehyuk Cho
- * <minzkn@minzkn.com> which has a BSD license (but no file headers).
+ * <minzkn@minzkn.com> which was released under the BSD license.
+ *
+ * Copyright (C) HWPORT.COM. All rights reserved.
+ * Author: JAEHYUK CHO <mailto:minzkn@minzkn.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
diff --git a/nuttx/lib/net/lib_inetpton.c b/nuttx/lib/net/lib_inetpton.c
index 195e96b60..281d03d40 100644
--- a/nuttx/lib/net/lib_inetpton.c
+++ b/nuttx/lib/net/lib_inetpton.c
@@ -5,7 +5,10 @@
* Author: Gregory Nutt <gnutt@nuttx.org>
*
* Includes some logic extracted from hwport_ftpd, written by Jaehyuk Cho
- * <minzkn@minzkn.com> which has a BSD license (but no file headers).
+ * <minzkn@minzkn.com> which was released under the BSD license.
+ *
+ * Copyright (C) HWPORT.COM. All rights reserved.
+ * Author: JAEHYUK CHO <mailto:minzkn@minzkn.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -123,6 +126,8 @@ int inet_pton(int af, FAR const char *src, FAR void *dst)
return -1;
}
+ (void)memset(dst, 0, sizeof(struct in_addr));
+
ip = (uint8_t *)dst;
srcoffset = 0;
numoffset = 0;
@@ -199,14 +204,6 @@ int inet_pton(int af, FAR const char *src, FAR void *dst)
return 0;
#else
- DEBUGASSERT(src && dst);
-
- if (af != AF_INET6)
- {
- set_errno(EAFNOSUPPORT);
- return -1;
- }
-
size_t srcoffset;
size_t numoffset;
long value;
@@ -214,22 +211,31 @@ int inet_pton(int af, FAR const char *src, FAR void *dst)
int nrsep;
uint8_t ch;
char numstr[5];
- uint8_t ip[sizeof(in_addr)];
- uint8_t rip[sizeof(in_addr)];
+ uint8_t ip[sizeof(struct in6_addr)];
+ uint8_t rip[sizeof(struct in6_addr)];
bool rtime;
- (void)memset(dst, 0, sizeof(in_addr));
- srcoffset = 0;
- numoffset = 0;
- nsep = 0;
- nrsep = 0;
- rtime = false;
+ DEBUGASSERT(src && dst);
+
+ if (af != AF_INET6)
+ {
+ set_errno(EAFNOSUPPORT);
+ return -1;
+ }
+
+ (void)memset(dst, 0, sizeof(struct in6_addr));
+
+ srcoffset = 0;
+ numoffset = 0;
+ nsep = 0;
+ nrsep = 0;
+ rtime = false;
for(;;)
{
ch = (uint8_t)src[srcoffset++];
- if (ch == ':' || ch == '\0' /* || ch == '/' */ )
+ if (ch == ':' || ch == '\0')
{
if (ch == ':' && (nsep + nrsep) >= 8)
{
@@ -242,7 +248,7 @@ int inet_pton(int af, FAR const char *src, FAR void *dst)
{
/* Empty numeric string */
- if (rtime != 0 && nrsep > 1)
+ if (rtime && nrsep > 1)
{
/* dup simple */
@@ -251,7 +257,6 @@ int inet_pton(int af, FAR const char *src, FAR void *dst)
numoffset = 0;
rtime = true;
-
continue;
}
@@ -281,7 +286,9 @@ int inet_pton(int af, FAR const char *src, FAR void *dst)
if (ch == '\0' /* || ch == '/' */)
{
- if ((nsep <= 1 && nrsep <= 0) || (nsep + nrsep) < 1 || (nsep + nrsep) > 8)
+ if ((nsep <= 1 && nrsep <= 0) ||
+ (nsep + nrsep) < 1 ||
+ (nsep + nrsep) > 8)
{
/* Separator count problem */
@@ -303,7 +310,9 @@ int inet_pton(int af, FAR const char *src, FAR void *dst)
return 0;
}
}
- else if ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F'))
+ else if ((ch >= '0' && ch <= '9') ||
+ (ch >= 'a' && ch <= 'f') ||
+ (ch >= 'A' && ch <= 'F'))
{
numstr[numoffset++] = ch;
if (numoffset >= 5)