/****************************************************************************
 * 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 <nuttx/config.h>

#include <sys/socket.h>
#include <sys/stat.h>

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <dirent.h>
#include <string.h>
#include <ctype.h>
#include <fcntl.h>
#include <poll.h>
#include <libgen.h>
#include <errno.h>
#include <debug.h>

#include <arpa/inet.h>

#include "netutils/ftpd.h"

#include "ftpd.h"

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

#define __NUTTX__ 1 /* Flags some unusual NuttX dependencies */

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/
/* Account functions */

static FAR struct ftpd_account_s *ftpd_account_new(FAR const char *user,
              uint8_t accountflags);
static void ftpd_account_free(FAR struct ftpd_account_s *account);
static int  ftpd_account_setpassword(FAR struct ftpd_account_s *account,
              FAR const char *passwd);
static int  ftpd_account_add(FAR struct ftpd_server_s *server,
              FAR struct ftpd_account_s *account);
static int  ftpd_account_sethome(FAR struct ftpd_account_s *account,
              FAR const char *home);
static FAR struct ftpd_account_s *
              ftpd_account_search_user(FAR struct ftpd_session_s *session,
              FAR const char *user);
static FAR struct ftpd_account_s *
              ftpd_account_login(FAR struct ftpd_session_s *session,
              FAR const char *user, FAR const char *passwd);

/* Parsing functions */

static FAR char *ftpd_strtok(bool skipspace, FAR const char *delimiters,
              FAR char **str);
static FAR char *ftpd_strtok_alloc(bool skipspace,
              FAR const char *delimiters, FAR const char **str);

/* Socket helpers */

static int  ftpd_rxpoll(int sd, int timeout);
static int  ftpd_txpoll(int sd, int timeout);
static int  ftpd_accept(int sd, FAR void *addr, FAR socklen_t *addrlen,
              int timeout);
static ssize_t ftpd_recv(int sd, FAR void *data, size_t size, int timeout);
static ssize_t ftpd_send(int sd, FAR const void *data, size_t size,
              int timeout);
static ssize_t ftpd_response(int sd, int timeout, FAR const char *fmt, ...);

static int  ftpd_dataopen(FAR struct ftpd_session_s *session);
static int  ftpd_dataclose(FAR struct ftpd_session_s *session);
static FAR struct ftpd_server_s *ftpd_openserver(int port);

/* Path helpers */

static int ftpd_pathignore(FAR struct ftpd_pathnode_s *currpath);
static void ftpd_nodefree(FAR struct ftpd_pathnode_s *node);
static FAR struct ftpd_pathnode_s *ftpd_path2node(FAR const char *path);
static FAR char *ftpd_node2path(FAR struct ftpd_pathnode_s *node,
              bool strip);
static FAR struct ftpd_pathnode_s *
              ftpd_nodeappend(FAR struct ftpd_pathnode_s *head,
              FAR struct ftpd_pathnode_s *node, bool override);
static int  ftpd_getpath(FAR struct ftpd_session_s *session,
              FAR const char *path, FAR char **abspath,
              FAR char **workpath);

/* Commmand helpers */

static int  ftpd_changedir(FAR struct ftpd_session_s *session,
              FAR const char *rempath);
static off_t ftpd_offsatoi(FAR const char *filename, off_t offset);
static int ftpd_stream(FAR struct ftpd_session_s *session, int cmdtype);
static uint8_t ftpd_listoption(FAR char **param);
static int  ftpd_listbuffer(FAR struct ftpd_session_s *session,
              FAR char *path, FAR struct stat *st, FAR char *buffer,
              size_t buflen, unsigned int opton);
static int  fptd_listscan(FAR struct ftpd_session_s *session,
              FAR char *path, unsigned int opton);
static int  ftpd_list(FAR struct ftpd_session_s *session,
              unsigned int opton);

/* Command handlers */

static int ftpd_command_user(FAR struct ftpd_session_s *session);
static int ftpd_command_pass(FAR struct ftpd_session_s *session);
static int ftpd_command_syst(FAR struct ftpd_session_s *session);
static int ftpd_command_type(FAR struct ftpd_session_s *session);
static int ftpd_command_mode(FAR struct ftpd_session_s *session);
static int ftpd_command_abor(FAR struct ftpd_session_s *session);
static int ftpd_command_quit(FAR struct ftpd_session_s *session);
static int ftpd_command_noop(FAR struct ftpd_session_s *session);
static int ftpd_command_port(FAR struct ftpd_session_s *session);
static int ftpd_command_eprt(FAR struct ftpd_session_s *session);
static int ftpd_command_pwd(FAR struct ftpd_session_s *session);
static int ftpd_command_cwd(FAR struct ftpd_session_s *session);
static int ftpd_command_cdup(FAR struct ftpd_session_s *session);
static int ftpd_command_rmd(FAR struct ftpd_session_s *session);
static int ftpd_command_mkd(FAR struct ftpd_session_s *session);
static int ftpd_command_dele(FAR struct ftpd_session_s *session);
static int ftpd_command_pasv(FAR struct ftpd_session_s *session);
static int ftpd_command_epsv(FAR struct ftpd_session_s *session);
static int ftpd_command_list(FAR struct ftpd_session_s *session);
static int ftpd_command_nlst(FAR struct ftpd_session_s *session);
static int ftpd_command_acct(FAR struct ftpd_session_s *session);
static int ftpd_command_size(FAR struct ftpd_session_s *session);
static int ftpd_command_stru(FAR struct ftpd_session_s *session);
static int ftpd_command_rnfr(FAR struct ftpd_session_s *session);
static int ftpd_command_rnto(FAR struct ftpd_session_s *session);
static int ftpd_command_retr(FAR struct ftpd_session_s *session);
static int ftpd_command_stor(FAR struct ftpd_session_s *session);
static int ftpd_command_appe(FAR struct ftpd_session_s *session);
static int ftpd_command_rest(FAR struct ftpd_session_s *session);
static int ftpd_command_mdtm(FAR struct ftpd_session_s *session);
static int ftpd_command_opts(FAR struct ftpd_session_s *session);
static int ftpd_command_site(FAR struct ftpd_session_s *session);
static int ftpd_command_help(FAR struct ftpd_session_s *session);

static int ftpd_command(FAR struct ftpd_session_s *session);

/* Worker thread */

static int  ftpd_startworker(pthread_startroutine_t handler, FAR void *arg,
              size_t stacksize);
static void ftpd_freesession(FAR struct ftpd_session_s *session);
static void ftpd_workersetup(FAR struct ftpd_session_s *session);
static FAR void *ftpd_worker(FAR void *arg);

/****************************************************************************
 * Private Data
 ****************************************************************************/

static const struct ftpd_cmd_s g_ftpdcmdtab[] =
{
  {"USER", ftpd_command_user, 0},                  /* USER <SP> <username> <CRLF> */
  {"PASS", ftpd_command_pass, 0},                  /* PASS <SP> <password> <CRLF> */
  {"SYST", ftpd_command_syst, FTPD_CMDFLAG_LOGIN}, /* SYST <CRLF> */
  {"TYPE", ftpd_command_type, FTPD_CMDFLAG_LOGIN}, /* TYPE <SP> <type-code> <CRLF> */
  {"MODE", ftpd_command_mode, FTPD_CMDFLAG_LOGIN}, /* MODE <SP> <mode-code> <CRLF> */
  {"ABOR", ftpd_command_abor, FTPD_CMDFLAG_LOGIN}, /* ABOR <CRLF> */
  {"QUIT", ftpd_command_quit, 0},                  /* QUIT <CRLF> */
  {"NOOP", ftpd_command_noop, FTPD_CMDFLAG_LOGIN}, /* NOOP <CRLF> */
  {"PORT", ftpd_command_port, FTPD_CMDFLAG_LOGIN}, /* PORT <SP> <host-port> <CRLF> */
  {"EPRT", ftpd_command_eprt, FTPD_CMDFLAG_LOGIN}, /* EPRT <SP> <d> <net-prt> <d> <net-addr> <d> <tcp-port> <d> <CRLF> */
  {"PWD" , ftpd_command_pwd , FTPD_CMDFLAG_LOGIN}, /* PWD  <CRLF> */
  {"XPWD", ftpd_command_pwd , FTPD_CMDFLAG_LOGIN}, /* XPWD <CRLF> */
  {"CWD" , ftpd_command_cwd , FTPD_CMDFLAG_LOGIN}, /* CWD  <SP> <pathname> <CRLF> */
  {"XCWD", ftpd_command_cwd , FTPD_CMDFLAG_LOGIN}, /* XCWD <SP> <pathname> <CRLF> */
  {"CDUP", ftpd_command_cdup, FTPD_CMDFLAG_LOGIN}, /* CDUP <CRLF> */
  {"XCUP", ftpd_command_cdup, FTPD_CMDFLAG_LOGIN}, /* XCUP <CRLF> */
  {"RMD" , ftpd_command_rmd , FTPD_CMDFLAG_LOGIN}, /* RMD  <SP> <pathname> <CRLF> */
  {"XRMD", ftpd_command_rmd , FTPD_CMDFLAG_LOGIN}, /* XRMD <SP> <pathname> <CRLF> */
  {"MKD" , ftpd_command_mkd , FTPD_CMDFLAG_LOGIN}, /* MKD  <SP> <pathname> <CRLF> */
  {"XMKD", ftpd_command_mkd , FTPD_CMDFLAG_LOGIN}, /* XMKD <SP> <pathname> <CRLF> */
  {"DELE", ftpd_command_dele, FTPD_CMDFLAG_LOGIN}, /* DELE <SP> <pathname> <CRLF> */
  {"PASV", ftpd_command_pasv, FTPD_CMDFLAG_LOGIN}, /* PASV <CRLF> */
  {"EPSV", ftpd_command_epsv, FTPD_CMDFLAG_LOGIN}, /* EPSV <SP> <net-prt> <CRLF> OR EPSV <SP> ALL <CRLF> */
  {"LPSV", ftpd_command_epsv, FTPD_CMDFLAG_LOGIN}, /* LPSV ??? */
  {"LIST", ftpd_command_list, FTPD_CMDFLAG_LOGIN}, /* LIST [<SP> <pathname>] <CRLF> */
  {"NLST", ftpd_command_nlst, FTPD_CMDFLAG_LOGIN}, /* NLST [<SP> <pathname>] <CRLF> */
  {"ACCT", ftpd_command_acct, FTPD_CMDFLAG_LOGIN}, /* ACCT <SP> <account-information> <CRLF> */
  {"SIZE", ftpd_command_size, FTPD_CMDFLAG_LOGIN}, /* SIZE <SP> <pathname> <CRLF> */
  {"STRU", ftpd_command_stru, FTPD_CMDFLAG_LOGIN}, /* STRU <SP> <structure-code> <CRLF> */
  {"RNFR", ftpd_command_rnfr, FTPD_CMDFLAG_LOGIN}, /* RNFR <SP> <pathname> <CRLF> */
  {"RNTO", ftpd_command_rnto, FTPD_CMDFLAG_LOGIN}, /* RNTO <SP> <pathname> <CRLF> */
  {"RETR", ftpd_command_retr, FTPD_CMDFLAG_LOGIN}, /* RETR <SP> <pathname> <CRLF> */
  {"STOR", ftpd_command_stor, FTPD_CMDFLAG_LOGIN}, /* STOR <SP> <pathname> <CRLF> */
  {"APPE", ftpd_command_appe, FTPD_CMDFLAG_LOGIN}, /* APPE <SP> <pathname> <CRLF> */
  {"REST", ftpd_command_rest, FTPD_CMDFLAG_LOGIN}, /* REST <SP> <marker> <CRLF> */
  {"MDTM", ftpd_command_mdtm, FTPD_CMDFLAG_LOGIN}, /* MDTM <SP> <pathname> <CRLF> */
  {"OPTS", ftpd_command_opts, FTPD_CMDFLAG_LOGIN}, /* OPTS <SP> <option> <value> <CRLF> */
  {"SITE", ftpd_command_site, FTPD_CMDFLAG_LOGIN}, /* SITE <SP> <string> <CRLF> */
  {"HELP", ftpd_command_help, FTPD_CMDFLAG_LOGIN}, /* HELP [<SP> <string>] <CRLF> */
#if 0
  {"SMNT", ftpd_command_smnt, FTPD_CMDFLAG_LOGIN}, /* SMNT <SP> <pathname> <CRLF> */
  {"REIN", ftpd_command_rein, FTPD_CMDFLAG_LOGIN}, /* REIN <CRLF> */
  {"STOU", ftpd_command_stou, FTPD_CMDFLAG_LOGIN}, /* STOU <CRLF> */
  {"STAT", ftpd_command_stat, FTPD_CMDFLAG_LOGIN}, /* STAT [<SP> <pathname>] <CRLF> */
  {"ALLO", ftpd_command_stat, FTPD_CMDFLAG_LOGIN}, /* ALLO <SP> <decimal-integer> [<SP> R <SP> <decimal-integer>] <CRLF> */
#endif
  {NULL, (ftpd_cmdhandler_t)0, 0}
};

static const char g_cdup[]      = "..";
static const char g_respfmt1[]  = "%03u%c%s\r\n";   /* Integer, character, string */
static const char g_respfmt2[]  = "%03u%c%s%s\r\n"; /* Integer, character, two strings */

static const char *g_monthtab[] =
{
  "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};

static const char *g_ftpdhelp[] =
{
  "The following commands are recognized (* =>'s unimplemented):",
  "CWD     XCWD    CDUP    XCUP    SMNT*   QUIT    PORT    PASV",
  "EPRT*   EPSV*   ALLO*   RNFR    RNTO    DELE    MDTM    RMD",
  "XRMD    MKD     XMKD    PWD     XPWD    SIZE    SYST    HELP",
  "NOOP    FEAT*   OPTS    AUTH*   CCC*    CONF*   ENC*    MIC*",
  "PBSZ*   PROT*   TYPE    STRU*   MODE*   RETR    STOR    STOU*",
  "APPE    REST    ABOR    USER    PASS    ACCT*   REIN*   LIST",
  "NLST    STAT*   SITE*   MLSD*   MLST*",
  "Direct comments to " CONFIG_FTPD_VENDORID,
   NULL
};

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Account Functions
 ****************************************************************************/
/****************************************************************************
 * Name: ftpd_account_new
 ****************************************************************************/

static FAR struct ftpd_account_s *ftpd_account_new(FAR const char *user,
                                                   uint8_t accountflags)
{
  FAR struct ftpd_account_s *ret;
  size_t usersize;
  size_t allocsize;

  /* Get the size of the allocation */

  allocsize = sizeof(struct ftpd_account_s);
  if (!user)
    {
      usersize = 0;
    }
  else
    {
      usersize = strlen(user);
      allocsize += usersize + 1;
    }

  /* Allocate the account and user string */

  ret = (struct ftpd_account_s *)zalloc(allocsize);
  if (!ret)
    {
      nerr("ERROR: Failed to allocate account\n");
      return NULL;
    }

  /* Initialize the account and user string */

  ret->flags = accountflags;

  if (user)
    {
      ret->user = (FAR char *)&ret[1];
      strcpy(ret->user, user);
    }

  return ret;
}

/****************************************************************************
 * Name: ftpd_account_free
 ****************************************************************************/

static void ftpd_account_free(FAR struct ftpd_account_s *account)
{
  struct ftpd_account_s *prev;
  DEBUGASSERT(account);

  /* Back up to the first entry in the list */

  while (account->blink)
    {
      account = account->blink;
    }

  /* Then free the entire list */

  while (account)
    {
      prev    = account;
      account = account->flink;

      /* Free the home path and the password */

      if (prev->home)
        {
          free(prev->home);
        }

      if (prev->password)
        {
          free(prev->password);
        }

      /* Then free the container itself */

      free(prev);
    }
}

/****************************************************************************
 * Name: ftpd_account_setpassword
 ****************************************************************************/

static int ftpd_account_setpassword(FAR struct ftpd_account_s *account,
                                   FAR const char *passwd)
{
  FAR char *temp;
  DEBUGASSERT(account);

  /* Make of copy of the password string (if it is non-null) */

  temp = NULL;
  if (passwd)
    {
      temp = strdup(passwd);
      if (!temp)
        {
          return -ENOMEM;
        }
    }

  /* Free any existing password string */

  if (account->password)
    {
      free(account->password);
    }

  /* Set the new password */

  account->password = temp;
  return OK;
}

/****************************************************************************
 * Name: ftpd_account_add
 ****************************************************************************/

static int ftpd_account_add(FAR struct ftpd_server_s *server,
                            FAR struct ftpd_account_s *account)
{
  FAR struct ftpd_account_s *head;
  FAR struct ftpd_account_s *tail;
  DEBUGASSERT(server && account);

  /* Find the beginning of the list */

  head = account;
  while (head->blink)
    {
      head = head->blink;
    }

  /* Find the tail of the list */

  tail = account;
  while (tail->flink)
    {
      tail = tail->flink;
    }

  /* Handle the case where the list is empty */

  if (!server->head)
    {
      server->head = head;
    }
  else
    {
      head->blink = server->tail;
      server->tail->flink = head;
    }

  server->tail = tail;
  return OK;
}

/****************************************************************************
 * Name: ftpd_account_sethome
 ****************************************************************************/

static int ftpd_account_sethome(FAR struct ftpd_account_s *account,
                                FAR const char *home)
{
  FAR char *temp;

  DEBUGASSERT(account);

  /* Make a copy of the home path string (unless it is NULL) */

  temp = NULL;
  if (home)
    {
      temp = strdup(home);
      if (!temp)
        {
          return -ENOMEM;
        }
    }

  /* Free any existing home path string */

  if (account->home)
    {
      free(account->home);
    }

  /* And set the new home path string */

  account->home = temp;
  return OK;
}

/****************************************************************************
 * Name: ftpd_account_search_user
 ****************************************************************************/

static FAR struct ftpd_account_s *
ftpd_account_search_user(FAR struct ftpd_session_s *session,
                         FAR const char *user)
{
  FAR struct ftpd_account_s *newaccount = NULL;
  FAR struct ftpd_account_s *account;
  uint8_t accountflags;

  account = session->head;
  while (account)
    {
      accountflags = account->flags;

      /* Check if the account has a user */

      if (!account->user)
        {
          /* No.. The account has no user, was a user name provided? */

          if (!user)
            {
              /* Yes.. create the account */

              newaccount = ftpd_account_new(NULL, accountflags);
              if (newaccount)
                {
                  if (ftpd_account_setpassword(newaccount, account->password) < 0)
                    {
                      ftpd_account_free(newaccount);
                      newaccount = NULL;
                    }
                  else if (ftpd_account_sethome(newaccount, account->home) < 0)
                    {
                      ftpd_account_free(newaccount);
                      newaccount = NULL;
                    }
                }
              break;
            }
        }

      /* Was a user name provided? */

      else if (user)
        {
          /* Check if matches the user name on the account */

          if (strcmp(user, (FAR const char *)account->user) == 0)
            {
              /* Yes.. create the account */

              newaccount = ftpd_account_new(account->user, accountflags);
              if (newaccount)
                {
                  if (ftpd_account_setpassword(newaccount, account->password) != 0)
                    {
                      ftpd_account_free(newaccount);
                      newaccount = NULL;
                    }
                  else if (ftpd_account_sethome(newaccount, account->home) != 0)
                    {
                      ftpd_account_free(newaccount);
                      newaccount = NULL;
                    }
                }
              break;
            }
        }

      /* Try the next account */

      account = account->flink;
    }

  return newaccount;
}

/****************************************************************************
 * Name: ftpd_account_login
 ****************************************************************************/

static FAR struct ftpd_account_s *
ftpd_account_login(FAR struct ftpd_session_s *session,
                   FAR const char *user, FAR const char *passwd)
{
  FAR struct ftpd_account_s *account;
  bool pwvalid;
  FAR char *home;

  account = ftpd_account_search_user(session, user);
  if (!account)
    {
      return NULL;
    }

  if (!account->password)
    {
      if (!passwd)
        {
          pwvalid = true;
        }
      else if (passwd[0] == '\0')
        {
          pwvalid = true;
        }
      else
        {
          pwvalid = false;
        }
    }
  else if (!passwd)
    {
      pwvalid = false;
    }
  else if (strcmp(passwd, (FAR const char *)account->password) == 0)
    {
      pwvalid = true;
    }
  else
    {
      pwvalid = false;
    }

  if (!pwvalid)
    {
      ftpd_account_free(account);
      return NULL;
    }

  home = account->home;
  if (!home)
    {
      home = getenv("HOME");
    }

  if ((account->flags & FTPD_ACCOUNTFLAG_ADMIN) != 0)
    {
      /* admin user */

      session->home = strdup("/");
      session->work = strdup(!home ? "/" : home);
    }
  else
      {
        /* normal user */

        session->home = strdup(!home ? "/" : home);
        session->work = strdup("/");
      }

  ftpd_account_free(account);
  return account;
}

/****************************************************************************
 * Parsing Functions
 ****************************************************************************/
/****************************************************************************
 * Name: ftpd_strtok
 ****************************************************************************/

static FAR char *ftpd_strtok(bool skipspace, FAR const char *delimiters,
                             FAR char **str)
{
  FAR const char *dptr;
  FAR char *sptr;
  FAR char *ret;

  sptr = *str;

  /* Skip any leading spaces */

  if (skipspace)
    {
      while (isspace(*sptr))
        {
          sptr++;
        }
    }

  ret = sptr;

  /* The following is an implementation of strtok.  It does not modify the
   * original string as strtok does, however.
   */

  while (*sptr != '\0')
    {
      dptr = delimiters;
      while (*sptr != *dptr && *dptr != '\0')
        {
          dptr++;
        }

      if (*sptr == *dptr)
        {
          break;
        }

      sptr++;
    }

    /* Save the place where we will resuming searching */

    *str = sptr;
    return ret;
}

/****************************************************************************
 * Name: ftpd_strtok_alloc
 ****************************************************************************/

static FAR char *ftpd_strtok_alloc(bool skipspace, FAR const char *delimiters,
                                   FAR const char **str)
{
  FAR const char *sptr;
  FAR const char *left;
  FAR const char *right;
  FAR const char *dptr;
  FAR char *ret;
  size_t tokenlen;

  sptr = *str;

  if (skipspace)
    {
      while (isspace(*sptr))
        {
          sptr++;
        }
    }

  right = sptr;
  left  = sptr;

  /* The the following logic is similar to strtok(), but only bounds the
   * token of interest between left (the first character of the substring)
   * and right (the character after the end of the substring).
   */

  while (*sptr != '\0')
    {
      dptr = delimiters;
      while (*sptr != *dptr && *dptr != '\0')
        {
          dptr++;
        }

      if (*sptr == *dptr)
        {
          break;
        }

      sptr++;

      /* Adjust the right pointer but ignoring any trailing spaces if
       * 'skipspace' is selected.
       */

      if (!skipspace || !isspace(*sptr))
        {
          right = sptr;
        }
    }

    /* Allocate memory large enough to hold the entire sub-string (including
     * the NUL terminator.
     */

    tokenlen = (size_t)(right - left);
    ret = (FAR char *)malloc(tokenlen + 1);
    if (ret)
      {
        if (tokenlen > 0)
          {
            memcpy(ret, left, tokenlen);
          }
        ret[tokenlen] = '\0';
      }

    /* Save the place where we will resuming searching */

    *str = sptr;
    return ret;
}

/****************************************************************************
 * Socket Helpers
 ****************************************************************************/
/****************************************************************************
 * Name: ftpd_rxpoll
 ****************************************************************************/

static int ftpd_rxpoll(int sd, int timeout)
{
  struct pollfd fds[1];
  int ret;

  /* Set up the poll */

  fds[0].fd      = sd;
  fds[0].events  = POLLIN;
  fds[0].revents = 0;

  /* Perform the poll. */

  ret = poll(fds, 1, timeout);

  /* Handle the result of the poll.  On success, poll returns the number
   * of structures that have nonzero revents fields.  A value of 0 indicates
   * that the call timed out and no file descriptors were ready.  On error,
   * -1 is returned, and errno is set appropriately:
   */

  if (ret == 0)
    {
      //ninfo("poll() timed out\n");
      return -ETIMEDOUT;
    }
  else if (ret < 0)
    {
      int errval = errno;
      ninfo("poll() failed: %d\n", errval);
      return -errval;
    }
  else
    {
      return OK;
    }
}

/****************************************************************************
 * Name: ftpd_txpoll
 ****************************************************************************/

static int ftpd_txpoll(int sd, int timeout)
{
  struct pollfd fds[1];
  int ret;

  /* Set up the poll */

  fds[0].fd      = sd;
  fds[0].events  = POLLOUT;
  fds[0].revents = 0;

  /* Perform the poll. */

  ret = poll(fds, 1, timeout);

  /* Handle the result of the poll.  On success, poll returns the number
   * of structures that have nonzero revents fields.  A value of 0 indicates
   * that the call timed out and no file descriptors were ready.  On error,
   * -1 is returned, and errno is set appropriately:
   */

  if (ret == 0)
    {
      ninfo("poll() timed out\n");
      return -ETIMEDOUT;
    }
  else if (ret < 0)
    {
      int errval = errno;
      ninfo("poll() failed: %d\n", errval);
      return -errval;
    }
  else
    {
      return OK;
    }
}

/****************************************************************************
 * Name: ftpd_accept
 ****************************************************************************/

static int ftpd_accept(int sd, FAR void *addr, FAR socklen_t *addrlen,
                       int timeout)
{
  int acceptsd;
  int ret;

  /* Handle any requested timeout */

  if (timeout >= 0)
    {
      ret = ftpd_rxpoll(sd, timeout);
      if (ret < 0)
        {
          /* Only report interesting, infrequent errors (not the common timeout) */

#ifdef CONFIG_DEBUG_NET
          if (ret != -ETIMEDOUT)
            {
              nerr("ERROR: ftpd_rxpoll() failed: %d\n", ret);
            }
#endif
          return ret;
        }
    }

  /* Accept the connection -- waiting if necessary */

  acceptsd = accept(sd, (FAR struct sockaddr *)addr, addrlen);
  if (acceptsd < 0)
    {
      int errval = errno;
      nerr("ERROR: accept() failed: %d\n", errval);
      return -errval;
    }

  return acceptsd;
}

/****************************************************************************
 * Name: ftpd_recv
 ****************************************************************************/

static ssize_t ftpd_recv(int sd, FAR void *data, size_t size, int timeout)
{
  ssize_t ret;
  int status;

  /* Handle any requested timetout */

  if (timeout >= 0)
    {
      status = ftpd_rxpoll(sd, timeout);
      if (status < 0)
        {
          ninfo("ftpd_rxpoll: %d\n", status);
          return (ssize_t)status;
        }
    }

  /* Receive the data... waiting if necessary.  The client side will break the
   * connection after the file has been sent.  Zero (end-of-file) should be
   * received in this case.
   */

  ret = recv(sd, data, size, 0);
  if (ret < 0)
    {
      int errval = errno;

      nerr("ERROR: recv() failed: %d\n", errval);
      return -errval;
    }

  return ret;
}

/****************************************************************************
 * Name: ftpd_send
 ****************************************************************************/

static ssize_t ftpd_send(int sd, FAR const void *data, size_t size, int timeout)
{
  ssize_t ret;

  /* Handle any requested timetout */

  if (timeout >= 0)
    {
      int status = ftpd_txpoll(sd, timeout);
      if (status < 0)
        {
          ninfo("ftpd_rxpoll: %d\n", status);
          return (ssize_t)status;
        }
    }

  /* Then send the data (waiting if necessary) */

  ret = send(sd, data, size, 0);
  if (ret < 0)
    {
      ssize_t errval = errno;
      nerr("ERROR: send() failed: %d\n", errval);
      return -errval;
    }

  return ret;
}

/****************************************************************************
 * Name: ftpd_response
 ****************************************************************************/

static ssize_t ftpd_response(int sd, int timeout, FAR const char *fmt, ...)
{
  FAR char *buffer;
  ssize_t bytessent;
  va_list ap;

  va_start(ap, fmt);
  vasprintf(&buffer, fmt, ap);
  va_end(ap);

  if (!buffer)
    {
      return -ENOMEM;
    }

  bytessent = ftpd_send(sd, buffer, strlen(buffer), timeout);
  free(buffer);

  return bytessent;
}

/****************************************************************************
 * Name: ftpd_dataopen
 ****************************************************************************/

static int ftpd_dataopen(FAR struct ftpd_session_s *session)
{
  int sd;
  int ret;

  if (session->data.sd < 0)
    {
      /* PORT session */

#ifdef CONFIG_NET_IPv6
      if (session->data.addr.ss.ss_family == AF_INET6)
        {
          session->data.sd = socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP);
        }
      else
        {
          session->data.sd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
        }
#else
      session->data.sd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
#endif

      if (session->data.sd < 0)
        {
          int errval = errno;
          nerr("ERROR: socket() failed: %d\n", errval);
          (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;
          nerr("ERROR: connect() failed: %d\n", errval);
          (void)ftpd_response(session->cmd.sd, session->txtimeout,
                              g_respfmt1, 451, ' ', "Connect error !");
          (void)ftpd_dataclose(session);
          return -errval;
        }

#ifdef CONFIG_NET_SOLINGER
        {
          struct linger ling;

          (void)memset(&ling, 0, sizeof(ling));
          ling.l_onoff = 1;
          ling.l_linger = 4;
          (void)setsockopt(session->data.sd, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling));
        }
#endif

      return OK;
    }

  /* PASV session */

  session->data.addrlen = sizeof(session->data.addr);
  sd = ftpd_accept(session->data.sd, (struct sockaddr *)(&session->data.addr),
                  &session->data.addrlen, -1);
  if (sd < 0)
    {
      nerr("ERROR: ftpd_accept() failed: %d\n", sd);
      (void)ftpd_response(session->cmd.sd, session->txtimeout,
                          g_respfmt1, 451, ' ', "Accept error !");
      (void)ftpd_dataclose(session);
      return sd;
    }

  close(session->data.sd);
  session->data.sd = sd;

#ifdef CONFIG_NET_SOLINGER
  {
    struct linger ling;

    (void)memset(&ling, 0, sizeof(ling));
    ling.l_onoff = 1;
    ling.l_linger = 4;
    (void)setsockopt(session->data.sd, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling));
  }
#endif

  return OK;
}

/****************************************************************************
 * Name: ftpd_dataclose
 ****************************************************************************/

static int ftpd_dataclose(FAR struct ftpd_session_s *session)
{
  if (session->data.sd >= 0)
    {
      close(session->data.sd);
      session->data.sd = -1;
    }

  return OK;
}

/****************************************************************************
 * Name: ftpd_openserver
 ****************************************************************************/

static FAR struct ftpd_server_s *ftpd_openserver(int port)
{
  FAR struct ftpd_server_s *server;
  sa_family_t family;
  socklen_t addrlen;
  FAR const void *addr;
#if defined(SOMAXCONN)
  int backlog = SOMAXCONN;
#else
  int backlog = 5;
#endif
  int ret;

  /* Allocate the server instance */

  server = (FAR struct ftpd_server_s *)zalloc(sizeof(struct ftpd_server_s));
  if (!server)
    {
      nerr("ERROR: Failed to allocate server\n");
      return NULL;
    }

  /* Initialize the server instance */

    server->sd   = -1;
    server->head = NULL;
    server->tail = NULL;

  /* Create the server listen socket */

#ifdef CONFIG_NET_IPv6
  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;
      nerr("ERROR: Failed to open %s: %d\n", filename, errval);
      return -errval;
    }

  ret  = 0;
  temp = 0;

  if (offset == (off_t)(-1))
    {
     for (;;)
        {
          ch = getc(outstream);
          if (ch == EOF)
            {
              break;
            }
          ret++;
          if (ch == '\n')
            {
              ret++;
            }
        }
        /* ret is ascii mode size */
    }
  else
    {
      while (offset < temp)
        {
          ch = getc(outstream);
          if (ch == EOF)
            {
              ret = -errno;
              break;
            }

          ret++;
          temp++;

          if (ch == '\n')
            {
              temp++;
            }
        }

      /* ret is binary mode offset */
    }

  (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)
            {
              nerr("ERROR: ftpd_offsatoi failed: %d\n", seekpos);
              errval = -seekpos;
            }
        }
      else
        {
          seekpos = session->restartpos;
          if (seekpos < 0)
            {
              nerr("ERROR: 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;
              nerr("ERROR: lseek failed: %d\n", errval);
            }
        }

      /* Report errors.  If an error occurred, seekoffs will be negative and
       * errval will hold the (positive) error code.
       */

      if (seekoffs < 0)
        {
          (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)
    {
      nerr("ERROR: ftpd_response failed: %d\n", ret);
      goto errout_with_session;
    }

  for (;;)
    {
      /* Read from the source (file or TCP connection) */

      if (session->type == FTPD_SESSIONTYPE_A)
        {
          buffer   = &session->data.buffer[session->data.buflen >> 2];
          wantsize = session->data.buflen >> 2;
        }
      else
        {
          buffer   = session->data.buffer;
          wantsize = session->data.buflen;
        }

      if (cmdtype == 0)
        {
          /* Read from the file.  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)
        {
          nerr("ERROR: 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;
              nerr("ERROR: ftpd_send failed: %d\n", errval);
            }
        }
      else
        {
          /* Write to the file */

          wrbytes = write(session->fd, buffer, buflen);
          if (wrbytes < 0)
            {
              errval = errno;
              nerr("ERROR: 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))
        {
          nerr("ERROR: Write failed: wrbytes=%d errval=%d\n", wrbytes, errval);
          (void)ftpd_response(session->cmd.sd, session->txtimeout,
                              g_respfmt1, 550, ' ', "Data send error !");
           ret = -errval;
           break;
        }

      /* Get the next file offset */

      pos += (off_t)wrbytes;
    }

errout_with_session:;
    close(session->fd);
    session->fd = -1;

    if (isnew && ret < 0)
      {
        (void)unlink(path);
      }

errout_with_data:;
    (void)ftpd_dataclose(session);

errout_with_path:
    free(abspath);

errout:
    return ret;
}

/****************************************************************************
 * Name: ftpd_listoption
 ****************************************************************************/

static uint8_t ftpd_listoption(FAR char **param)
{
  FAR char *ptr = *param;
  uint8_t ret = 0;

  while (*ptr == '-')
    {
      while (*ptr != '\0' && !isspace(*ptr))
        {
          switch (*ptr)
            {
              case 'a':
              case 'A':
                ret |= FTPD_LISTOPTION_A;
                break;

              case 'l':
              case 'L':
                ret |= FTPD_LISTOPTION_L;
                break;

              case 'f':
              case 'F':
                ret |= FTPD_LISTOPTION_F;
                break;

              case 'r':
              case 'R':
                ret |= FTPD_LISTOPTION_R;
                break;

              default:
                ret |= FTPD_LISTOPTION_UNKNOWN;
                break;
            }

          ptr++;
        }

      if (*ptr != '\0')
        {
          while (*ptr != '\0' && isspace(*ptr))
            {
              ptr++;
            }
        }
    }

  *param = ptr;
  return ret;
}

/****************************************************************************
 * Name: fptd_listscan
 ****************************************************************************/

static int ftpd_listbuffer(FAR struct ftpd_session_s *session, FAR char *path,
                           FAR struct stat *st, FAR char *buffer,
                           size_t buflen, unsigned int opton)
{
  FAR char *name;
  size_t offset = 0;

  name = basename(path);

  if ((opton & FTPD_LISTOPTION_L) != 0)
    {
      FAR const char *str;
      struct tm tm;
      time_t now;

      if (S_ISREG(st->st_mode) != 0)
        {
          str = "-";
        }
      else if (S_ISDIR(st->st_mode) != 0)
        {
          str = "d";
        }
      else if (S_ISCHR(st->st_mode) != 0)
        {
          str = "c";
        }
      else if (S_ISBLK(st->st_mode) != 0)
        {
          str = "b";
        }
      else if (S_ISFIFO(st->st_mode) != 0)
        {
          str = "p";
        }
      else if (S_ISLNK(st->st_mode) != 0)
        {
          str = "l";
        }
      else if (S_ISSOCK(st->st_mode) != 0)
        {
          str = "s";
        }
      else
        {
          str = "-";
        }

      offset += snprintf(&buffer[offset], buflen - offset, "%s", str);

      /* User */

      str = ((st->st_mode & S_IRUSR) != 0) ? "r" : "-";
      offset += snprintf(&buffer[offset], buflen - offset, "%s", str);

      str = ((st->st_mode & S_IWUSR) != 0) ? "w" : "-";
      offset += snprintf(&buffer[offset], buflen - offset, "%s", str);

      if ((st->st_mode & S_ISUID) != 0 && (st->st_mode & S_IXUSR) != 0)
        {
          str = "s";
        }
      else if ((st->st_mode & S_ISUID) != 0)
        {
          str = "S";
        }
      else if ((st->st_mode & S_IXUSR) != 0)
        {
          str = "x";
        }
      else
        {
          str = "-";
        }

      offset += snprintf(&buffer[offset], buflen - offset, "%s", str);

      /* group */

      str = ((st->st_mode & S_IRGRP) != 0) ? "r" : "-";
      offset += snprintf(&buffer[offset], buflen - offset, "%s", str);

      str = ((st->st_mode & S_IWGRP) != 0) ? "w" : "-";
      offset += snprintf(&buffer[offset], buflen - offset, "%s", str);

      if ((st->st_mode & S_ISGID) != 0 && (st->st_mode & S_IXGRP) != 0)
        {
          str = "s";
        }
      else if ((st->st_mode & S_ISGID) != 0)
        {
          str = "S";
        }
      else if ((st->st_mode & S_IXGRP) != 0)
        {
          str = "x";
        }
      else
        {
          str = "-";
        }

      offset += snprintf(&buffer[offset], buflen - offset, "%s", str);

      /* other */

      str = ((st->st_mode & S_IROTH) != 0) ? "r" : "-";
      offset += snprintf(&buffer[offset], buflen - offset, "%s", str);

      str = ((st->st_mode & S_IWOTH) != 0) ? "w" : "-";
      offset += snprintf(&buffer[offset], buflen - offset, "%s", str);

      if ((st->st_mode & S_ISVTX) != 0 && (st->st_mode & S_IXOTH) != 0)
        {
          str = "t";
        }
      else if ((st->st_mode & S_ISVTX) != 0)
        {
          str = "T";
        }
      else if ((st->st_mode & S_IXOTH) != 0)
        {
          str = "x";
        }
      else
        {
          str = "-";
        }

      offset += snprintf(&buffer[offset], buflen - offset, "%s", str);

#ifdef __NUTTX__
      /* Fake nlink, user id, and group id */

      offset += snprintf(&buffer[offset], buflen - offset, "%4u %8u %8u", 1, 1001, 512);
#else
      /* nlink */

      offset += snprintf(&buffer[offset], buflen - offset, "%4u", st->st_nlink);

      /* user id */

      offset += snprintf(&buffer[offset], buflen - offset, " %8u", st->st_uid);

      /* group id */

      offset += snprintf(&buffer[offset], buflen - offset, " %8u", st->st_gid);
#endif

      /* size */

      offset += snprintf(&buffer[offset], buflen - offset, " %8u", st->st_size);

      /* time */

      memcpy(&tm, localtime((FAR const time_t *)&st->st_mtime), sizeof(tm));
      offset += snprintf(&buffer[offset], buflen - offset, " %s %2u",
                         g_monthtab[tm.tm_mon], tm.tm_mday);
      now = time(0);
      if ((now - st->st_mtime) > (time_t)(60 * 60 * 24 * 180))
        {
          offset += snprintf(&buffer[offset], buflen - offset, " %5u",
                             tm.tm_year + 1900);
        }
      else
        {
          offset += snprintf(&buffer[offset], buflen - offset, " %02u:%02u",
                             tm.tm_hour, tm.tm_min);
        }

      /* basename */

      offset += snprintf(&buffer[offset], buflen - offset, " %s", name);

      /* linkname */

#ifndef __NUTTX__
      if (S_ISLNK(st->st_mode) != 0)
        {
          FAR char *temp;
          int namelen;

          temp = (FAR char *)malloc(PATH_MAX + 1);
          if (temp)
            {
              namelen = readlink(path, temp, PATH_MAX);
              if (namelen != (-1))\
                {
                  temp[namelen] = '\0';
                }

              offset += snprintf(&buffer[offset], buflen - offset, " -> %s", temp);
              free(temp);
            }
        }
#endif

      /* end */

      offset += snprintf(&buffer[offset], buflen - offset, "\r\n");
    }
  else
    {
      /* basename */

      offset += snprintf(&buffer[offset], buflen - offset, "%s\r\n", name);
    }

  return 0;
}

/****************************************************************************
 * Name: fptd_listscan
 ****************************************************************************/

static int fptd_listscan(FAR struct ftpd_session_s *session, FAR char *path,
                         unsigned int opton)
{
  FAR char *temp;
  DIR *dir;
  struct dirent *entry;
  struct stat st;
  int ret;

  ret = stat(path, &st);
  if (ret < 0)
    {
      return -errno;
    }

  if (!S_ISDIR(st.st_mode))
    {
      ret = ftpd_listbuffer(session, path, &st, session->data.buffer,
                            session->data.buflen, opton);
      if (ret == 0)
        {
          ret = ftpd_response(session->data.sd, session->txtimeout,
                              "%s", (FAR char *)session->data.buffer);
        }

      return ret;
    }

  dir = opendir(path);
  if (!dir)
    {
      int errval = errno;
      nerr("ERROR: 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)
            {
              nerr("ERROR: ftpd_response failed: %d\n", ret);
              return ret;
            }
        }

      value[index++] = (uint8_t)temp;
    }

  if (index < 6)
    {
      return ftpd_response(session->cmd.sd, session->txtimeout,
                           g_respfmt1, 501, ' ', "Illegal PORT command");
    }

  (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
    {
      nerr("ERROR: 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
    {
      nerr("ERROR: Unsupported family\n");
    }

  session->data.addr.in4.sin_port = 0;
  ret = bind(session->data.sd, (FAR const struct sockaddr *)&session->data.addr,
             session->data.addrlen);
  if (ret < 0)
    {
      (void)ftpd_dataclose(session);
      return ftpd_response(session->cmd.sd, session->txtimeout,
                           g_respfmt1, 425, ' ', "PASV bind fail !");
    }

  ret = getsockname(session->data.sd, (FAR struct sockaddr *)&session->data.addr,
                    &session->data.addrlen);
  if (ret < 0)
    {
      (void)ftpd_dataclose(session);
      return ftpd_response(session->cmd.sd, session->txtimeout,
                           g_respfmt1, 425, ' ', "PASV getsockname fail !");
    }

  ret = listen(session->data.sd, 1);
  if (ret < 0)
    {
      (void)ftpd_dataclose(session);
      return ftpd_response(session->cmd.sd, session->txtimeout,
                           g_respfmt1, 425, ' ', "PASV listen fail !");
    }

  if (ntohl(session->data.addr.in4.sin_addr.s_addr) == INADDR_ANY)
    {
      (void)ftpd_dataclose(session);
      return ftpd_response(session->cmd.sd, session->txtimeout,
                           g_respfmt1, 425, ' ',
                           "Can not open passive connection");
    }

  temp = ntohl(session->data.addr.in4.sin_addr.s_addr);
  value[0] = (temp >> 24) & 0xff;
  value[1] = (temp >> 16) & 0xff;
  value[2] = (temp >> 8) & 0xff;
  value[3] = (temp) & 0xff;

  temp = (unsigned int)ntohs(session->data.addr.in4.sin_port);
  value[4] = (temp >> 8) & 0xff;
  value[5] = (temp) & 0xff;

  ret = ftpd_response(session->cmd.sd, session->txtimeout,
                      "%03u%cEntering passive mode (%u,%u,%u,%u,%u,%u).\r\n",
                      227, ' ',
                      value[0], value[1], value[2],
                      value[3], value[4], value[5]);
  if (ret < 0)
    {
      (void)ftpd_dataclose(session);
    }

  return ret;
}

/****************************************************************************
 * Name: ftpd_command_epsv
 ****************************************************************************/

static int ftpd_command_epsv(FAR struct ftpd_session_s *session)
{
  int ret;

  (void)ftpd_dataclose(session);

  session->data.addrlen = sizeof(session->data.addr);

#ifdef CONFIG_NET_IPv6
  session->data.sd = socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP);
  if (session->data.sd < 0)
    {
      session->data.sd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    }
  else
    {
#if defined(IPPROTO_IPV6) && defined(IPV6_V6ONLY)
      int ipv6only = 0;
      (void)setsockopt(session->data.sd, IPPROTO_IPV6, IPV6_V6ONLY, &ipv6only, sizeof(ipv6only));
#endif
    }
#else
  session->data.sd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
#endif

  if (session->data.sd < 0)
    {
      ret = ftpd_response(session->cmd.sd, session->txtimeout,
                          g_respfmt1, 500, ' ', "EPSV socket create fail !");
      (void)ftpd_dataclose(session);
      return ret;
    }

  ret = getsockname(session->cmd.sd, (FAR struct sockaddr *)&session->data.addr,
                   &session->data.addrlen);
  if (ret < 0)
    {
      ret = ftpd_response(session->cmd.sd, session->txtimeout,
                          g_respfmt1, 500, ' ', "EPSV getsockname fail !");
      (void)ftpd_dataclose(session);
      return ret;
    }

#ifdef CONFIG_NET_IPv6
  if (session->data.addr.ss.ss_family == AF_INET6)
    {
      session->data.addr.in6.sin6_port = htons(0);
    }
  else if (session->data.addr.ss.ss_family == AF_INET)
    {
      session->data.addr.in4.sin_port = htons(0);
    }
#else
  session->data.addr.in4.sin_port = htons(0);
#endif

  ret = bind(session->data.sd, (FAR const struct sockaddr *)&session->data.addr,
             session->data.addrlen);
  if (ret < 0)
    {
      ret = ftpd_response(session->cmd.sd, session->txtimeout,
                          g_respfmt1, 500, ' ', "EPSV bind fail !");
      (void)ftpd_dataclose(session);
      return ret;
    }

  ret = getsockname(session->data.sd, (FAR struct sockaddr *)&session->data.addr,
                    &session->data.addrlen);
  if (ret < 0)
    {
      ret = ftpd_response(session->cmd.sd, session->txtimeout,
                          g_respfmt1, 500, ' ', "EPSV getsockname fail !");
      (void)ftpd_dataclose(session);
      return ret;
    }

  ret = listen(session->data.sd, 1);
  if (ret < 0)
    {
      ret = ftpd_response(session->cmd.sd, session->txtimeout,
            g_respfmt1, 500, ' ', "EPSV listen fail !");
      (void)ftpd_dataclose(session);
      return ret;
    }

#ifdef CONFIG_NET_IPv6
  if (session->data.addr.ss.ss_family == AF_INET6)
    {
      ret = ftpd_response(session->cmd.sd, session->txtimeout,
                          "%03u%cEntering Extended Passive Mode (|||%u|).\r\n",
                          229, ' ',
                          ntohs(session->data.addr.in6.sin6_port));
      if (ret < 0)
        {
          (void)ftpd_dataclose(session);
          return ret;
        }
    }
  else
#else
  if (session->data.addr.ss.ss_family == AF_INET)
    {
      ret = ftpd_response(session->cmd.sd, session->txtimeout,
                          "%03u%cEntering Extended Passive Mode (|%u||%u|).\r\n",
                          229, ' ', 1,
                          ntohs(session->data.addr.in4.sin_port));
      if (ret < 0)
        {
          (void)ftpd_dataclose(session);
          return ret;
        }
    }
  else
#endif
    {
      ret = ftpd_response(session->cmd.sd, session->txtimeout,
                          g_respfmt1, 502, ' ',
                          "EPSV command not implemented !");
    }

  return ret;
}

/****************************************************************************
 * Name: ftpd_command_list
 ****************************************************************************/

static int ftpd_command_list(FAR struct ftpd_session_s *session)
{
  uint8_t opton = FTPD_LISTOPTION_L;
  int ret;

  ret = ftpd_dataopen(session);
  if (ret < 0)
    {
      return 0;
    }

  ret = ftpd_response(session->cmd.sd, session->txtimeout,
                      g_respfmt1, 150, ' ',
                      "Opening ASCII mode data connection for file list");
  if (ret < 0)
    {
      (void)ftpd_dataclose(session);
      return ret;
    }

  opton |= ftpd_listoption((char **)(&session->param));
  (void)ftpd_list(session, opton);

  ret = ftpd_response(session->cmd.sd, session->txtimeout,
                      g_respfmt1, 226, ' ', "Transfer complete");

  (void)ftpd_dataclose(session);
  return ret;
}

/****************************************************************************
 * Name: ftpd_command_nlst
 ****************************************************************************/

static int ftpd_command_nlst(FAR struct ftpd_session_s *session)
{
  uint8_t opton = 0;
  int ret;

  ret = ftpd_dataopen(session);
  if (ret < 0)
    {
      return 0;
    }

  ret = ftpd_response(session->cmd.sd, session->txtimeout,
                      g_respfmt1, 150, ' ',
                      "Opening ASCII mode data connection for file list");
  if (ret < 0)
    {
      (void)ftpd_dataclose(session);
      return ret;
    }

  opton |= ftpd_listoption((char **)(&session->param));
  (void)ftpd_list(session, opton);

  ret = ftpd_response(session->cmd.sd, session->txtimeout,
                      g_respfmt1, 226, ' ', "Transfer complete");

  (void)ftpd_dataclose(session);
  return ret;
}

/****************************************************************************
 * Name: ftpd_command_acct
 ****************************************************************************/

static int ftpd_command_acct(FAR struct ftpd_session_s *session)
{
  return ftpd_response(session->cmd.sd, session->txtimeout,
                       g_respfmt1, 502, ' ', "ACCT command not implemented !");
}

/****************************************************************************
 * Name: ftpd_command_size
 ****************************************************************************/

static int ftpd_command_size(FAR struct ftpd_session_s *session)
{
  FAR char *abspath;
  FAR char *path;
  struct stat st;
  FAR FILE *outstream;
  off_t offset;
  int ch;
  int status;
  int ret;

  ret = ftpd_getpath(session, session->param, &abspath, NULL);
  if (ret < 0)
    {
      return ftpd_response(session->cmd.sd, session->txtimeout,
                           g_respfmt1, 550, ' ', "Unknown size !");
    }
  path = abspath;

  ret = 0;
  switch (session->type)
    {
    case FTPD_SESSIONTYPE_NONE:
    case FTPD_SESSIONTYPE_L8:
    case FTPD_SESSIONTYPE_I:
      {
        status = stat(path, &st);
        if (status < 0)
          {
            ret = ftpd_response(session->cmd.sd, session->txtimeout,
                                g_respfmt2, 550, ' ', session->param,
                                ": not a regular file.");
          }
        else if (!S_ISREG(st.st_mode))
          {
             ret = ftpd_response(session->cmd.sd, session->txtimeout,
                                 g_respfmt2, 550, ' ', session->param,
                                 ": not a regular file.");
          }
        else
          {
            ret = ftpd_response(session->cmd.sd, session->txtimeout,
                                "%03u%c%llu\r\n", 213, ' ', (unsigned long long)st.st_size);
          }
      }
      break;

    case FTPD_SESSIONTYPE_A:
      {
        status = stat(path, &st);
        if (status < 0)
          {
            ret = ftpd_response(session->cmd.sd, session->txtimeout,
                                g_respfmt2, 550, ' ', session->param,
                                ": not a regular file.");
            if (ret < 0)
              {
                return ret;
              }
          }
        else if (!S_ISREG(st.st_mode))
          {
            ret = ftpd_response(session->cmd.sd, session->txtimeout,
                                g_respfmt2, 550, ' ', session->param,
                                ": not a regular file.");
            if (ret < 0)
              {
                return ret;
              }
          }


        outstream = fopen(path, "r");
        if (!outstream)
          {
            ret = ftpd_response(session->cmd.sd, session->txtimeout,
                                g_respfmt2, 550, ' ', session->param,
                                ": Can not open file !");
            if (ret < 0)
              {
                return ret;
              }
           }

        offset = 0;
        for (;;)
          {
            ch = getc(outstream);
            if (ch == EOF)
              {
                break;
              }
            else if (ch == 'c')
              {
                offset++;
              }
            offset++;
          }

        (void)fclose(outstream);
        ret = ftpd_response(session->cmd.sd, session->txtimeout,
                            "%03u%c%llu\r\n", 213, ' ', (unsigned long long)offset);
      }
      break;

    default:
      {
        ret = ftpd_response(session->cmd.sd, session->txtimeout,
                            g_respfmt1, 504, ' ', "SIZE not implemented for type");
      }
      break;
    }

  free(abspath);
  return ret;
}

/****************************************************************************
 * Name: ftpd_command_stru
 ****************************************************************************/

static int ftpd_command_stru(FAR struct ftpd_session_s *session)
{
  return ftpd_response(session->cmd.sd, session->txtimeout,
                       g_respfmt1, 502, ' ', "STRU command not implemented !");
}

/****************************************************************************
 * Name:
 ****************************************************************************/

static int ftpd_command_rnfr(FAR struct ftpd_session_s *session)
{
  FAR char *abspath;
  FAR char *path;
  struct stat st;
  int ret;

  if (session->renamefrom)
    {
      free(session->renamefrom);
      session->renamefrom = NULL;
    }

  ret = ftpd_getpath(session, session->param, &abspath, NULL);
  if (ret < 0)
    {
      return ftpd_response(session->cmd.sd, session->txtimeout,
                           g_respfmt1, 550, ' ', "RNFR error !");
    }
  path = abspath;

  ret = stat(path, &st);
  if (ret < 0)
    {
      free(abspath);
      return ftpd_response(session->cmd.sd, session->txtimeout,
                           g_respfmt2, 550, ' ', session->param,
                           ": No such file or directory.");
    }

  session->renamefrom = abspath;
  session->flags |= FTPD_SESSIONFLAG_RENAMEFROM;

  return ftpd_response(session->cmd.sd, session->txtimeout,
                       g_respfmt1, 350, ' ', "RNFR successful");
}

/****************************************************************************
 * Name: ftpd_command_rnto
 ****************************************************************************/

static int ftpd_command_rnto(FAR struct ftpd_session_s *session)
{
  FAR char *abspath;
  int ret;

  if (!session->renamefrom)
    {
      return ftpd_response(session->cmd.sd, session->txtimeout,
                           g_respfmt1, 550, ' ', "RNTO error !");
    }

  ret = ftpd_getpath(session, session->param, &abspath, NULL);
  if (ret < 0)
    {
      return ftpd_response(session->cmd.sd, session->txtimeout,
                           g_respfmt1, 550, ' ', "RNTO error !");
    }

  ret = rename(session->renamefrom, abspath);
  if (ret < 0)
    {
      free(abspath);
      return ftpd_response(session->cmd.sd, session->txtimeout,
                           g_respfmt2, 550, ' ', session->param,
                           ": Rename error.");
    }

  free(abspath);
  return ftpd_response(session->cmd.sd, session->txtimeout,
                       g_respfmt1, 250, ' ', "Rename successful");
}

/****************************************************************************
 * Name: ftpd_command_retr
 ****************************************************************************/

static int ftpd_command_retr(FAR struct ftpd_session_s *session)
{
    return ftpd_stream(session, 0);
}

/****************************************************************************
 * Name: ftpd_command_stor
 ****************************************************************************/

static int ftpd_command_stor(FAR struct ftpd_session_s *session)
{
    return ftpd_stream(session, 1);
}

/****************************************************************************
 * Name: ftpd_command_appe
 ****************************************************************************/

static int ftpd_command_appe(FAR struct ftpd_session_s *session)
{
    return ftpd_stream(session, 2);
}

/****************************************************************************
 * Name: ftpd_command_rest
 ****************************************************************************/

static int ftpd_command_rest(FAR struct ftpd_session_s *session)
{
#ifdef CONFIG_HAVE_LONG_LONG
    session->restartpos = (off_t)atoll(session->param);
#else
    session->restartpos = (off_t)atoi(session->param);
#endif
    session->flags |= FTPD_SESSIONFLAG_RESTARTPOS;

    return ftpd_response(session->cmd.sd, session->txtimeout,
                         g_respfmt1, 320, ' ', "Restart position ready");
}

/****************************************************************************
 * Name: ftpd_command_mdtm
 ****************************************************************************/

static int ftpd_command_mdtm(FAR struct ftpd_session_s *session)
{
  FAR char *abspath;
  FAR char *path;
  struct stat st;
  struct tm tm;
  int ret;

  ret = ftpd_getpath(session, session->param, &abspath, NULL);
  if (ret <0)
    {
      return ftpd_response(session->cmd.sd, session->txtimeout,
                           g_respfmt1, 550, ' ', "Unknown size !");
    }
  path = abspath;

  ret = stat(path, &st);
  if (ret < 0)
    {
      free(abspath);
      return ftpd_response(session->cmd.sd, session->txtimeout,
                           g_respfmt2, 550, ' ', session->param,
                           ": not a plain file.");
    }

    if (!S_ISREG(st.st_mode))
      {
        free(abspath);
        return ftpd_response(session->cmd.sd, session->txtimeout,
                             g_respfmt2, 550, ' ', session->param,
                             ": not a plain file.");
    }

  free(abspath);

  memcpy(&tm, gmtime(&st.st_mtime), sizeof(tm));
  return ftpd_response(session->cmd.sd, session->txtimeout,
                       "%03u%c%04u%02u%02u%02u%02u%02u\r\n", 213, ' ',
                       tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
                       tm.tm_hour, tm.tm_min, tm.tm_sec);
}

/****************************************************************************
 * Name: ftpd_command_opts
 ****************************************************************************/

static int ftpd_command_opts(FAR struct ftpd_session_s *session)
{
  FAR char *str;
  FAR char *option;
  FAR char *value;
  bool remote = false;
  bool local = false;

  /* token: name and value */

  str = session->param;
  option = ftpd_strtok(true, " \t", &str);

  /* Unlike the "real" strtok, ftpd_strtok does not NUL-terminate
   * the returned string.
   */

  if (*str != '\0')
    {
      *str = '\0';
      str++;
    }
  value = str;

  if (strcasecmp(option, "UTF8") == 0 || strcasecmp(option, "UTF-8") == 0)
    {
      FAR char *lang;

      if (value[0] == '\0' || strcasecmp(value, "ON") == 0 ||
          strcasecmp(value, "ENABLE") == 0 || strcasecmp(value, "TRUE") == 0)
        {
            remote = true;
        }
        else {
            remote = false;
        }

      lang = getenv("LANG");
      if (lang)
        {
          if (strcasestr(lang, "UTF8") || strcasestr(lang, "UTF-8"))
            {
              local = true;
            }
          else
            {
              local = false;
            }
        }
#if 1 /*  OPTION: UTF-8 is default */
      else
        {
          local = true;
        }
#endif

      if (remote != local)
        {
          return ftpd_response(session->cmd.sd, session->txtimeout,
                               g_respfmt1, 504, ' ', "UIF-8 disabled");
        }

      return ftpd_response(session->cmd.sd, session->txtimeout,
                           g_respfmt1, 200, ' ', "OK, UTF-8 enabled");
    }

  return ftpd_response(session->cmd.sd, session->txtimeout,
                       "%03u%c%s%s%s\r\n", 501, ' ', "OPTS: ", option,
                       " not understood");
}

/****************************************************************************
 * Name: ftpd_command_site
 ****************************************************************************/

static int ftpd_command_site(FAR struct ftpd_session_s *session)
{
  return ftpd_response(session->cmd.sd, session->txtimeout,
                       g_respfmt1, 502, ' ', "SITE command not implemented !");
}

/****************************************************************************
 * Name: ftpd_command_help
 ****************************************************************************/

static int ftpd_command_help(FAR struct ftpd_session_s *session)
{
  int index;
  int ret;

  index = 0;
  while (g_ftpdhelp[index])
    {
      if (index == 0 || !g_ftpdhelp[index + 1])
        {
          ret = ftpd_response(session->cmd.sd, session->txtimeout,
                              g_respfmt1, 214,
                              !g_ftpdhelp[index + 1] ? ' ' : '-',
                              g_ftpdhelp[index]);
        }
      else
        {
          ret = ftpd_response(session->cmd.sd, session->txtimeout,
                              "%c%s\r\n", ' ', g_ftpdhelp[index]);
        }

      if (ret < 0)
        {
          return ret;
        }

      index++;
    }

  return OK;
}

/****************************************************************************
 * Name: ftpd_command
 ****************************************************************************/

static int ftpd_command(FAR struct ftpd_session_s *session)
{
  int index = 0;

  /* Search the command table for a matching command */

  for (index = 0; g_ftpdcmdtab[index].command; index++)
    {
      /* Does the command string match this entry? */

      if (strcmp(session->command, g_ftpdcmdtab[index].command) == 0)
        {
          /* Yes.. is a login required to execute this command? */

          if ((g_ftpdcmdtab[index].flags & FTPD_CMDFLAG_LOGIN) != 0)
            {
              /* Yes... Check if the user is logged in */

              if (!session->curr && session->head)
                {
                  return ftpd_response(session->cmd.sd, session->txtimeout,
                                       g_respfmt1, 530, ' ',
                                       "Please login with USER and PASS !");
                }
            }

          /* Check if there is a handler for the command */

          if (g_ftpdcmdtab[index].handler)
            {
              /* Yess.. invoke the command handler. */

              return g_ftpdcmdtab[index].handler(session);
            }

          /* No... this command is not in the command table.  Break out of
           * the loop and send the 500 message.
           */

          break;
        }
    }

  /* There is nothing in the command table matching this command */

  return ftpd_response(session->cmd.sd, session->txtimeout,
                       g_respfmt2, 500, ' ', session->command,
                       " not understood");
}

/****************************************************************************
 * Worker Thread
 ****************************************************************************/
/****************************************************************************
 * Name: ftpd_startworker
 ****************************************************************************/

static int ftpd_startworker(pthread_startroutine_t handler, FAR void *arg,
                            size_t stacksize)
{
  pthread_t threadid;
  pthread_attr_t attr;
  int ret;

  /* Initialize the thread attributes */

  ret = pthread_attr_init(&attr);
  if (ret != 0)
    {
      nerr("ERROR: pthread_attr_init() failed: %d\n", ret);
      goto errout;
    }

  /* The set the thread stack size */

  ret = pthread_attr_setstacksize(&attr, stacksize);
  if (ret != 0)
    {
      nerr("ERROR: pthread_attr_setstacksize() failed: %d\n", ret);
      goto errout_with_attr;
    }

  /* And create the thread */

  ret = pthread_create(&threadid, &attr, handler, arg);
  if (ret != 0)
    {
      nerr("ERROR: pthread_create() failed: %d\n", ret);
      goto errout_with_attr;
    }

  /* Put the thread in the detached stated */

  ret = pthread_detach(threadid);
  if (ret != 0)
    {
      nerr("ERROR: pthread_detach() failed: %d\n", ret);
    }

errout_with_attr:
  pthread_attr_destroy(&attr);
errout:
  return -ret;
}

/****************************************************************************
 * Name: ftpd_freesession
 ****************************************************************************/

static void ftpd_freesession(FAR struct ftpd_session_s *session)
{
  /* Free resources */

  if (session->renamefrom)
    {
      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_SOLINGER
  struct linger ling;
#endif

#ifdef CONFIG_NET_HAVE_IPTOS
  temp = IPTOS_LOWDELAY;
  (void)setsockopt(session->cmd.sd, IPPROTO_IP, IP_TOS, &temp, sizeof(temp));
#endif

#ifdef CONFIG_NET_HAVE_OOBINLINE
  temp = 1;
  (void)setsockopt(session->cmd.sd, SOL_SOCKET, SO_OOBINLINE, &temp, sizeof(temp));
#endif

#ifdef CONFIG_NET_SOLINGER
  (void)memset(&ling, 0, sizeof(ling));
  ling.l_onoff = 1;
  ling.l_linger = 4;
  (void)setsockopt(session->cmd.sd, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling));
#endif
}

/****************************************************************************
 * Name: ftpd_worker
 ****************************************************************************/

static FAR void *ftpd_worker(FAR void *arg)
{
  FAR struct ftpd_session_s *session = (FAR struct ftpd_session_s *)arg;
  ssize_t recvbytes;
  size_t offset;
  uint8_t ch;
  int ret;

  ninfo("Worker started\n");
  DEBUGASSERT(session);

  /* Configure the session sockets */

  ftpd_workersetup(session);

  /* Send the welcoming message */

  ret = ftpd_response(session->cmd.sd, session->txtimeout,
                          g_respfmt1, 220, ' ', CONFIG_FTPD_SERVERID);
  if (ret < 0)
    {
      nerr("ERROR: ftpd_response() failed: %d\n", ret);
      ftpd_freesession(session);
      return NULL;
    }

  /* Then loop processing FTP commands */

  for (;;)
    {
      /* Receive the next command */

      recvbytes = ftpd_recv(session->cmd.sd, session->cmd.buffer,
                            session->cmd.buflen - 1, session->rxtimeout);

      /* recbytes < 0 is a receive failure (posibily a timeout);
       * recbytes == 0 indicates that we have lost the connection.
       */

      if (recvbytes <= 0)
        {
          /* Break out of the server loop */

          break;
        }

      /* Make sure that the 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)
        {
          nerr("ERROR: Disconnected by the command handler: %d\n", ret);
          break;
        }
    }

  ftpd_freesession(session);
  return NULL;
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: ftpd_open
 *
 * Description:
 *   Create an instance of the FTPD server and return a handle that can be
 *   used to run the server.
 *
 * Input Parameters:
 *    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)
    {
      nerr("ERROR: Failed to allocte memory to the account\n");
      ret = -ENOMEM;
      goto errout;
    }

  ret = ftpd_account_setpassword(newaccount, passwd);
  if (ret < 0)
    {
      nerr("ERROR: ftpd_account_setpassword failed: %d\n", ret);
      goto errout_with_account;
    }

  ret = ftpd_account_sethome(newaccount, home);
  if (ret < 0)
    {
      nerr("ERROR: ftpd_account_sethome failed: %d\n", ret);
      goto errout_with_account;
    }

  server = (FAR struct ftpd_server_s *)handle;
  ret = ftpd_account_add(server, newaccount);
  if (ret < 0)
    {
      nerr("ERROR: ftpd_account_add failed: %d\n", ret);
      goto errout_with_account;
    }

  return OK;

errout_with_account:
  ftpd_account_free(newaccount);

errout:
  return ret;
}

/****************************************************************************
 * Name: ftpd_session
 *
 * Description:
 *   Execute the FTPD server.  This thread does not return until either (1)
 *   the timeout expires with no connection, (2) some other error occurs, or
 *   (2) a connection was accepted and an FTP worker thread was started to
 *   service the session.
 *
 * Input Parameters:
 *   handle - A handle previously returned by ftpd_open
 *   timeout - A time in milliseconds to wait for a connection. If this
 *     time elapses with no connected, the -ETIMEDOUT error will be returned.
 *
 * Returned Value:
 *   Zero is returned if the FTP worker was started.  On failure, a negated
 *   errno value is returned to indicate why the servier terminated.
 *   -ETIMEDOUT indicates that the user-provided timeout elapsed with no
 *   connection.
 *
 ****************************************************************************/

int ftpd_session(FTPD_SESSION handle, int timeout)
{
  FAR struct ftpd_server_s  *server;
  FAR struct ftpd_session_s *session;
  int ret;

  DEBUGASSERT(handle);

  server = (FAR struct ftpd_server_s *)handle;

  /* Allocate a session */

  session = (FAR struct ftpd_session_s *)zalloc(sizeof(struct ftpd_session_s));
  if (!session)
    {
      nerr("ERROR: 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)
    {
      nerr("ERROR: Failed to allocate command buffer\n");
      ret = -ENOMEM;
      goto errout_with_session;
    }

  /* Allocate a data buffer */

  session->data.buffer = (FAR char *)malloc(session->data.buflen);
  if (!session->data.buffer)
    {
      nerr("ERROR: Failed to allocate data buffer\n");
      ret = -ENOMEM;
      goto errout_with_session;
    }

  /* Accept a connection */

  session->cmd.sd = ftpd_accept(server->sd, (FAR void *)&session->cmd.addr,
                                &session->cmd.addrlen, timeout);
  if (session->cmd.sd < 0)
    {
      /* Only report interesting, infrequent errors (not the common timeout) */

#ifdef CONFIG_DEBUG_NET
      if (session->cmd.sd != -ETIMEDOUT)
        {
          nerr("ERROR: ftpd_accept() failed: %d\n", session->cmd.sd);
        }
#endif
      ret = session->cmd.sd;
      goto errout_with_session;
    }

  /* And create a worker thread to service the session */

  ret = ftpd_startworker(ftpd_worker, (FAR void *)session,
                         CONFIG_FTPD_WORKERSTACKSIZE);
  if (ret < 0)
    {
      nerr("ERROR: ftpd_startworker() failed: %d\n", ret);
      goto errout_with_session;
    }

  /* Successfully connected an launched the worker thread */

  return 0;

errout_with_session:
  ftpd_freesession(session);
errout:
  return ret;
}

/****************************************************************************
 * Name: ftpd_close
 *
 * Description:
 *   Close and destroy the handle created by ftpd_open.
 *
 * Input Parameters:
 *   handle - A handle previously returned by ftpd_open
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

void ftpd_close(FTPD_SESSION handle)
{
  struct ftpd_server_s *server;
  DEBUGASSERT(handle);

  server = (struct ftpd_server_s *)handle;
  ftpd_account_free(server->head);

  if (server->sd >= 0)
    {
      close(server->sd);
      server->sd = -1;
    }

  free(server);
}

