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