/**
 * @file ftp_server_commands.c
 * @brief FTP server (command processing)
 *
 * @section License
 *
 * Copyright (C) 2010-2014 Oryx Embedded. All rights reserved.
 *
 * This file is part of CycloneTCP Open.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 * @author Oryx Embedded (www.oryx-embedded.com)
 * @version 1.4.4
 **/

//Switch to the appropriate trace level
#define TRACE_LEVEL FTP_TRACE_LEVEL

//Dependencies
#include <stdlib.h>
#include "ftp_server.h"
#include "ftp_server_events.h"
#include "ftp_server_commands.h"
#include "ftp_server_misc.h"
#include "str.h"
#include "path.h"
#include "error.h"
#include "debug.h"

//Check TCP/IP stack configuration
#if (FTP_SERVER_SUPPORT == ENABLED)


/**
 * @brief FTP command processing
 * @param[in] context Pointer to the FTP server context
 * @param[in] connection Pointer to the client connection
 **/

void ftpServerProcessCmd(FtpServerContext *context,
   FtpClientConnection *connection)
{
   size_t n;
   char_t *p;

   //The <CRLF> sequence should be used to terminate the command line
   for(n = 0; n < connection->commandLength; n++)
   {
      if(connection->command[n] == '\n')
         break;
   }

   //Any command to process?
   if(n < connection->commandLength)
   {
      //Properly terminate the string with a NULL character
      connection->command[n] = '\0';
      //Remove trailing whitespace from the command line
      strRemoveTrailingSpace(connection->command);

      //Debug message
      TRACE_DEBUG("FTP client: %s\r\n", connection->command);

      //Command line too long?
      if(connection->controlState == FTP_CONTROL_STATE_DISCARD)
      {
         //Switch to idle state
         connection->controlState = FTP_CONTROL_STATE_IDLE;
         //Format response message
         strcpy(connection->response, "500 Command line too long\r\n");
      }
      else
      {
         //The command name and the arguments are separated by one or more spaces
         for(p = connection->command; *p != '\0' && *p != ' '; p++);

         //Space character found?
         if(*p == ' ')
         {
            //Split the string at the first occurrence of the space character
            *(p++) = '\0';
            //Skip extra whitespace
            while(*p == ' ') p++;
         }

         //NOOP command received
         if(!strcasecmp(connection->command, "NOOP"))
            ftpServerProcessNoop(context, connection, p);
         //SYST command received
         else if(!strcasecmp(connection->command, "SYST"))
            ftpServerProcessSyst(context, connection, p);
         //FEAT command received?
         else if(!strcasecmp(connection->command, "FEAT"))
            ftpServerProcessFeat(context, connection, p);
         //TYPE command received?
         else if(!strcasecmp(connection->command, "TYPE"))
            ftpServerProcessType(context, connection, p);
         //STRU command received?
         else if(!strcasecmp(connection->command, "STRU"))
            ftpServerProcessStru(context, connection, p);
         //MODE command received?
         else if(!strcasecmp(connection->command, "MODE"))
            ftpServerProcessMode(context, connection, p);
         //USER command received?
         else if(!strcasecmp(connection->command, "USER"))
            ftpServerProcessUser(context, connection, p);
         //PASS command received?
         else if(!strcasecmp(connection->command, "PASS"))
            ftpServerProcessPass(context, connection, p);
         //REIN command received?
         else if(!strcasecmp(connection->command, "REIN"))
            ftpServerProcessRein(context, connection, p);
         //QUIT command received?
         else if(!strcasecmp(connection->command, "QUIT"))
            ftpServerProcessQuit(context, connection, p);
         //PORT command received?
         else if(!strcasecmp(connection->command, "PORT"))
            ftpServerProcessPort(context, connection, p);
         //EPRT command received?
         else if(!strcasecmp(connection->command, "EPRT"))
            ftpServerProcessEprt(context, connection, p);
         //PASV command received?
         else if(!strcasecmp(connection->command, "PASV"))
            ftpServerProcessPasv(context, connection, p);
         //EPSV command received?
         else if(!strcasecmp(connection->command, "EPSV"))
            ftpServerProcessEpsv(context, connection, p);
         //ABOR command received?
         else if(!strcasecmp(connection->command, "ABOR"))
            ftpServerProcessAbor(context, connection, p);
         //PWD command received?
         else if(!strcasecmp(connection->command, "PWD"))
            ftpServerProcessPwd(context, connection, p);
         //LIST command received?
         else if(!strcasecmp(connection->command, "LIST"))
            ftpServerProcessList(context, connection, p);
         //CWD command received?
         else if(!strcasecmp(connection->command, "CWD"))
            ftpServerProcessCwd(context, connection, p);
         //CDUP command received?
         else if(!strcasecmp(connection->command, "CDUP"))
            ftpServerProcessCdup(context, connection, p);
         //MKD command received?
         else if(!strcasecmp(connection->command, "MKD"))
            ftpServerProcessMkd(context, connection, p);
         //RMD command received?
         else if(!strcasecmp(connection->command, "RMD"))
            ftpServerProcessRmd(context, connection, p);
         //SIZE command received?
         else if(!strcasecmp(connection->command, "SIZE"))
            ftpServerProcessSize(context, connection, p);
         //RETR command received?
         else if(!strcasecmp(connection->command, "RETR"))
            ftpServerProcessRetr(context, connection, p);
         //STOR command received?
         else if(!strcasecmp(connection->command, "STOR"))
            ftpServerProcessStor(context, connection, p);
         //APPE command received?
         else if(!strcasecmp(connection->command, "APPE"))
            ftpServerProcessAppe(context, connection, p);
         //RNFR command received?
         else if(!strcasecmp(connection->command, "RNFR"))
            ftpServerProcessRnfr(context, connection, p);
         //RNTO command received?
         else if(!strcasecmp(connection->command, "RNTO"))
            ftpServerProcessRnto(context, connection, p);
         //DELE command received?
         else if(!strcasecmp(connection->command, "DELE"))
            ftpServerProcessDele(context, connection, p);
         //Invalid command received?
         else
            ftpServerProcessInvalidCmd(context, connection, p);
      }

      //Debug message
      TRACE_DEBUG("FTP server: %s", connection->response);

      //Number of bytes in the response buffer
      connection->responseLength = strlen(connection->response);
      connection->responsePos = 0;

      //Clear command line
      connection->commandLength = 0;
   }
   else if(connection->commandLength >= FTP_SERVER_MAX_LINE_LEN)
   {
      //The command line is too long...
      connection->controlState = FTP_CONTROL_STATE_DISCARD;
      //Drop incoming data
      connection->commandLength = 0;
   }
}


/**
 * @brief Invalid command processing
 * @param[in] context Pointer to the FTP server context
 * @param[in] connection Pointer to the client connection
 * @param[in] param Command line parameters
 **/

void ftpServerProcessInvalidCmd(FtpServerContext *context,
   FtpClientConnection *connection, char_t *param)
{
   //Format response message
   strcpy(connection->response, "500 Command unrecognized\r\n");
}


/**
 * @brief NOOP command processing
 *
 * The NOOP command does not affect any parameters or previously entered
 * commands. It specifies no action other than that the server send an OK reply
 *
 * @param[in] context Pointer to the FTP server context
 * @param[in] connection Pointer to the client connection
 * @param[in] param Command line parameters
 **/

void ftpServerProcessNoop(FtpServerContext *context,
   FtpClientConnection *connection, char_t *param)
{
   //Send an OK reply
   strcpy(connection->response, "200 Command okay\r\n");
}


/**
 * @brief SYST command processing
 *
 * The SYST command is used to find out the type of operating system
 * at the server side
 *
 * @param[in] context Pointer to the FTP server context
 * @param[in] connection Pointer to the client connection
 * @param[in] param Command line parameters
 **/

void ftpServerProcessSyst(FtpServerContext *context,
   FtpClientConnection *connection, char_t *param)
{
   //Format the response to the SYST command
   strcpy(connection->response, "215 UNIX Type: L8\r\n");
}


/**
 * @brief FEAT command processing
 *
 * The FEAT command allows a client to discover which optional
 * commands a server supports
 *
 * @param[in] context Pointer to the FTP server context
 * @param[in] connection Pointer to the client connection
 * @param[in] param Command line parameters
 **/

void ftpServerProcessFeat(FtpServerContext *context,
   FtpClientConnection *connection, char_t *param)
{
   //Format the response to the FEAT command
   strcpy(connection->response, "211-Features supported:\r\n");
   strcat(connection->response, " SIZE\r\n");
   strcat(connection->response, " EPRT\r\n");
   strcat(connection->response, " EPSV\r\n");
   strcat(connection->response, "211 End\r\n");
}


/**
 * @brief TYPE command processing
 *
 * The TYPE command specifies the representation type
 *
 * @param[in] context Pointer to the FTP server context
 * @param[in] connection Pointer to the client connection
 * @param[in] param Command line parameters
 **/

void ftpServerProcessType(FtpServerContext *context,
   FtpClientConnection *connection, char_t *param)
{
   //The argument specifies the representation type
   if(*param != '\0')
   {
      //ASCII type?
      if(!strcasecmp(param, "A"))
      {
         //Format the response to the TYPE command
         strcpy(connection->response, "200 Type set to A\r\n");
      }
      //Image type?
      else if(!strcasecmp(param, "I"))
      {
         //Format the response to the TYPE command
         strcpy(connection->response, "200 Type set to I\r\n");
      }
      //Unknown type?
      else
      {
         //Report an error
         strcpy(connection->response, "504 Unknown type\r\n");
      }
   }
   else
   {
      //The argument is missing...
      strcpy(connection->response, "501 Missing parameter\r\n");
   }
}


/**
 * @brief STRU command processing
 *
 * The STRU command specifies the file structure
 *
 * @param[in] context Pointer to the FTP server context
 * @param[in] connection Pointer to the client connection
 * @param[in] param Command line parameters
 **/

void ftpServerProcessStru(FtpServerContext *context,
   FtpClientConnection *connection, char_t *param)
{
   //The argument specifies the file structure
   if(*param != '\0')
   {
      //No record structure?
      if(!strcasecmp(param, "F"))
      {
         //Format the response to the STRU command
         strcpy(connection->response, "200 Structure set to F\r\n");
      }
      //Unknown file structure?
      else
      {
         //Report an error
         strcpy(connection->response, "504 Unknown structure\r\n");
      }
   }
   else
   {
      //The argument is missing...
      strcpy(connection->response, "501 Missing parameter\r\n");
   }
}


/**
 * @brief MODE command processing
 *
 * The MODE command specifies the data transfer mode
 *
 * @param[in] context Pointer to the FTP server context
 * @param[in] connection Pointer to the client connection
 * @param[in] param Command line parameters
 **/

void ftpServerProcessMode(FtpServerContext *context,
   FtpClientConnection *connection, char_t *param)
{
   //The argument specifies the data transfer mode
   if(*param != '\0')
   {
      //Stream mode?
      if(!strcasecmp(param, "S"))
      {
         //Format the response to the MODE command
         strcpy(connection->response, "200 Mode set to S\r\n");
      }
      //Unknown data transfer mode?
      else
      {
         //Report an error
         strcpy(connection->response, "504 Unknown mode\r\n");
      }
   }
   else
   {
      //The argument is missing...
      strcpy(connection->response, "501 Missing parameter\r\n");
   }
}


/**
 * @brief USER command processing
 *
 * The USER command is used to identify the user
 *
 * @param[in] context Pointer to the FTP server context
 * @param[in] connection Pointer to the client connection
 * @param[in] param Command line parameters
 **/

void ftpServerProcessUser(FtpServerContext *context,
   FtpClientConnection *connection, char_t *param)
{
   uint_t status;

   //The argument specifies the user name
   if(*param == '\0')
   {
      //The argument is missing...
      strcpy(connection->response, "501 Missing parameter\r\n");
      //Exit immediately
      return;
   }

   //Check the length of the user name
   if(strlen(param) > FTP_SERVER_MAX_USERNAME_LEN)
   {
      //The specified user name is not valid...
      strcpy(connection->response, "501 Invalid parameter\r\n");
      //Exit immediately
      return;
   }

   //Save user name
   strcpy(connection->user, param);
   //Log out the user
   connection->userLoggedIn = FALSE;
   //Set home directory
   strcpy(connection->homeDir, context->settings.rootDir);
   //Set current directory
   strcpy(connection->currentDir, context->settings.rootDir);

   //Invoke user-defined callback, if any
   if(context->settings.checkUserCallback != NULL)
      status = context->settings.checkUserCallback(connection, param);
   else
      status = FTP_ACCESS_ALLOWED;

   //Access allowed?
   if(status == FTP_ACCESS_ALLOWED)
   {
      //The user is now logged in
      connection->userLoggedIn = TRUE;
      //Format response message
      strcpy(connection->response, "230 User logged in, proceed\r\n");
   }
   //Password required?
   else if(status == FTP_PASSWORD_REQUIRED)
   {
      //This command must be immediately followed by a PASS command
      connection->controlState = FTP_CONTROL_STATE_USER;
      //Format response message
      strcpy(connection->response, "331 User name okay, need password\r\n");
   }
   //Access denied?
   else
   {
      //Format response message
      strcpy(connection->response, "530 Login authentication failed\r\n");
   }
}


/**
 * @brief PASS command processing
 *
 * The USER command specifies the user's password
 *
 * @param[in] context Pointer to the FTP server context
 * @param[in] connection Pointer to the client connection
 * @param[in] param Command line parameters
 **/

void ftpServerProcessPass(FtpServerContext *context,
   FtpClientConnection *connection, char_t *param)
{
   uint_t status;

   //This command must immediately follow a USER command
   if(connection->controlState != FTP_CONTROL_STATE_USER)
   {
      //Switch to idle state
      connection->controlState = FTP_CONTROL_STATE_IDLE;
      //Report an error
      strcpy(connection->response, "503 Bad sequence of commands\r\n");
      //Exit immediately
      return;
   }

   //Switch to idle state
   connection->controlState = FTP_CONTROL_STATE_IDLE;

   //The argument specifies the password
   if(*param == '\0')
   {
      //The argument is missing...
      strcpy(connection->response, "501 Missing parameter\r\n");
      //Exit immediately
      return;
   }

   //Invoke user-defined callback, if any
   if(context->settings.checkPasswordCallback != NULL)
      status = context->settings.checkPasswordCallback(connection, connection->user, param);
   else
      status = FTP_ACCESS_ALLOWED;

   //Access allowed?
   if(status == FTP_ACCESS_ALLOWED)
   {
      //The user is now logged in
      connection->userLoggedIn = TRUE;
      //Format response message
      strcpy(connection->response, "230 User logged in, proceed\r\n");
   }
   //Access denied?
   else
   {
      //Format response message
      strcpy(connection->response, "530 Login authentication failed\r\n");
   }
}


/**
 * @brief REIN command processing
 *
 * The REIN command is used to reinitialize a user session
 *
 * @param[in] context Pointer to the FTP server context
 * @param[in] connection Pointer to the client connection
 * @param[in] param Command line parameters
 **/

void ftpServerProcessRein(FtpServerContext *context,
   FtpClientConnection *connection, char_t *param)
{
   //Close data connection
   ftpServerCloseDataConnection(connection);

   //Release previously allocated resources
   if(connection->file != NULL)
   {
      fsFileClose(connection->file);
      connection->file = NULL;
   }
   if(connection->dir != NULL)
   {
      fsDirClose(connection->dir);
      connection->dir = NULL;
   }

   //Clear account information
   connection->userLoggedIn = FALSE;

   //Format response message
   strcpy(connection->response, "220 Service ready for new user\r\n");
}


/**
 * @brief QUIT command processing
 *
 * The QUIT command is used to terminate a user session
 *
 * @param[in] context Pointer to the FTP server context
 * @param[in] connection Pointer to the client connection
 * @param[in] param Command line parameters
 **/

void ftpServerProcessQuit(FtpServerContext *context,
   FtpClientConnection *connection, char_t *param)
{
   //There are two cases to consider upon receipt of this command
   if(connection->dataState == FTP_DATA_STATE_CLOSED)
   {
      //If the FTP service command was already completed, the server closes
      //the data connection (if it is open)...
      ftpServerCloseDataConnection(connection);

      //...and responds with a 221 reply
      strcpy(connection->response, "221 Service closing control connection\r\n");
   }
   else
   {
      //If the FTP service command is still in progress, the server aborts
      //the FTP service in progress and closes the data connection...
      ftpServerCloseDataConnection(connection);

      //...returning a 426 reply to indicate that the service request
      //terminated abnormally
      strcpy(connection->response, "426 Connection closed; transfer aborted\r\n");

      //The server then sends a 221 reply
      strcat(connection->response, "221 Service closing control connection\r\n");
   }

   //Release previously allocated resources
   if(connection->file != NULL)
   {
      fsFileClose(connection->file);
      connection->file = NULL;
   }
   if(connection->dir != NULL)
   {
      fsDirClose(connection->dir);
      connection->dir = NULL;
   }

   //Clear account information
   connection->userLoggedIn = FALSE;
   //Gracefully disconnect from the remote host
   connection->controlState = FTP_CONTROL_STATE_WAIT_ACK;
}


/**
 * @brief PORT command processing
 *
 * The PORT command specifies the data port to be used for the data connection
 *
 * @param[in] context Pointer to the FTP server context
 * @param[in] connection Pointer to the client connection
 * @param[in] param Command line parameters
 **/

void ftpServerProcessPort(FtpServerContext *context,
   FtpClientConnection *connection, char_t *param)
{
   error_t error;
   size_t i;
   size_t j;
   char_t *p;
   char_t *token;
   char_t *end;

   //Ensure the user is logged in
   if(!connection->userLoggedIn)
   {
      //Format response message
      strcpy(connection->response, "530 Not logged in\r\n");
      //Exit immediately
      return;
   }

   //The argument is the concatenation of the IP address and the 16-bit port number
   if(*param == '\0')
   {
      //The argument is missing...
      strcpy(connection->response, "501 Missing parameter\r\n");
      //Exit immediately
      return;
   }

   //Close the data connection, if any
   ftpServerCloseDataConnection(connection);

   //Start of exception handling block
   do
   {
      //Assume an error condition...
      error = ERROR_INVALID_SYNTAX;

      //Parse the string
      for(i = 0, j = 1; param[i] != '\0'; i++)
      {
         //Change commas to dots
         if(param[i] == ',' && j < sizeof(Ipv4Addr))
         {
            param[i] = '.';
            j++;
         }
      }

      //Get the IP address to be used
      token = strtok_r(param, ",", &p);
      //Syntax error?
      if(!token) break;

      //Convert the dot-decimal string to a binary IP address
      error = ipStringToAddr(token, &connection->remoteIpAddr);
      //Invalid IP address?
      if(error) break;

      //Assume an error condition...
      error = ERROR_INVALID_SYNTAX;

      //Get the most significant byte of the port number
      token = strtok_r(NULL, ",", &p);
      //Syntax error?
      if(!token) break;

      //Convert the string representation to integer
      connection->remotePort = strtoul(token, &end, 10) << 8;
      //Syntax error?
      if(*end != '\0') break;

      //Get the least significant byte of the port number
      token = strtok_r(NULL, ",", &p);
      //Syntax error?
      if(!token) break;

      //Convert the string representation to integer
      connection->remotePort |= strtoul(token, &end, 10) & 0xFF;
      //Syntax error?
      if(*end != '\0') break;

      //Successful processing
      error = NO_ERROR;

      //End of exception handling block
   } while(0);

   //Any error to report?
   if(error)
   {
      //Re initialize data connection
      connection->passiveMode = FALSE;
      connection->remotePort = 0;

      //Format response message
      strcpy(connection->response, "501 Syntax error in parameters or arguments\r\n");
      //Exit immediately
      return;
   }

   //Successful processing
   strcpy(connection->response, "200 Command okay\r\n");
}


/**
 * @brief EPRT command processing
 *
 * The EPRT command allows for the specification of an extended address
 * for the data connection
 *
 * @param[in] context Pointer to the FTP server context
 * @param[in] connection Pointer to the client connection
 * @param[in] param Command line parameters
 **/

void ftpServerProcessEprt(FtpServerContext *context,
   FtpClientConnection *connection, char_t *param)
{
   error_t error;
   uint_t protocol;
   char_t *p;
   char_t *token;
   char_t *end;
   char_t delimiter[2];

   //Ensure the user is logged in
   if(!connection->userLoggedIn)
   {
      //Format response message
      strcpy(connection->response, "530 Not logged in\r\n");
      //Exit immediately
      return;
   }

   //The extended address must consist of the network protocol
   //as well as the IP address and the 16-bit port number
   if(*param == '\0')
   {
      //The argument is missing...
      strcpy(connection->response, "501 Missing parameter\r\n");
      //Exit immediately
      return;
   }

   //Close the data connection, if any
   ftpServerCloseDataConnection(connection);

   //Start of exception handling block
   do
   {
      //A delimiter character must be specified
      delimiter[0] = param[0];
      delimiter[1] = '\0';
      //Skip delimiter character
      param++;

      //Assume an error condition...
      error = ERROR_INVALID_SYNTAX;

      //Retrieve the network protocol to be used
      token = strtok_r(param, delimiter, &p);
      //Syntax error?
      if(!token) break;

      //Convert the string representation to integer
      protocol = strtoul(token, &end, 10);
      //Syntax error?
      if(*end != '\0') break;

      //Get the IP address to be used
      token = strtok_r(NULL, delimiter, &p);
      //Syntax error?
      if(!token) break;

#if (IPV4_SUPPORT == ENABLED)
      //IPv4 address family?
      if(protocol == 1)
      {
         //IPv4 addresses are 4-byte long
         connection->remoteIpAddr.length = sizeof(Ipv4Addr);
         //Convert the string to IPv4 address
         error = ipv4StringToAddr(token, &connection->remoteIpAddr.ipv4Addr);
         //Invalid IP address?
         if(error) break;
      }
      else
#endif
#if (IPV6_SUPPORT == ENABLED)
      //IPv6 address family?
      if(protocol == 2)
      {
         //IPv6 addresses are 16-byte long
         connection->remoteIpAddr.length = sizeof(Ipv6Addr);
         //Convert the string to IPv6 address
         error = ipv6StringToAddr(token, &connection->remoteIpAddr.ipv6Addr);
         //Invalid IP address?
         if(error) break;
      }
      else
#endif
      //Unknown address family?
      {
         //Report an error
         error = ERROR_INVALID_ADDRESS;
         //Exit immediately
         break;
      }

      //Assume an error condition...
      error = ERROR_INVALID_SYNTAX;

      //Get the port number to be used
      token = strtok_r(NULL, delimiter, &p);
      //Syntax error?
      if(!token) break;

      //Convert the string representation to integer
      connection->remotePort = strtoul(token, &end, 10);
      //Syntax error?
      if(*end != '\0') break;

      //Successful processing
      error = NO_ERROR;

      //End of exception handling block
   } while(0);

   //Any error to report?
   if(error)
   {
      //Re initialize data connection
      connection->passiveMode = FALSE;
      connection->remotePort = 0;

      //Format response message
      strcpy(connection->response, "501 Syntax error in parameters or arguments\r\n");
      //Exit immediately
      return;
   }

   //Successful processing
   strcpy(connection->response, "200 Command okay\r\n");
}


/**
 * @brief PASV command processing
 *
 * The PASV command requests the server to listen on a data port and
 * to wait for a connection rather than initiate one upon receipt of
 * a transfer command
 *
 * @param[in] context Pointer to the FTP server context
 * @param[in] connection Pointer to the client connection
 * @param[in] param Command line parameters
 **/

void ftpServerProcessPasv(FtpServerContext *context,
   FtpClientConnection *connection, char_t *param)
{
   error_t error;
   size_t n;
   IpAddr ipAddr;
   uint16_t port;

   //Ensure the user is logged in
   if(!connection->userLoggedIn)
   {
      //Format response message
      strcpy(connection->response, "530 Not logged in\r\n");
      //Exit immediately
      return;
   }

   //Close the data connection, if any
   ftpServerCloseDataConnection(connection);

   //Start of exception handling block
   do
   {
      //Open data socket
      connection->dataSocket = socketOpen(SOCKET_TYPE_STREAM, SOCKET_IP_PROTO_TCP);
      //Failed to open socket?
      if(!connection->dataSocket)
      {
         //Report an error
         error = ERROR_OPEN_FAILED;
         //Exit immediately
         break;
      }

      //Force the socket to operate in non-blocking mode
      error = socketSetTimeout(connection->dataSocket, 0);
      //Any error to report?
      if(error) break;

      //Change the size of the TX buffer
      error = socketSetTxBufferSize(connection->dataSocket, FTP_SERVER_DATA_SOCKET_BUFFER_SIZE);
      //Any error to report?
      if(error) break;

      //Change the size of the RX buffer
      error = socketSetRxBufferSize(connection->dataSocket, FTP_SERVER_DATA_SOCKET_BUFFER_SIZE);
      //Any error to report?
      if(error) break;

      //Associate the socket with the relevant interface
      error = socketBindToInterface(connection->dataSocket, context->settings.interface);
      //Unable to bind the socket to the desired interface?
      if(error) break;

      //Place the data socket in the listening state
      error = socketListen(connection->dataSocket);
      //Any error to report?
      if(error) break;

      //Retrieve local IP address
      error = socketGetLocalAddr(connection->controlSocket, &ipAddr, NULL);
      //Any error to report?
      if(error) break;

      //The local IP address must be a valid IPv4 address
      if(ipAddr.length != sizeof(Ipv4Addr))
      {
         //PASV command cannot be used on IPv6 connections
         error = ERROR_INVALID_ADDRESS;
         //Exit immediately
         break;
      }

      //Retrieve local port number
      error = socketGetLocalAddr(connection->dataSocket, NULL, &port);
      //Any error to report?
      if(error) break;

      //End of exception handling block
   } while(0);

   //Any error to report?
   if(error)
   {
      //Clean up side effects
      ftpServerCloseDataConnection(connection);
      //Format response message
      strcpy(connection->response, "425 Can't enter passive mode\r\n");
      //Exit immediately
      return;
   }

   //Use passive data transfer
   connection->passiveMode = TRUE;
   //Update data connection state
   connection->dataState = FTP_DATA_STATE_LISTEN;

   //Format response message
   n = sprintf(connection->response, "227 Entering passive mode (");
   //Append host address
   ipAddrToString(&ipAddr, connection->response + n);

   //Parse the resulting string
   for(n = 0; connection->response[n] != '\0'; n++)
   {
      //Change dots to commas
      if(connection->response[n] == '.')
         connection->response[n] = ',';
   }

   //Append port number
   sprintf(connection->response + n, ",%" PRIu8 ",%" PRIu8 ")\r\n", MSB(port), LSB(port));
}


/**
 * @brief EPSV command processing
 *
 * The EPSV command requests that a server listen on a data port and
 * wait for a connection
 *
 * @param[in] context Pointer to the FTP server context
 * @param[in] connection Pointer to the client connection
 * @param[in] param Command line parameters
 **/

void ftpServerProcessEpsv(FtpServerContext *context,
   FtpClientConnection *connection, char_t *param)
{
   error_t error;
   uint16_t port;

   //Ensure the user is logged in
   if(!connection->userLoggedIn)
   {
      //Format response message
      strcpy(connection->response, "530 Not logged in\r\n");
      //Exit immediately
      return;
   }

   //Close the data connection, if any
   ftpServerCloseDataConnection(connection);

   //Start of exception handling block
   do
   {
      //Open data socket
      connection->dataSocket = socketOpen(SOCKET_TYPE_STREAM, SOCKET_IP_PROTO_TCP);
      //Failed to open socket?
      if(!connection->dataSocket)
      {
         //Report an error
         error = ERROR_OPEN_FAILED;
         //Exit immediately
         break;
      }

      //Force the socket to operate in non-blocking mode
      error = socketSetTimeout(connection->dataSocket, 0);
      //Any error to report?
      if(error) break;

      //Change the size of the TX buffer
      error = socketSetTxBufferSize(connection->dataSocket, FTP_SERVER_DATA_SOCKET_BUFFER_SIZE);
      //Any error to report?
      if(error) break;

      //Change the size of the RX buffer
      error = socketSetRxBufferSize(connection->dataSocket, FTP_SERVER_DATA_SOCKET_BUFFER_SIZE);
      //Any error to report?
      if(error) break;

      //Associate the socket with the relevant interface
      error = socketBindToInterface(connection->dataSocket, context->settings.interface);
      //Unable to bind the socket to the desired interface?
      if(error) break;

      //Place the data socket in the listening state
      error = socketListen(connection->dataSocket);
      //Any error to report?
      if(error) break;

      //Retrieve local port number
      error = socketGetLocalAddr(connection->dataSocket, NULL, &port);
      //Any error to report?
      if(error) break;

      //End of exception handling block
   } while(0);

   //Any error to report?
   if(error)
   {
      //Clean up side effects
      ftpServerCloseDataConnection(connection);
      //Format response message
      strcpy(connection->response, "425 Can't enter passive mode\r\n");
      //Exit immediately
      return;
   }

   //Use passive data transfer
   connection->passiveMode = TRUE;
   //Update data connection state
   connection->dataState = FTP_DATA_STATE_LISTEN;

   //The response code for entering passive mode using an extended address must be 229
   snprintf(connection->response, FTP_SERVER_MAX_LINE_LEN,
      "229 Entering extended passive mode (|||%" PRIu16 "|)\r\n", port);
}


/**
 * @brief ABOR command processing
 *
 * The ABOR command tells the server to abort the previous FTP
 * service command and any associated transfer of data
 *
 * @param[in] context Pointer to the FTP server context
 * @param[in] connection Pointer to the client connection
 * @param[in] param Command line parameters
 **/

void ftpServerProcessAbor(FtpServerContext *context,
   FtpClientConnection *connection, char_t *param)
{
   //There are two cases to consider upon receipt of this command
   if(connection->dataState == FTP_DATA_STATE_CLOSED)
   {
      //If the FTP service command was already completed, the server closes
      //the data connection (if it is open)...
      ftpServerCloseDataConnection(connection);

      //...and responds with a 226 reply, indicating that the abort command
      //was successfully processed
      strcpy(connection->response, "226 Abort command successful\r\n");
   }
   else
   {
      //If the FTP service command is still in progress, the server aborts
      //the FTP service in progress and closes the data connection...
      ftpServerCloseDataConnection(connection);

      //...returning a 426 reply to indicate that the service request
      //terminated abnormally
      strcpy(connection->response, "426 Connection closed; transfer aborted\r\n");

      //The server then sends a 226 reply, indicating that the abort command
      //was successfully processed
      strcat(connection->response, "226 Abort command successful\r\n");
   }

   //Release previously allocated resources
   if(connection->file != NULL)
   {
      fsFileClose(connection->file);
      connection->file = NULL;
   }
   if(connection->dir != NULL)
   {
      fsDirClose(connection->dir);
      connection->dir = NULL;
   }
}


/**
 * @brief PWD command processing
 *
 * The PWD command causes the name of the current working
 * directory to be returned in the reply
 *
 * @param[in] context Pointer to the FTP server context
 * @param[in] connection Pointer to the client connection
 * @param[in] param Command line parameters
 **/

void ftpServerProcessPwd(FtpServerContext *context,
   FtpClientConnection *connection, char_t *param)
{
   //Ensure the user is logged in
   if(!connection->userLoggedIn)
   {
      //Format response message
      strcpy(connection->response, "530 Not logged in\r\n");
      //Exit immediately
      return;
   }

   //A successful PWD command uses the 257 reply code
   snprintf(connection->response, FTP_SERVER_MAX_LINE_LEN,
      "257 \"%s\" is current directory\r\n",
      ftpServerStripHomeDir(connection, connection->currentDir));
}


/**
 * @brief CWD command processing
 *
 * The CWD command allows the user to work with a different
 * directory
 *
 * @param[in] context Pointer to the FTP server context
 * @param[in] connection Pointer to the client connection
 * @param[in] param Command line parameters
 **/

void ftpServerProcessCwd(FtpServerContext *context,
   FtpClientConnection *connection, char_t *param)
{
   error_t error;
   uint_t perm;

   //Ensure the user is logged in
   if(!connection->userLoggedIn)
   {
      //Format response message
      strcpy(connection->response, "530 Not logged in\r\n");
      //Exit immediately
      return;
   }

   //The argument specifies the pathname
   if(*param == '\0')
   {
      //The argument is missing...
      strcpy(connection->response, "501 Missing parameter\r\n");
      //Exit immediately
      return;
   }

   //Retrieve the full pathname
   error = ftpServerGetPath(connection, param,
      connection->path, FTP_SERVER_MAX_PATH_LEN);

   //Make sure the pathname is valid
   if(error)
   {
      //Report an error
      strcpy(connection->response, "501 Invalid parameter\r\n");
      //Exit immediately
      return;
   }

   //Retrieve permissions for the specified directory
   perm = ftpServerGetFilePermissions(context, connection, connection->path);

   //Insufficient access rights?
   if(!(perm & FTP_FILE_PERM_READ))
   {
      //Report an error
      strcpy(connection->response, "550 Access denied\r\n");
      //Exit immediately
      return;
   }

   //Make sure the specified directory exists
   if(!fsDirExists(connection->path))
   {
      //Report an error
      strcpy(connection->response, "550 Directory not found\r\n");
      //Exit immediately
      return;
   }

   //Change current working directory
   strcpy(connection->currentDir, connection->path);

   //A successful PWD command uses the 250 reply code
   snprintf(connection->response, FTP_SERVER_MAX_LINE_LEN,
      "250 Directory changed to %s\r\n",
      ftpServerStripHomeDir(connection, connection->currentDir));
}


/**
 * @brief CDUP command processing
 *
 * The CDUP command allows the user to change to the parent directory
 *
 * @param[in] context Pointer to the FTP server context
 * @param[in] connection Pointer to the client connection
 * @param[in] param Command line parameters
 **/

void ftpServerProcessCdup(FtpServerContext *context,
   FtpClientConnection *connection, char_t *param)
{
   uint_t perm;

   //Ensure the user is logged in
   if(!connection->userLoggedIn)
   {
      //Format response message
      strcpy(connection->response, "530 Not logged in\r\n");
      //Exit immediately
      return;
   }

   //Get current directory
   strcpy(connection->path, connection->currentDir);

   //Change to the parent directory
   pathCombine(connection->path, "..", FTP_SERVER_MAX_PATH_LEN);
   pathCanonicalize(connection->path);

   //Retrieve permissions for the directory
   perm = ftpServerGetFilePermissions(context, connection, connection->path);

   //Check access rights
   if(perm & FTP_FILE_PERM_READ)
   {
      //Update current directory
      strcpy(connection->currentDir, connection->path);
   }

   //A successful PWD command uses the 250 reply code
   snprintf(connection->response, FTP_SERVER_MAX_LINE_LEN,
      "250 Directory changed to %s\r\n",
      ftpServerStripHomeDir(connection, connection->currentDir));
}


/**
 * @brief LIST command processing
 *
 * The LIST command is used to list the content of a directory
 *
 * @param[in] context Pointer to the FTP server context
 * @param[in] connection Pointer to the client connection
 * @param[in] param Command line parameters
 **/

void ftpServerProcessList(FtpServerContext *context,
   FtpClientConnection *connection, char_t *param)
{
   error_t error;
   uint_t perm;

   //Ensure the user is logged in
   if(!connection->userLoggedIn)
   {
      //Format response message
      strcpy(connection->response, "530 Not logged in\r\n");
      //Exit immediately
      return;
   }

   //Any option flags
   while(*param == '-')
   {
      //Skip option flags
      while(*param != ' ' && *param != '\0')
         param++;
      //Skip whitespace characters
      while(*param == ' ')
         param++;
   }

   //The pathname is optional
   if(*param == '\0')
   {
      //Use current directory if no pathname is specified
      strcpy(connection->path, connection->currentDir);
   }
   else
   {
      //Retrieve the full pathname
      error = ftpServerGetPath(connection, param,
         connection->path, FTP_SERVER_MAX_PATH_LEN);

      //Any error to report?
      if(error)
      {
         //The specified pathname is not valid...
         strcpy(connection->response, "501 Invalid parameter\r\n");
         //Exit immediately
         return;
      }
   }

   //Retrieve permissions for the specified directory
   perm = ftpServerGetFilePermissions(context, connection, connection->path);

   //Insufficient access rights?
   if(!(perm & FTP_FILE_PERM_READ))
   {
      //Report an error
      strcpy(connection->response, "550 Access denied\r\n");
      //Exit immediately
      return;
   }

   //Open specified directory for reading
   connection->dir = fsDirOpen(connection->path);

   //Failed to open the directory?
   if(!connection->dir)
   {
      //Report an error
      strcpy(connection->response, "550 Directory not found\r\n");
      //Exit immediately
      return;
   }

   //Check current data transfer mode
   if(connection->passiveMode)
   {
      //Check whether the data connection is already opened
      if(connection->dataState == FTP_DATA_STATE_IDLE)
         connection->dataState = FTP_DATA_STATE_SEND;
   }
   else
   {
      //Open the data connection
      error = ftpServerOpenDataConnection(connection);

      //Any error to report?
      if(error)
      {
         //Clean up side effects
         fsDirClose(connection->dir);
         //Format response
         strcpy(connection->response, "450 Can't open data connection\r\n");
         //Exit immediately
         return;
      }

      //The data connection is ready to send data
      connection->dataState = FTP_DATA_STATE_SEND;
   }

   //Flush transmission buffer
   connection->bufferLength = 0;
   connection->bufferPos = 0;

   //LIST command is being processed
   connection->controlState = FTP_CONTROL_STATE_LIST;

   //Format response message
   strcpy(connection->response, "150 Opening data connection\r\n");
}


/**
 * @brief MKD command processing
 *
 * The MKD command causes the directory specified in the pathname
 * to be created as a directory
 *
 * @param[in] context Pointer to the FTP server context
 * @param[in] connection Pointer to the client connection
 * @param[in] param Command line parameters
 **/

void ftpServerProcessMkd(FtpServerContext *context,
   FtpClientConnection *connection, char_t *param)
{
   error_t error;
   uint_t perm;

   //Ensure the user is logged in
   if(!connection->userLoggedIn)
   {
      //Format response message
      strcpy(connection->response, "530 Not logged in\r\n");
      //Exit immediately
      return;
   }

   //The argument specifies the pathname
   if(*param == '\0')
   {
      //The argument is missing...
      strcpy(connection->response, "501 Missing parameter\r\n");
      //Exit immediately
      return;
   }

   //Retrieve the full pathname
   error = ftpServerGetPath(connection, param,
      connection->path, FTP_SERVER_MAX_PATH_LEN);

   //Any error to report?
   if(error)
   {
      //The specified pathname is not valid...
      strcpy(connection->response, "501 Invalid parameter\r\n");
      //Exit immediately
      return;
   }

   //Retrieve permissions for the specified directory
   perm = ftpServerGetFilePermissions(context, connection, connection->path);

   //Insufficient access rights?
   if(!(perm & FTP_FILE_PERM_WRITE))
   {
      //Report an error
      strcpy(connection->response, "550 Access denied\r\n");
      //Exit immediately
      return;
   }

   //Create the specified directory
   error = fsDirCreate(connection->path);

   //Any error to report?
   if(error)
   {
      //The specified pathname is not valid...
      strcpy(connection->response, "550 Can't create directory\r\n");
      //Exit immediately
      return;
   }

   //The specified directory was successfully created
   snprintf(connection->response, FTP_SERVER_MAX_LINE_LEN,
      "257 \"%s\" created\r\n",
      ftpServerStripHomeDir(connection, connection->path));
}


/**
 * @brief RMD command processing
 *
 * The RMD command causes the directory specified in the pathname
 * to be removed
 *
 * @param[in] context Pointer to the FTP server context
 * @param[in] connection Pointer to the client connection
 * @param[in] param Command line parameters
 **/

void ftpServerProcessRmd(FtpServerContext *context,
   FtpClientConnection *connection, char_t *param)
{
   error_t error;
   uint_t perm;

   //Ensure the user is logged in
   if(!connection->userLoggedIn)
   {
      //Format response message
      strcpy(connection->response, "530 Not logged in\r\n");
      //Exit immediately
      return;
   }

   //The argument specifies the directory to be removed
   if(*param == '\0')
   {
      //The argument is missing...
      strcpy(connection->response, "501 Missing parameter\r\n");
      //Exit immediately
      return;
   }

   //Retrieve the full pathname of the directory
   error = ftpServerGetPath(connection, param,
      connection->path, FTP_SERVER_MAX_PATH_LEN);

   //Any error to report?
   if(error)
   {
      //The specified pathname is not valid...
      strcpy(connection->response, "501 Invalid parameter\r\n");
      //Exit immediately
      return;
   }

   //Retrieve permissions for the specified directory
   perm = ftpServerGetFilePermissions(context, connection, connection->path);

   //Insufficient access rights?
   if(!(perm & FTP_FILE_PERM_WRITE))
   {
      //Report an error
      strcpy(connection->response, "550 Access denied\r\n");
      //Exit immediately
      return;
   }

   //Remove the specified directory
   error = fsDirRemove(connection->path);

   //Any error to report?
   if(error)
   {
      //The specified directory cannot be deleted...
      strcpy(connection->response, "550 Can't remove directory\r\n");
      //Exit immediately
      return;
   }

   //The specified directory was successfully removed
   strcpy(connection->response, "250 Directory removed\r\n");
}


/**
 * @brief SIZE command processing
 *
 * The SIZE command is used to obtain the transfer size of the specified file
 *
 * @param[in] context Pointer to the FTP server context
 * @param[in] connection Pointer to the client connection
 * @param[in] param Command line parameters
 **/

void ftpServerProcessSize(FtpServerContext *context,
   FtpClientConnection *connection, char_t *param)
{
   error_t error;
   uint_t perm;
   uint32_t size;

   //Ensure the user is logged in
   if(!connection->userLoggedIn)
   {
      //Format response message
      strcpy(connection->response, "530 Not logged in\r\n");
      //Exit immediately
      return;
   }

   //The argument specifies the pathname of the file
   if(*param == '\0')
   {
      //The argument is missing...
      strcpy(connection->response, "501 Missing parameter\r\n");
      //Exit immediately
      return;
   }

   //Retrieve the full pathname
   error = ftpServerGetPath(connection, param,
      connection->path, FTP_SERVER_MAX_PATH_LEN);

   //Any error to report?
   if(error)
   {
      //The specified pathname is not valid...
      strcpy(connection->response, "501 Invalid parameter\r\n");
      //Exit immediately
      return;
   }

   //Retrieve permissions for the specified directory
   perm = ftpServerGetFilePermissions(context, connection, connection->path);

   //Insufficient access rights?
   if(!(perm & FTP_FILE_PERM_LIST) && !(perm & FTP_FILE_PERM_READ))
   {
      //Report an error
      strcpy(connection->response, "550 Access denied\r\n");
      //Exit immediately
      return;
   }

   //Retrieve the size of the specified file
   error = fsFileGetSize(connection->path, &size);

   //Any error to report?
   if(error)
   {
      //Report an error
      strcpy(connection->response, "550 File not found\r\n");
      //Exit immediately
      return;
   }

   //Format response message
   sprintf(connection->response, "213 %" PRIu32 "\r\n", size);
}


/**
 * @brief RETR command processing
 *
 * The RETR command is used to retrieve the content of the specified file
 *
 * @param[in] context Pointer to the FTP server context
 * @param[in] connection Pointer to the client connection
 * @param[in] param Command line parameters
 **/

void ftpServerProcessRetr(FtpServerContext *context,
   FtpClientConnection *connection, char_t *param)
{
   error_t error;
   uint_t perm;

   //Ensure the user is logged in
   if(!connection->userLoggedIn)
   {
      //Format response message
      strcpy(connection->response, "530 Not logged in\r\n");
      //Exit immediately
      return;
   }

   //The argument specifies the pathname of the file to read
   if(*param == '\0')
   {
      //The argument is missing...
      strcpy(connection->response, "501 Missing parameter\r\n");
      //Exit immediately
      return;
   }

   //Retrieve the full pathname
   error = ftpServerGetPath(connection, param,
      connection->path, FTP_SERVER_MAX_PATH_LEN);

   //Any error to report?
   if(error)
   {
      //The specified pathname is not valid...
      strcpy(connection->response, "501 Invalid parameter\r\n");
      //Exit immediately
      return;
   }

   //Retrieve permissions for the specified directory
   perm = ftpServerGetFilePermissions(context, connection, connection->path);

   //Insufficient access rights?
   if(!(perm & FTP_FILE_PERM_READ))
   {
      //Report an error
      strcpy(connection->response, "550 Access denied\r\n");
      //Exit immediately
      return;
   }

   //Open specified file for reading
   connection->file = fsFileOpen(connection->path, FS_FILE_MODE_READ);

   //Failed to open the file?
   if(!connection->file)
   {
      //Report an error
      strcpy(connection->response, "550 File not found\r\n");
      //Exit immediately
      return;
   }

   //Check current data transfer mode
   if(connection->passiveMode)
   {
      //Check whether the data connection is already opened
      if(connection->dataState == FTP_DATA_STATE_IDLE)
         connection->dataState = FTP_DATA_STATE_SEND;
   }
   else
   {
      //Open the data connection
      error = ftpServerOpenDataConnection(connection);

      //Any error to report?
      if(error)
      {
         //Clean up side effects
         fsFileClose(connection->file);
         //Format response
         strcpy(connection->response, "450 Can't open data connection\r\n");
         //Exit immediately
         return;
      }

      //The data connection is ready to send data
      connection->dataState = FTP_DATA_STATE_SEND;
   }

   //Flush transmission buffer
   connection->bufferLength = 0;
   connection->bufferPos = 0;

   //RETR command is being processed
   connection->controlState = FTP_CONTROL_STATE_RETR;

   //Format response message
   strcpy(connection->response, "150 Opening data connection\r\n");
}


/**
 * @brief STOR command processing
 *
 * The STOR command is used to store data to the specified file
 *
 * @param[in] context Pointer to the FTP server context
 * @param[in] connection Pointer to the client connection
 * @param[in] param Command line parameters
 **/

void ftpServerProcessStor(FtpServerContext *context,
   FtpClientConnection *connection, char_t *param)
{
   error_t error;
   uint_t perm;

   //Ensure the user is logged in
   if(!connection->userLoggedIn)
   {
      //Format response message
      strcpy(connection->response, "530 Not logged in\r\n");
      //Exit immediately
      return;
   }

   //The argument specifies the pathname of the file to written
   if(*param == '\0')
   {
      //The argument is missing...
      strcpy(connection->response, "501 Missing parameter\r\n");
      //Exit immediately
      return;
   }

   //Retrieve the full pathname
   error = ftpServerGetPath(connection, param,
      connection->path, FTP_SERVER_MAX_PATH_LEN);

   //Any error to report?
   if(error)
   {
      //The specified pathname is not valid...
      strcpy(connection->response, "501 Invalid parameter\r\n");
      //Exit immediately
      return;
   }

   //Retrieve permissions for the specified directory
   perm = ftpServerGetFilePermissions(context, connection, connection->path);

   //Insufficient access rights?
   if(!(perm & FTP_FILE_PERM_WRITE))
   {
      //Report an error
      strcpy(connection->response, "550 Access denied\r\n");
      //Exit immediately
      return;
   }

   //Open specified file for writing
   connection->file = fsFileOpen(connection->path,
      FS_FILE_MODE_WRITE | FS_FILE_MODE_CREATE);

   //Failed to open the file?
   if(!connection->file)
   {
      //Report an error
      strcpy(connection->response, "550 File not found\r\n");
      //Exit immediately
      return;
   }

   //Check current data transfer mode
   if(connection->passiveMode)
   {
      //Check whether the data connection is already opened
      if(connection->dataState == FTP_DATA_STATE_IDLE)
         connection->dataState = FTP_DATA_STATE_RECEIVE;
   }
   else
   {
      //Open the data connection
      error = ftpServerOpenDataConnection(connection);

      //Any error to report?
      if(error)
      {
         //Clean up side effects
         fsFileClose(connection->file);
         //Format response
         strcpy(connection->response, "450 Can't open data connection\r\n");
         //Exit immediately
         return;
      }

      //The data connection is ready to receive data
      connection->dataState = FTP_DATA_STATE_RECEIVE;
   }

   //Flush reception buffer
   connection->bufferLength = 0;
   connection->bufferPos = 0;

   //STOR command is being processed
   connection->controlState = FTP_CONTROL_STATE_STOR;

   //Format response message
   strcpy(connection->response, "150 Opening data connection\r\n");
}


/**
 * @brief APPE command processing
 *
 * The APPE command is used to append data to the the specified file
 *
 * @param[in] context Pointer to the FTP server context
 * @param[in] connection Pointer to the client connection
 * @param[in] param Command line parameters
 **/

void ftpServerProcessAppe(FtpServerContext *context,
   FtpClientConnection *connection, char_t *param)
{
   error_t error;
   uint_t perm;

   //Ensure the user is logged in
   if(!connection->userLoggedIn)
   {
      //Format response message
      strcpy(connection->response, "530 Not logged in\r\n");
      //Exit immediately
      return;
   }

   //The argument specifies the pathname of the file to written
   if(*param == '\0')
   {
      //The argument is missing...
      strcpy(connection->response, "501 Missing parameter\r\n");
      //Exit immediately
      return;
   }

   //Retrieve the full pathname
   error = ftpServerGetPath(connection, param,
      connection->path, FTP_SERVER_MAX_PATH_LEN);

   //Any error to report?
   if(error)
   {
      //The specified pathname is not valid...
      strcpy(connection->response, "501 Invalid parameter\r\n");
      //Exit immediately
      return;
   }

   //Retrieve permissions for the specified directory
   perm = ftpServerGetFilePermissions(context, connection, connection->path);

   //Insufficient access rights?
   if(!(perm & FTP_FILE_PERM_WRITE))
   {
      //Report an error
      strcpy(connection->response, "550 Access denied\r\n");
      //Exit immediately
      return;
   }

   //Open specified file for writing
   connection->file = fsFileOpen(connection->path,
      FS_FILE_MODE_WRITE | FS_FILE_MODE_CREATE);

   //Failed to open the file?
   if(!connection->file)
   {
      //Report an error
      strcpy(connection->response, "550 File not found\r\n");
      //Exit immediately
      return;
   }

   //Move to the end of the file
   error = fsFileSeek(connection->file, 0, FS_SEEK_END);

   //Any error to report?
   if(error)
   {
      //Clean up side effects
      fsFileClose(connection->file);
      //Format response
      strcpy(connection->response, "550 File unavailable\r\n");
   }

   //Check current data transfer mode
   if(connection->passiveMode)
   {
      //Check whether the data connection is already opened
      if(connection->dataState == FTP_DATA_STATE_IDLE)
         connection->dataState = FTP_DATA_STATE_RECEIVE;
   }
   else
   {
      //Open the data connection
      error = ftpServerOpenDataConnection(connection);

      //Any error to report?
      if(error)
      {
         //Clean up side effects
         fsFileClose(connection->file);
         //Format response
         strcpy(connection->response, "450 Can't open data connection\r\n");
         //Exit immediately
         return;
      }

      //The data connection is ready to receive data
      connection->dataState = FTP_DATA_STATE_RECEIVE;
   }

   //Flush reception buffer
   connection->bufferLength = 0;
   connection->bufferPos = 0;

   //APPE command is being processed
   connection->controlState = FTP_CONTROL_STATE_APPE;

   //Format response message
   strcpy(connection->response, "150 Opening data connection\r\n");
}


/**
 * @brief RNFR command processing
 *
 * The RNFR command specifies the old pathname of the file which is
 * to be renamed
 *
 * @param[in] context Pointer to the FTP server context
 * @param[in] connection Pointer to the client connection
 * @param[in] param Command line parameters
 **/

void ftpServerProcessRnfr(FtpServerContext *context,
   FtpClientConnection *connection, char_t *param)
{
   error_t error;
   uint_t perm;

   //Ensure the user is logged in
   if(!connection->userLoggedIn)
   {
      //Format response message
      strcpy(connection->response, "530 Not logged in\r\n");
      //Exit immediately
      return;
   }

   //The argument specifies the file to be renamed
   if(*param == '\0')
   {
      //The argument is missing...
      strcpy(connection->response, "501 Missing parameter\r\n");
      //Exit immediately
      return;
   }

   //Retrieve the full pathname
   error = ftpServerGetPath(connection, param,
      connection->path, FTP_SERVER_MAX_PATH_LEN);

   //Any error to report?
   if(error)
   {
      //The specified pathname is not valid...
      strcpy(connection->response, "501 Invalid parameter\r\n");
      //Exit immediately
      return;
   }

   //Retrieve permissions for the specified directory
   perm = ftpServerGetFilePermissions(context, connection, connection->path);

   //Insufficient access rights?
   if(!(perm & FTP_FILE_PERM_WRITE))
   {
      //Report an error
      strcpy(connection->response, "550 Access denied\r\n");
      //Exit immediately
      return;
   }

   //Make sure the file exists
   if(!fsFileExists(connection->path) && !fsDirExists(connection->path))
   {
      //No such file or directory...
      strcpy(connection->response, "550 File not found\r\n");
      //Exit immediately
      return;
   }

   //This command must be immediately followed by a RNTO command
   connection->controlState = FTP_CONTROL_STATE_RNFR;
   //Format the response message
   strcpy(connection->response, "350 File exists, ready for destination name\r\n");
}


/**
 * @brief RNTO command processing
 *
 * The RNTO command specifies the new pathname of the file specified
 * in the immediately preceding RNFR command
 *
 * @param[in] context Pointer to the FTP server context
 * @param[in] connection Pointer to the client connection
 * @param[in] param Command line parameters
 **/

void ftpServerProcessRnto(FtpServerContext *context,
   FtpClientConnection *connection, char_t *param)
{
   error_t error;
   uint_t perm;
   char_t newPath[FTP_SERVER_MAX_PATH_LEN];

   //Ensure the user is logged in
   if(!connection->userLoggedIn)
   {
      //Format response message
      strcpy(connection->response, "530 Not logged in\r\n");
      //Exit immediately
      return;
   }

   //This command must immediately follow a RNFR command
   if(connection->controlState != FTP_CONTROL_STATE_RNFR)
   {
      //Switch to idle state
      connection->controlState = FTP_CONTROL_STATE_IDLE;
      //Report an error
      strcpy(connection->response, "503 Bad sequence of commands\r\n");
      //Exit immediately
      return;
   }

   //Switch to idle state
   connection->controlState = FTP_CONTROL_STATE_IDLE;

   //The argument specifies the new pathname
   if(*param == '\0')
   {
      //The argument is missing...
      strcpy(connection->response, "501 Missing parameter\r\n");
      //Exit immediately
      return;
   }

   //Retrieve the full pathname
   error = ftpServerGetPath(connection, param,
      newPath, FTP_SERVER_MAX_PATH_LEN);

   //Any error to report?
   if(error)
   {
      //The specified pathname is not valid...
      strcpy(connection->response, "501 Invalid parameter\r\n");
      //Exit immediately
      return;
   }

   //Retrieve permissions for the specified directory
   perm = ftpServerGetFilePermissions(context, connection, newPath);

   //Insufficient access rights?
   if(!(perm & FTP_FILE_PERM_WRITE))
   {
      //Report an error
      strcpy(connection->response, "550 Access denied\r\n");
      //Exit immediately
      return;
   }

   //Check whether the file name already exists
   if(fsFileExists(newPath) || fsDirExists(newPath))
   {
      //Report an error
      strcpy(connection->response, "550 File already exists\r\n");
      //Exit immediately
      return;
   }

   //Rename the specified file
   error = fsFileRename(connection->path, newPath);

   //Any error to report?
   if(error)
   {
      //The specified file cannot be renamed
      strcpy(connection->response, "550 Can't rename file\r\n");
      //Exit immediately
      return;
   }

   //The specified file was successfully deleted
   strcpy(connection->response, "250 File renamed\r\n");
}


/**
 * @brief DELE command processing
 *
 * The DELE command causes the file specified in the pathname to be
 * deleted at the server site
 *
 * @param[in] context Pointer to the FTP server context
 * @param[in] connection Pointer to the client connection
 * @param[in] param Command line parameters
 **/

void ftpServerProcessDele(FtpServerContext *context,
   FtpClientConnection *connection, char_t *param)
{
   error_t error;
   uint_t perm;

   //Ensure the user is logged in
   if(!connection->userLoggedIn)
   {
      //Format response message
      strcpy(connection->response, "530 Not logged in\r\n");
      //Exit immediately
      return;
   }

   //The argument specifies the file to be deleted
   if(*param == '\0')
   {
      //The argument is missing...
      strcpy(connection->response, "501 Missing parameter\r\n");
      //Exit immediately
      return;
   }

   //Retrieve the full pathname of the file
   error = ftpServerGetPath(connection, param,
      connection->path, FTP_SERVER_MAX_PATH_LEN);

   //Any error to report?
   if(error)
   {
      //The specified pathname is not valid...
      strcpy(connection->response, "501 Invalid parameter\r\n");
      //Exit immediately
      return;
   }

   //Retrieve permissions for the specified directory
   perm = ftpServerGetFilePermissions(context, connection, connection->path);

   //Insufficient access rights?
   if(!(perm & FTP_FILE_PERM_WRITE))
   {
      //Report an error
      strcpy(connection->response, "550 Access denied\r\n");
      //Exit immediately
      return;
   }

   //Delete the specified file
   error = fsFileDelete(connection->path);

   //Any error to report?
   if(error)
   {
      //The specified file cannot be deleted...
      strcpy(connection->response, "550 Can't delete file\r\n");
      //Exit immediately
      return;
   }

   //The specified file was successfully deleted
   strcpy(connection->response, "250 File deleted\r\n");
}

#endif
