summaryrefslogblamecommitdiffstats
path: root/c/src/libnetworking/rtems_servers/ftpd.c
blob: 79a293eb0f4d021240f093543db48646975b3930 (plain) (tree)
1
2
3
4
5
6
7
8
9
                                                        
                                         

                                                                            


                                                                          
                                               
  
                     


                                                               
                                                     


            



















                                                                               





































                                                                              
                                     






                                                                        
                           
                                                                        


                                                                             


        



























































                                                                          

                                                                           
                                                                          






                                                                       
                                                                      



                                                                         
                                                                    











                                                                           






                   
                  
 


                               
                        
                          



















                                                                        
                                    




























                                                                 
      

                         

                                                         

              



                                                                         
                                                                            



                                                                   

                                                                     


                     



















                                 
                 
   

                                   




                                               
 



                           




       
                                                        




                  


                       

 




                                       
 













                                            
                                
  




































                                                      













                                                        

                                                                   







































































                                                                      
































                                                                              


































                                                                        






























































                                                                              








                                        

















                        



































                                                                         
                  
















































                                                                              


                                                                              
           
                          








                                                        

               
                                   

















                                                                          
                             

   
                                       


                                                                        
                         
 
 
 



















                                                                      

 

      
                   
  
                                                    


                                               
                                       

                     
         

   

                                                                 
 
                             


                                         





                                            
 
                                          
   

                                                 



                        
                        
 

             










                                                    
     































                                                                 










                         
 

               
 





                                                
 
         


 

      






















                                                           












                                                             
 




                                            

                                      


                                                         
 





                                            



                        
           
 
                                        

           
                                              

                                                                         
       
                   
   

                                                      
   

                                                                             






                                                
                                            
       

                       
       







                                                              
                                                                          






                                                                        
      
                                     
       


                                       


                                                               

     
      
                                               
       
             
                                                                 




                         
                                                                    
                    

                              
     
 
      
                     



                                                       
     


                                                       
     


      





                                                                   


               


                                                    
     
 
                                 
     
                                                      
       
                                 



                  
       
     
                                      
     












                                                                          
                                  

































                                        
                                                     



                    
                                                










                                                   
     

   










                                                                              
 
 








                                                                     
                                                                             








                                                                              
                                       

   
          

















                                                               
       

                                
                   
                         
       

                                   
               












                                                                
          





















                                                         

                                     




                                                           

                                   
   
           

 












                                                
           
                                                                   
 




                                        
                 
             












                                                                        
                                 




                                                   
                                                                          







                                                
                       
                                                                       

                                                                   


                                                                     

                                                
                                                                             




                   





                                                 


 

      
              
  
                                    





                                                
         
  

           
                                                 
 
                     
                                                     
      
                                                 
 

 



































                                                             
 



               
                                                                   






                                               






                                                          
                              












                                                           

 














                                                                            
           
                                                        
 


















































































                                                                              
                                              











                                                                               


                                                                             


                        
                
       











                                                                         


 










                                                               




                      
                    
            
                        


                                                             





                               
       
                                   
              
                 



            

                 


           

























































                                                                            



                     
           
                                                             
 
                           
                        
 

                  
                           
   
                             
   
                                
   




                                 

                                  
                                
   
                                 

                               
                                
   
                                 

                                 
                                
   
                                 

                                 
                                
   
                                 

                              
                                
   
                                        
   
                                
   
                       



                                              
                            
     








                                                                
                                                        


                                             
                                
   





                                              
                         







                                                
                                
   


                                            
     







                                                      
                                        






                                                   

        
                        
   
                               
   





                                              
                        







                                               
                               
   





                                              
                                                     







                                               
                               
   
                                 
                             
   
                                
   
                            
   
                               
   
                      

      



                                                     

 
 















                                                                               
           
                                
 
                                                               





                                                                       



                        
 

                                                                            
 




                                                        
                                               
 

             



                                                          
       

                                                      

       


                                             
       

                                          


          
                                      
       
     
 
                                                                     
                            


                            


 













                                                                            
           
        
 

                              
                           


                                   



                                                               



                                                              
 
                                                          
                                                                      
                            
                                                                           
                

           

                                                       
               
                                                                              

                                                  



                                
       
                         


          

                                                                      






                                                                         












                                                                     

                                       






                                                                             
         
       

     
                                


 












                                                                         

                       
 















                                                      






                                                































                                                                  
                  

                                    




                                              


                                                                 
 
                          
 
/* FIXME: 1. Parse command is a hack.  We can do better.
 *        2. Some sort of access control?
 *        3. OSV: hooks support seems to be bad, as it requires storing of
 *           entire input file in memory.  Seem to be better to change it to
 *           something more reasonable, like having
 *           'hook_write(void const *buf, int count)' routine that will be
 *           called multiple times while file is being received.
 *        4. OSV: Remove hack with "/dev/null"?
 *
 *  FTP Server Daemon
 *
 *  Submitted by: Jake Janovetz <janovetz@tempest.ece.uiuc.edu>
 *
 *  Changed by:   Sergei Organov <osv@javad.ru> (OSV)
 *
 *  Changes:
 *
 *    2001-01-31        Sergei Organov <osv@javad.ru>
 *
 *      * Hacks with current dir and root dir removed in favor of new libio
 *        support for task-local current and root directories.
 *
 *    2001-01-30        Sergei Organov <osv@javad.ru>
 *
 *      * Bug in `close_data_socket()' introduced by previous change fixed.
 *      * `command_pasv()' changed to set timeout on socket we are listening on
 *        and code fixed to don't close socket twice on error.
 *      * `serr()' changed to clear `errno'.
 *      * `data_socket()' changed to clear `errno' before `bind()'.
 *      * `session()' changed to clear `errno' before processing session.
 *
 *    2001-01-29        Sergei Organov <osv@javad.ru>
 *
 *      * `close_data_socket()' fixed to close both active and passive sockets
 *      * Initialize info->data_socket to -1 in `daemon()'
 *      * Initialize `fname' to empty string  in `exec_command()'
 *
 *    2001-01-22        Sergei Organov <osv@javad.ru>
 *
 *      * Timeouts on sockets implemented. 'idle' field added to
 *        configuration. No timeout by default to keep backward compatibility.
 *        Note: SITE IDLE command not implemented yet.
 *      * Basic global access control implemented. 'access' field added to
 *        configuration. No access limitations by default to keep backward
 *        compatibility.
 *
 *    2001-01-17        Sergei Organov <osv@javad.ru>
 *
 *      * Anchor data socket for active mode (using self IP and port 20.)
 *      * Fixed default data port support (still not tested).
 *      * Don't allow IP address different from originating host in
 *        PORT command to improve security.
 *      * Fixed bug in MDTM command.
 *      * Check for correctness of parsing of argument in command_port().
 *      * Fixed squeeze_path() to don't allow names like 'NAME/smth' where
 *        'NAME' is not a directory.
 *      * Command parsing a little bit improved: command names are now
 *        converted to upper-case to be more compatible with RFC (command
 *        names are not case-sensitive.)
 *      * Reformat comments so that they have RTEMS look-and-feel.
 *
 *    2001-01-16        Sergei Organov <osv@javad.ru>
 *
 *      * Fixed DELE, SITE CHMOD, RMD, MKD broken by previous changes
 *      * True ASCII mode implemented (doesn't work for hooks and /dev/null)
 *      * Passive mode implemented, PASV command added.
 *      * Default port for data connection could be used (untested, can't find
 *        ftp client that doesn't send PORT command)
 *      * SYST reply changed to UNIX, as former RTEMS isn't registered name.
 *      * Reply codes reviewed and fixed.
 *
 *    2001-01-08        Sergei Organov <osv@javad.ru>
 *
 *      * use pool of pre-created threads to handle sessions
 *      * LIST output now similar to what "/bin/ls -al" would output, thus
 *        FTP clients could parse it.
 *      * LIST NAME now works (both for files and directories)
 *      * keep track of CWD for every session separately
 *      * ability to specify root directory name in configuration table
 *      * options sent in commands are ignored, thus LIST -al FILE works
 *      * added support for NLST, CDUP and MDTM commands
 *      * buffers are allocated on stack instead of heap where possible
 *      * drop using of task notepad to pass parameters - use function
 *        arguments instead
 *      * various bug-fixes, e.g., use of PF_INET in socket() instead of
 *        AF_INET, use snprintf() instead of sprintf() everywhere for safety,
 *        etc.
 *
 *  $Id$
 */

/*************************************************************************
 *                                 ftpd.c
 *************************************************************************
 * Description:
 *
 *    This file contains the daemon which services requests that appear
 *    on the FTP port.  This server is compatible with FTP, but it
 *    also provides 'hooks' to make it usable in situations where files
 *    are not used/necessary.  Once the server is started, it runs
 *    forever.
 *
 *
 *    Organization:
 *
 *       The FTP daemon is started upon boot along with a (configurable)
 *       number of tasks to handle sessions.  It runs all the time and
 *       waits for connections on the known FTP port (21).  When
 *       a connection is made, it wakes-up a 'session' task.  That
 *       session then interacts with the remote host.  When the session
 *       is complete, the session task goes to sleep.  The daemon still
 *       runs, however.
 *
 *
 * Supported commands are:
 *
 * RETR xxx     - Sends a file from the client.
 * STOR xxx     - Receives a file from the client.  xxx = filename.
 * LIST xxx     - Sends a file list to the client.
 * NLST xxx     - Sends a file list to the client.
 * USER         - Does nothing.
 * PASS         - Does nothing.
 * SYST         - Replies with the system type (`RTEMS').
 * DELE xxx     - Delete file xxx.
 * MKD xxx      - Create directory xxx.
 * RMD xxx      - Remove directory xxx.
 * PWD          - Print working directory.
 * CWD xxx      - Change working directory.
 * CDUP         - Change to upper directory.
 * SITE CHMOD xxx yyy - Change permissions on file yyy to xxx.
 * PORT a,b,c,d,x,y   - Setup for a data port to IP address a.b.c.d
 *                      and port (x*256 + y).
 * MDTM xxx     - Send file modification date/time to the client.
 *                xxx = filename.
 * PASV         - Use passive mode data connection.
 *
 *
 * The public routines contained in this file are:
 *
 *    rtems_initialize_ftpd - Initializes and starts the server daemon,
 *                            then returns to its caller.
 *
 *------------------------------------------------------------------------
 * Jake Janovetz
 * University of Illinois
 * 1406 West Green Street
 * Urbana IL  61801
 *************************************************************************
 * Change History:
 *  12/01/97   - Creation (JWJ)
 *  2001-01-08 - Changes by OSV
 *************************************************************************/

/*************************************************************************
 * Meanings of first and second digits of reply codes:
 *
 * Reply:  Description:
 *-------- --------------
 *  1yz    Positive preliminary reply.  The action is being started but
 *         expect another reply before sending another command.
 *  2yz    Positive completion reply.  A new command can be sent.
 *  3yz    Positive intermediate reply.  The command has been accepted
 *         but another command must be sent.
 *  4yz    Transient negative completion reply.  The requested action did
 *         not take place, but the error condition is temporary so the
 *         command can be reissued later.
 *  5yz    Permanent negative completion reply.  The command was not
 *         accepted and should not be retried.
 *-------------------------------------------------------------------------
 *  x0z    Syntax errors.
 *  x1z    Information.
 *  x2z    Connections.  Replies referring to the control or data
 *         connections.
 *  x3z    Authentication and accounting.  Replies for the login or
 *         accounting commands.
 *  x4z    Unspecified.
 *  x5z    Filesystem status.
 *************************************************************************/

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

#include <rtems.h>
#include <rtems/rtems_bsdnet.h>
#include <rtems/error.h>
#include <rtems/libio.h>
#include <rtems/userenv.h>
#include <syslog.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/ftp.h>
#include <netinet/in.h>

#include "ftpd.h"


#ifdef __GNUC__
/* change to #if 1 to disable syslog entirely */
#if 0
#undef  syslog
#define syslog(a, b, ...) while(0){}
#endif
#endif

#define FTPD_SERVER_MESSAGE  "RTEMS FTP server (Version 1.1-JWJ) ready."

#define FTPD_SYSTYPE "UNIX Type: L8"

/* Seem to be unused */
#if 0
#define FTPD_WELCOME_MESSAGE \
   "Welcome to the RTEMS FTP server.\n" \
   "\n" \
   "Login accepted.\n"
#endif

/* Various buffer sizes */
enum
{
  FTPD_BUFSIZE  = 256,       /* Size for temporary buffers */
  FTPD_DATASIZE = 1024,      /* Size for file transfer buffers */
  FTPD_STACKSIZE = 8 * 1024, /* Tasks stack size */
};

/* Event to be used by session tasks for waiting */
enum
{
  FTPD_RTEMS_EVENT = RTEMS_EVENT_1
};

/* Configuration table */
extern struct rtems_ftpd_configuration rtems_ftpd_configuration;

/* this is not prototyped in strict ansi mode */
FILE *fdopen (int fildes, const char *mode);

/*PAGE
 * SessionInfo structure.
 *
 * The following structure is allocated for each session.
 */
typedef struct
{
  struct sockaddr_in  ctrl_addr;   /* Control connection self address */
  struct sockaddr_in  data_addr;   /* Data address set by PORT command */
  struct sockaddr_in  def_addr;    /* Default address for data */
  int                 use_default; /* 1 - use default address for data */
  FILE                *ctrl_fp;    /* File pointer for control connection */
  int                 ctrl_socket; /* Socket for ctrl connection */
  int                 pasv_socket; /* Socket for PASV connection */
  int                 data_socket; /* Socket for data connection */
  int                 idle;        /* Timeout in seconds */
  int                 xfer_mode;   /* Transfer mode (ASCII/binary) */
  rtems_id            tid;         /* Task id */
} FTPD_SessionInfo_t;


/*
 * TaskPool structure.
 */
typedef struct
{
  FTPD_SessionInfo_t    *info;
  FTPD_SessionInfo_t    **queue;
  int                   count;
  int                   head;
  int                   tail;
  rtems_id              mutex;
  rtems_id              sem;
} FTPD_TaskPool_t;

/*
 * Task pool instance.
 */
static FTPD_TaskPool_t task_pool;

/*
 * Root directory
 */

static char const* ftpd_root = "/";

/*
 * Default idle timeout for sockets in seconds.
 */
static int ftpd_timeout = 0;

/*
 * Global access flags.
 */
static int ftpd_access = 0;

/*PAGE
 *
 * serr
 *
 * Return error string corresponding to current 'errno'.
 *
 */
static char const*
serr(void)
{
  int err = errno;
  errno = 0;
  return strerror(err);
}

/*PAGE
 *
 * Utility routines for access control.
 *
 */

static int
can_read(void)
{
  return (ftpd_access & FTPD_NO_READ) == 0;
}

static int
can_write(void)
{
  return (ftpd_access & FTPD_NO_WRITE) == 0;
}

/*PAGE
 *
 * Task pool management routines
 *
 */


/*PAGE
 *
 * task_pool_done
 *
 * Cleanup task pool.
 *
 * Input parameters:
 *   count - number of entries in task pool to cleanup
 *
 * Output parameters:
 *   NONE
 *
 */
static void
task_pool_done(int count)
{
  int i;
  for(i = 0; i < count; ++i)
    rtems_task_delete(task_pool.info[i].tid);
  if(task_pool.info)
    free(task_pool.info);
  if(task_pool.queue)
    free(task_pool.queue);
  if(task_pool.mutex != (rtems_id)-1)
    rtems_semaphore_delete(task_pool.mutex);
  if(task_pool.sem != (rtems_id)-1)
    rtems_semaphore_delete(task_pool.sem);
  task_pool.info = 0;
  task_pool.queue = 0;
  task_pool.count = 0;
  task_pool.sem = -1;
  task_pool.mutex = -1;
}

/*PAGE
 *
 * task_pool_init
 *
 * Initialize task pool.
 *
 * Input parameters:
 *   count    - number of entries in task pool to create
 *   priority - priority tasks are started with
 *
 * Output parameters:
 *   returns 1 on success, 0 on failure.
 *
 */
static void session(rtems_task_argument arg); /* Forward declare */

static int
task_pool_init(int count, rtems_task_priority priority)
{
  int i;
  rtems_status_code sc;
  char id = 'a';

  task_pool.count = 0;
  task_pool.head = task_pool.tail = 0;
  task_pool.mutex = (rtems_id)-1;
  task_pool.sem   = (rtems_id)-1;

  sc = rtems_semaphore_create(
    rtems_build_name('F', 'T', 'P', 'M'),
    1,
    RTEMS_DEFAULT_ATTRIBUTES
    | RTEMS_BINARY_SEMAPHORE
    | RTEMS_INHERIT_PRIORITY
    | RTEMS_PRIORITY,
    RTEMS_NO_PRIORITY,
    &task_pool.mutex);

  if(sc == RTEMS_SUCCESSFUL)
    sc = rtems_semaphore_create(
      rtems_build_name('F', 'T', 'P', 'S'),
      count,
      RTEMS_DEFAULT_ATTRIBUTES,
      RTEMS_NO_PRIORITY,
      &task_pool.sem);

  if(sc != RTEMS_SUCCESSFUL) {
    task_pool_done(0);
    syslog(LOG_ERR, "ftpd: Can not create semaphores");
    return 0;
  }

  task_pool.info = (FTPD_SessionInfo_t*)
    malloc(sizeof(FTPD_SessionInfo_t) * count);
  task_pool.queue = (FTPD_SessionInfo_t**)
    malloc(sizeof(FTPD_SessionInfo_t*) * count);
  if (NULL == task_pool.info || NULL == task_pool.queue)
  {
    task_pool_done(0);
    syslog(LOG_ERR, "ftpd: Not enough memory");
    return 0;
  }

  for(i = 0; i < count; ++i)
  {
    FTPD_SessionInfo_t *info = &task_pool.info[i];
    sc = rtems_task_create(rtems_build_name('F', 'T', 'P', id),
      priority, FTPD_STACKSIZE,
      RTEMS_PREEMPT | RTEMS_NO_TIMESLICE |
      RTEMS_NO_ASR | RTEMS_INTERRUPT_LEVEL(0),
      RTEMS_NO_FLOATING_POINT | RTEMS_LOCAL,
      &info->tid);
    if (sc == RTEMS_SUCCESSFUL)
    {
      sc = rtems_task_start(
        info->tid, session, (rtems_task_argument)info);
      if (sc != RTEMS_SUCCESSFUL)
        task_pool_done(i);
    }
    else
      task_pool_done(i + 1);
    if (sc != RTEMS_SUCCESSFUL)
    {
      syslog(LOG_ERR, "ftpd: Could not create/start FTPD session: %s",
        rtems_status_text(sc));
      return 0;
    }
    task_pool.queue[i] = task_pool.info + i;
    if (++id > 'z')
      id = 'a';
  }
  task_pool.count = count;
  return 1;
}

/*PAGE
 *
 * task_pool_obtain
 *
 * Obtain free task from task pool.
 *
 * Input parameters:
 *   NONE
 *
 * Output parameters:
 *   returns pointer to the corresponding SessionInfo structure on success,
 *           NULL if there are no free tasks in the pool.
 *
 */
static FTPD_SessionInfo_t*
task_pool_obtain()
{
  FTPD_SessionInfo_t* info = 0;
  rtems_status_code sc;
  sc = rtems_semaphore_obtain(task_pool.sem, RTEMS_NO_WAIT, RTEMS_NO_TIMEOUT);
  if (sc == RTEMS_SUCCESSFUL)
  {
    rtems_semaphore_obtain(task_pool.mutex, RTEMS_WAIT, RTEMS_NO_TIMEOUT);
    info = task_pool.queue[task_pool.head];
    if(++task_pool.head >= task_pool.count)
      task_pool.head = 0;
    rtems_semaphore_release(task_pool.mutex);
  }
  return info;
}

/*PAGE
 *
 * task_pool_release
 *
 * Return task obtained by 'obtain()' back to the task pool.
 *
 * Input parameters:
 *   info  - pointer to corresponding SessionInfo structure.
 *
 * Output parameters:
 *   NONE
 *
 */
static void
task_pool_release(FTPD_SessionInfo_t* info)
{
  rtems_semaphore_obtain(task_pool.mutex, RTEMS_WAIT, RTEMS_NO_TIMEOUT);
  task_pool.queue[task_pool.tail] = info;
  if(++task_pool.tail >= task_pool.count)
    task_pool.tail = 0;
  rtems_semaphore_release(task_pool.mutex);
  rtems_semaphore_release(task_pool.sem);
}

/*
 * End of task pool routines
 */

/*PAGE
 *
 * Function: send_reply
 *
 *
 *    This procedure sends a reply to the client via the control
 *    connection.
 *
 *
 * Input parameters:
 *   code  - 3-digit reply code.
 *   text  - Reply text.
 *
 * Output parameters:
 *   NONE
 */
static void
send_reply(FTPD_SessionInfo_t  *info, int code, char *text)
{
  char const* s = (info->xfer_mode == TYPE_A) ? "\r" : "";
  /* If a text reply exists, add it to the reply data. */
  if (text != NULL)
    fprintf(info->ctrl_fp, "%d %.70s%s\n", code, text, s);
  else
    fprintf(info->ctrl_fp, "%d%s\n", code, s);
  fflush(info->ctrl_fp);
}


/*PAGE
 *
 * close_socket
 *
 * Close socket.
 *
 * Input parameters:
 *   s - socket descriptor.
 *   seconds - number of seconds the timeout should be,
 *             if >= 0 - infinite timeout (no timeout).
 *
 * Output parameters:
 *   returns 1 on success, 0 on failure.
 */
static int
set_socket_timeout(int s, int seconds)
{
  int res = 0;
  struct timeval tv;
  int len = sizeof(tv);

  if(seconds < 0)
    seconds = 0;
  tv.tv_usec = 0;
  tv.tv_sec  = seconds;
  if(0 != setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &tv, len))
    syslog(LOG_ERR, "ftpd: Can't set send timeout on socket: %s.", serr());
  else if(0 != setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, len))
    syslog(LOG_ERR, "ftpd: Can't set receive timeout on socket: %s.", serr());
  else
    res = 1;
  return res;
}

/*PAGE
 *
 * close_socket
 *
 * Close socket.
 *
 * Input parameters:
 *   s - socket descriptor to be closed.
 *
 * Output parameters:
 *   returns 1 on success, 0 on failure
 */
static int
close_socket(int s)
{
  if (0 <= s)
  {
    if (0 != close(s))
    {
      shutdown(s, 2);
      if (0 != close(s))
        return 0;
    }
  }
  return 1;
}

/*PAGE
 *
 * data_socket
 *
 * Create data socket for session.
 *
 * Input parameters:
 *   info - corresponding SessionInfo structure
 *
 * Output parameters:
 *   returns socket descriptor, or -1 if failure
 *
 */
static int
data_socket(FTPD_SessionInfo_t *info)
{
  int s = info->pasv_socket;
  if(0 > s)
  {
    int on = 1;
    s = socket(PF_INET, SOCK_STREAM, 0);
    if(0 > s)
      send_reply(info, 425, "Can't create data socket.");
    else if(0 > setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)))
    {
      close_socket(s);
      s = -1;
    }
    else
    {
      struct sockaddr_in data_source;
      int tries;

      /* anchor socket to avoid multi-homing problems */
      data_source = info->ctrl_addr;
      data_source.sin_port = htons(20); /* ftp-data port */
      for(tries = 1; tries < 10; ++tries)
      {
        errno = 0;
        if(bind(s, (struct sockaddr *)&data_source, sizeof(data_source)) >= 0)
          break;
        if (errno != EADDRINUSE)
          tries = 10;
        else
          rtems_task_wake_after(tries * 10);
      }
      if(tries >= 10)
      {
        send_reply(info, 425, "Can't bind data socket.");
        close_socket(s);
        s = -1;
      }
      else
      {
        struct sockaddr_in *data_dest =
          (info->use_default) ? &info->def_addr : &info->data_addr;
        if(0 > connect(s, (struct sockaddr *)data_dest, sizeof(*data_dest)))
        {
          send_reply(info, 425, "Can't connect data socket.");
          close_socket(s);
          s = -1;
        }
      }
    }
  }
  info->data_socket = s;
  info->use_default = 1;
  if(s >= 0)
    set_socket_timeout(s, info->idle);
  return s;
}

/*PAGE
 *
 * close_data_socket
 *
 * Close data socket for session.
 *
 * Input parameters:
 *   info - corresponding SessionInfo structure
 *
 * Output parameters:
 *   NONE
 *
 */
static void
close_data_socket(FTPD_SessionInfo_t *info)
{
  /* As at most one data socket could be open simultaneously and in some cases
     data_socket == pasv_socket, we select socket to close, then close it. */
  int s = info->data_socket;
  if(0 > s)
    s = info->pasv_socket;
  if(!close_socket(s))
    syslog(LOG_ERR, "ftpd: Error closing data socket.");
  info->data_socket = -1;
  info->pasv_socket = -1;
  info->use_default = 1;
}

/*PAGE
 *
 * close_stream
 *
 * Close control stream of session.
 *
 * Input parameters:
 *   info - corresponding SessionInfo structure
 *
 * Output parameters:
 *   NONE
 *
 */
static void
close_stream(FTPD_SessionInfo_t* info)
{
  if (NULL != info->ctrl_fp)
  {
    if (0 != fclose(info->ctrl_fp))
    {
      syslog(LOG_ERR, "ftpd: Could not close control stream: %s", serr());
    }
    else
      info->ctrl_socket = -1;
  }

  if (!close_socket(info->ctrl_socket))
    syslog(LOG_ERR, "ftpd: Could not close control socket: %s", serr());

  info->ctrl_fp = NULL;
  info->ctrl_socket = -1;
}


/*PAGE
 *
 * send_mode_reply
 *
 * Sends BINARY/ASCII reply string depending on current transfer mode.
 *
 * Input parameters:
 *   info - corresponding SessionInfo structure
 *
 * Output parameters:
 *   NONE
 *
 */
static void
send_mode_reply(FTPD_SessionInfo_t *info)
{
  if(info->xfer_mode == TYPE_I)
    send_reply(info, 150, "Opening BINARY mode data connection.");
  else
    send_reply(info, 150, "Opening ASCII mode data connection.");
}

/*PAGE
 *
 * command_retrieve
 *
 * Perform the "RETR" command (send file to client).
 *
 * Input parameters:
 *   info - corresponding SessionInfo structure
 *   char *filename  - source filename.
 *
 * Output parameters:
 *   NONE
 *
 */
static void
command_retrieve(FTPD_SessionInfo_t  *info, char const *filename)
{
  int                 s = -1;
  int                 fd = -1;
  char                buf[FTPD_DATASIZE];
  int                 res = 0;

  if(!can_read())
  {
    send_reply(info, 550, "Access denied.");
    return;
  }

  if (0 > (fd = open(filename, O_RDONLY)))
  {
    send_reply(info, 550, "Error opening file.");
    return;
  }

  send_mode_reply(info);

  s = data_socket(info);

  if (0 <= s)
  {
    int n = -1;

    if(info->xfer_mode == TYPE_I)
    {
      while ((n = read(fd, buf, FTPD_DATASIZE)) > 0)
      {
        if(send(s, buf, n, 0) != n)
          break;
      }
    }
    else if (info->xfer_mode == TYPE_A)
    {
      int rest = 0;
      while (rest == 0 && (n = read(fd, buf, FTPD_DATASIZE)) > 0)
      {
        char const* e = buf;
        char const* b;
        int i;
        rest = n;
        do
        {
          char lf = '\0';

          b = e;
          for(i = 0; i < rest; ++i, ++e)
          {
            if(*e == '\n')
            {
              lf = '\n';
              break;
            }
          }
          if(send(s, b, i, 0) != i)
            break;
          if(lf == '\n')
          {
            if(send(s, "\r\n", 2, 0) != 2)
              break;
            ++e;
            ++i;
          }
        }
        while((rest -= i) > 0);
      }
    }

    if (0 == n)
    {
      if (0 == close(fd))
      {
        fd = -1;
        res = 1;
      }
    }
  }

  if (-1 != fd)
    close(fd);

  if (0 == res)
    send_reply(info, 451, "File read error.");
  else
    send_reply(info, 226, "Transfer complete.");

  close_data_socket(info);

  return;
}


/*PAGE
 *
 * discard
 *
 * Analog of `write' routine that just discards passed data
 *
 * Input parameters:
 *   fd    - file descriptor (ignored)
 *   buf   - data to write (ignored)
 *   count - number of bytes in `buf'
 *
 * Output parameters:
 *   returns `count'
 *
 */
static ssize_t
discard(int fd, void const* buf, size_t count)
{
  (void)fd;
  (void)buf;
  return count;
}

/*PAGE
 *
 * command_store
 *
 * Performs the "STOR" command (receive data from client).
 *
 * Input parameters:
 *   info - corresponding SessionInfo structure
 *   char *filename   - Destination filename.
 *
 * Output parameters:
 *   NONE
 */
static void
command_store(FTPD_SessionInfo_t *info, char const *filename)
{
  int                    s;
  int                    n;
  unsigned long          size = 0;
  struct rtems_ftpd_hook *usehook = NULL;
  char                   buf[FTPD_DATASIZE];
  int                    res = 1;
  int                    bare_lfs = 0;
  int                    null = 0;
  typedef ssize_t (*WriteProc)(int, void const*, size_t);
  WriteProc              wrt = &write;

  if(!can_write())
  {
    send_reply(info, 550, "Access denied.");
    return;
  }

  send_mode_reply(info);

  s = data_socket(info);
  if(0 > s)
    return;

  null = !strcmp("/dev/null", filename);
  if (null)
  {
    /* File "/dev/null" just throws data away.
     *  FIXME: this is hack.  Using `/dev/null' filesystem entry would be
     *  better.
     */
    wrt = &discard;
  }

  if (!null && rtems_ftpd_configuration.hooks != NULL)
  {

    /* Search our list of hooks to see if we need to do something special. */
    struct rtems_ftpd_hook *hook;
    int i;

    i = 0;
    hook = &rtems_ftpd_configuration.hooks[i++];
    while (hook->filename != NULL)
    {
      if (!strcmp(hook->filename, filename))
      {
        usehook = hook;
        break;
      }
      hook = &rtems_ftpd_configuration.hooks[i++];
    }
  }

  if (usehook != NULL)
  {
    /*
     * OSV: FIXME: Small buffer could be used and hook routine
     * called multiple times instead.  Alternatively, the support could be
     * removed entirely in favor of configuring RTEMS pseudo-device with
     * given name.
     */

    char                *bigBufr;
    size_t filesize = rtems_ftpd_configuration.max_hook_filesize + 1;

    /*
     * Allocate space for our "file".
     */
    bigBufr = (char *)malloc(filesize);
    if (bigBufr == NULL)
    {
      send_reply(info, 451, "Local resource failure: malloc.");
      close_data_socket(info);
      return;
    }

    /*
     * Retrieve the file into our buffer space.
     */
    size = 0;
    while ((n = recv(s, bigBufr + size, filesize - size, 0)) > 0)
    {
      size += n;
    }
    if (size >= filesize)
    {
      send_reply(info, 451, "File too long: buffer size exceeded.");
      free(bigBufr);
      close_data_socket(info);
      return;
    }

    /*
     * Call our hook.
     */
    res = (usehook->hook_function)(bigBufr, size) == 0;
    free(bigBufr);
    if(!res)
    {
      send_reply(info, 451, "File processing failed.");
      close_data_socket(info);
      return;
    }
  }
  else
  {
    /* Data transfer to regular file or /dev/null. */
    int fd = 0;

    if(!null)
      fd = creat(filename,
        S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);

    if (0 > fd)
    {
      send_reply(info, 550, "Error creating file.");
      close_data_socket(info);
      return;
    }

    if(info->xfer_mode == TYPE_I)
    {
      while ((n = recv(s, buf, FTPD_DATASIZE, 0)) > 0)
      {
        if (wrt(fd, buf, n) != n)
        {
          res = 0;
          break;
        }
      }
    }
    else if(info->xfer_mode == TYPE_A)
    {
      int rest = 0;
      int pended_cr = 0;
      while (res && rest == 0 && (n = recv(s, buf, FTPD_DATASIZE, 0)) > 0)
      {
        char const* e = buf;
        char const* b;
        int i;

        rest = n;
        if(pended_cr && *e != '\n')
        {
          char const lf = '\r';
          pended_cr = 0;
          if(wrt(fd, &lf, 1) != 1)
          {
            res = 0;
            break;
          }
        }
        do
        {
          int count;
          int sub = 0;

          b = e;
          for(i = 0; i < rest; ++i, ++e)
          {
            int pcr = pended_cr;
            pended_cr = 0;
            if(*e == '\r')
            {
              pended_cr = 1;
            }
            else if(*e == '\n')
            {
              if(pcr)
              {
                sub = 2;
                ++i;
                ++e;
                break;
              }
              ++bare_lfs;
            }
          }
          if(res == 0)
            break;
          count = i - sub - pended_cr;
          if(count > 0 && wrt(fd, b, count) != count)
          {
            res = 0;
            break;
          }
          if(sub == 2 && wrt(fd, e - 1, 1) != 1)
            res = 0;
        }
        while((rest -= i) > 0);
      }
    }

    if (0 > close(fd) || res == 0)
    {
      send_reply(info, 452, "Error writing file.");
      close_data_socket(info);
      return;
    }
  }

  if (bare_lfs > 0)
  {
    snprintf(buf, FTPD_BUFSIZE,
      "Transfer complete. WARNING! %d bare linefeeds received in ASCII mode.",
      bare_lfs);
    send_reply(info, 226, buf);
  }
  else
    send_reply(info, 226, "Transfer complete.");
  close_data_socket(info);

}


/*PAGE
 *
 * send_dirline
 *
 * Sends one line of LIST command reply corresponding to single file.
 *
 * Input parameters:
 *   s - socket descriptor to send data to
 *   wide - if 0, send only file name.  If not 0, send 'stat' info as well in
 *          "ls -l" format.
 *   curTime - current time
 *   path - path to be prepended to what is given by 'add'
 *   add  - path to be appended to what is given by 'path', the resulting path
 *          is then passed to 'stat()' routine
 *   name - file name to be reported in output
 *   buf  - buffer for temporary data
 *
 * Output parameters:
 *   returns 0 on failure, 1 on success
 *
 */
static int
send_dirline(int s, int wide, time_t curTime, char const* path,
  char const* add, char const* fname, char* buf)
{
  if(wide)
  {
    struct stat stat_buf;

    int plen = strlen(path);
    int alen = strlen(add);
    if(plen == 0)
    {
      buf[plen++] = '/';
      buf[plen] = '\0';
    }
    else
    {
      strcpy(buf, path);
      if(alen > 0 && buf[plen - 1] != '/')
      {
        buf[plen++] = '/';
        if(plen >= FTPD_BUFSIZE)
          return 0;
        buf[plen] = '\0';
      }
    }
    if(plen + alen >= FTPD_BUFSIZE)
      return 0;
    strcpy(buf + plen, add);

    if (stat(buf, &stat_buf) == 0)
    {
      int len;
      struct tm bt;
      time_t tf = stat_buf.st_mtime;
      enum { SIZE = 80 };
      enum { SIX_MONTHS = 365*24*60*60/2 };
      char timeBuf[SIZE];
      gmtime_r(&tf, &bt);
      if(curTime > tf + SIX_MONTHS || tf > curTime + SIX_MONTHS)
        strftime (timeBuf, SIZE, "%b %d  %Y", &bt);
      else
        strftime (timeBuf, SIZE, "%b %d %H:%M", &bt);

      len = snprintf(buf, FTPD_BUFSIZE,
        "%c%c%c%c%c%c%c%c%c%c  1 %5d %5d %11u %s %s\r\n",
        (S_ISLNK(stat_buf.st_mode)?('l'):
          (S_ISDIR(stat_buf.st_mode)?('d'):('-'))),
        (stat_buf.st_mode & S_IRUSR)?('r'):('-'),
        (stat_buf.st_mode & S_IWUSR)?('w'):('-'),
        (stat_buf.st_mode & S_IXUSR)?('x'):('-'),
        (stat_buf.st_mode & S_IRGRP)?('r'):('-'),
        (stat_buf.st_mode & S_IWGRP)?('w'):('-'),
        (stat_buf.st_mode & S_IXGRP)?('x'):('-'),
        (stat_buf.st_mode & S_IROTH)?('r'):('-'),
        (stat_buf.st_mode & S_IWOTH)?('w'):('-'),
        (stat_buf.st_mode & S_IXOTH)?('x'):('-'),
        (int)stat_buf.st_uid,
        (int)stat_buf.st_gid,
        (int)stat_buf.st_size,
        timeBuf,
        fname
      );

      if(send(s, buf, len, 0) != len)
        return 0;
    }
  }
  else
  {
    int len = snprintf(buf, FTPD_BUFSIZE, "%s\r\n", fname);
    if(send(s, buf, len, 0) != len)
      return 0;
  }
  return 1;
}

/*PAGE
 *
 * command_list
 *
 * Send file list to client.
 *
 * Input parameters:
 *   info - corresponding SessionInfo structure
 *   char *fname  - File (or directory) to list.
 *
 * Output parameters:
 *   NONE
 */
static void
command_list(FTPD_SessionInfo_t *info, char const *fname, int wide)
{
  int                 s;
  DIR                 *dirp = 0;
  struct dirent       *dp = 0;
  struct stat         stat_buf;
  char                buf[FTPD_BUFSIZE];
  time_t curTime;
  int sc = 1;

  send_reply(info, 150, "Opening ASCII mode data connection for LIST.");

  s = data_socket(info);
  if(0 > s)
  {
    syslog(LOG_ERR, "ftpd: Error connecting to data socket.");
    return;
  }

  if(fname[0] == '\0')
    fname = ".";

  if (0 > stat(fname, &stat_buf))
  {
    snprintf(buf, FTPD_BUFSIZE,
      "%s: No such file or directory.\r\n", fname);
    send(s, buf, strlen(buf), 0);
  }
  else if (S_ISDIR(stat_buf.st_mode) && (NULL == (dirp = opendir(fname))))
  {
    snprintf(buf, FTPD_BUFSIZE,
      "%s: Can not open directory.\r\n", fname);
    send(s, buf, strlen(buf), 0);
  }
  else
  {
    time(&curTime);
    if(!dirp && *fname)
      sc = sc && send_dirline(s, wide, curTime, fname, "", fname, buf);
    else {
      /* FIXME: need "." and ".." only when '-a' option is given */
      sc = sc && send_dirline(s, wide, curTime, fname, "", ".", buf);
      sc = sc && send_dirline(s, wide, curTime, fname,
        (strcmp(fname, ftpd_root) ? ".." : ""), "..", buf);
      while (sc && (dp = readdir(dirp)) != NULL)
        sc = sc &&
          send_dirline(s, wide, curTime, fname, dp->d_name, dp->d_name, buf);
    }
  }

  if(dirp)
    closedir(dirp);
  close_data_socket(info);

  if(sc)
    send_reply(info, 226, "Transfer complete.");
  else
    send_reply(info, 426, "Connection aborted.");
}


/*PAGE
 *
 * command_cwd
 *
 * Change current working directory.
 *
 * Input parameters:
 *   info - corresponding SessionInfo structure
 *   dir  - directory name passed in CWD command
 *
 * Output parameters:
 *   NONE
 *
 */
static void
command_cwd(FTPD_SessionInfo_t  *info, char *dir)
{
  if(chdir(dir) == 0)
    send_reply(info, 250, "CWD command successful.");
  else
    send_reply(info, 550, "CWD command failed.");
}


/*PAGE
 *
 * command_pwd
 *
 * Send current working directory to client.
 *
 * Input parameters:
 *   info - corresponding SessionInfo structure
 *
 * Output parameters:
 *   NONE
 */
static void
command_pwd(FTPD_SessionInfo_t  *info)
{
  char buf[FTPD_BUFSIZE];
  char const* cwd;
  errno = 0;
  buf[0] = '"';
  cwd = getcwd(buf + 1, FTPD_BUFSIZE - 4);
  if(cwd)
  {
    int len = strlen(cwd);
    static char const txt[] = "\" is the current directory.";
    int size = sizeof(txt);
    if(len + size + 1 >= FTPD_BUFSIZE)
      size = FTPD_BUFSIZE - len - 2;
    memcpy(buf + len + 1, txt, size);
    buf[len + size] = '\0';
    send_reply(info, 250, buf);
  }
  else {
    snprintf(buf, FTPD_BUFSIZE, "Error: %s.", serr());
    send_reply(info, 452, buf);
  }
}

/*PAGE
 *
 * command_mdtm
 *
 * Handle FTP MDTM command (send file modification time to client)/
 *
 * Input parameters:
 *   info - corresponding SessionInfo structure
 *   fname - file name passed in MDTM command
 *
 * Output parameters:
 *   info->cwd is set to new CWD value.
 */
static void
command_mdtm(FTPD_SessionInfo_t  *info, char const* fname)
{
  struct stat stbuf;
  char buf[FTPD_BUFSIZE];

  if (0 > stat(fname, &stbuf))
  {
    snprintf(buf, FTPD_BUFSIZE, "%s: %s.", fname, serr());
    send_reply(info, 550, buf);
  }
  else
  {
    struct tm *t = gmtime(&stbuf.st_mtime);
    snprintf(buf, FTPD_BUFSIZE, "%04d%02d%02d%02d%02d%02d",
      1900 + t->tm_year,
      t->tm_mon+1, t->tm_mday,
      t->tm_hour, t->tm_min, t->tm_sec);
    send_reply(info, 213, buf);
  }
}

/*PAGE
 *
 * command_port
 *
 * This procedure fills address for data connection given the IP address and
 * port of the remote machine.
 *
 * Input parameters:
 *   info - corresponding SessionInfo structure
 *   args - arguments to the "PORT" command.
 *
 * Output parameters:
 *   info->data_addr is set according to arguments of the PORT command.
 *   info->use_default is set to 0 on success, 1 on failure.
 */
static void
command_port(FTPD_SessionInfo_t *info, char const *args)
{
  enum { NUM_FIELDS = 6 };
  unsigned int a[NUM_FIELDS];
  int n;

  close_data_socket(info);

  n = sscanf(args, "%u,%u,%u,%u,%u,%u", a+0, a+1, a+2, a+3, a+4, a+5);
  if(NUM_FIELDS == n)
  {
    int i;
    unsigned8 b[NUM_FIELDS];

    for(i = 0; i < NUM_FIELDS; ++i)
    {
      if(a[i] > 255)
        break;
      b[i] = (unsigned8)a[i];
    }

    if(i == NUM_FIELDS)
    {
      /* Note: while it contradicts with RFC959, we don't allow PORT command
       * to specify IP address different than those of the originating client
       * for the sake of safety. */
      unsigned32 const *ip   = (unsigned32 *)b;
      if(*ip == info->def_addr.sin_addr.s_addr)
      {
        info->data_addr.sin_addr.s_addr = *ip;
        info->data_addr.sin_port        = *(unsigned16 *)(b + 4);
        info->data_addr.sin_family      = AF_INET;
        memset(info->data_addr.sin_zero, 0, sizeof(info->data_addr.sin_zero));

        info->use_default = 0;
        send_reply(info, 200, "PORT command successful.");
        return; /* success */
      }
      else
      {
        send_reply(info, 425, "Address doesn't match peer's IP.");
        return;
      }
    }
  }
  send_reply(info, 501, "Syntax error.");
}


/*PAGE
 *
 * command_pasv
 *
 * Handle FTP PASV command.
 * Open socket, listen for and accept connection on it.
 *
 * Input parameters:
 *   info - corresponding SessionInfo structure
 *
 * Output parameters:
 *   info->pasv_socket is set to the descriptor of the data socket
 */
static void
command_pasv(FTPD_SessionInfo_t *info)
{
  int s = -1;
  int err = 1;

  close_data_socket(info);

  s = socket(PF_INET, SOCK_STREAM, 0);
  if (s < 0)
    syslog(LOG_ERR, "ftpd: Error creating PASV socket: %s", serr());
  else
  {
    struct sockaddr_in addr;
    int addrLen = sizeof(addr);

    addr = info->ctrl_addr;
    addr.sin_port = htons(0);

    if (0 > bind(s, (struct sockaddr *)&addr, addrLen))
      syslog(LOG_ERR, "ftpd: Error binding PASV socket: %s", serr());
    else if (0 > listen(s, 1))
      syslog(LOG_ERR, "ftpd: Error listening on PASV socket: %s", serr());
    else if(set_socket_timeout(s, info->idle))
    {
      char buf[FTPD_BUFSIZE];
      unsigned char const *ip, *p;

      getsockname(s, (struct sockaddr *)&addr, &addrLen);
      ip = (unsigned char const*)&(addr.sin_addr);
      p  = (unsigned char const*)&(addr.sin_port);
      snprintf(buf, FTPD_BUFSIZE, "Entering passive mode (%u,%u,%u,%u,%u,%u).",
        ip[0], ip[1], ip[2], ip[3], p[0], p[1]);
      send_reply(info, 227, buf);

      info->pasv_socket = accept(s, (struct sockaddr *)&addr, &addrLen);
      if (0 > info->pasv_socket)
        syslog(LOG_ERR, "ftpd: Error accepting PASV connection: %s", serr());
      else
      {
        close_socket(s);
        s = -1;
        err = 0;
      }
    }
  }
  if(err)
  {
    /* (OSV) The note is from FreeBSD FTPD.
     * Note: a response of 425 is not mentioned as a possible response to
     * the PASV command in RFC959.  However, it has been blessed as a
     * legitimate response by Jon Postel in a telephone conversation
     * with Rick Adams on 25 Jan 89. */
    send_reply(info, 425, "Can't open passive connection.");
    close_socket(s);
  }
}


/*PAGE
 *
 * skip_options
 *
 * Utility routine to skip options (if any) from input command.
 *
 * Input parameters:
 *   p  - pointer to pointer to command
 *
 * Output parameters:
 *   p  - is changed to point to first non-option argument
 */
static void
skip_options(char **p)
{
  char* buf = *p;
  char* last = NULL;
  while(1) {
    while(isspace(*buf))
      ++buf;
    if(*buf == '-') {
      if(*++buf == '-') { /* `--' should terminate options */
        if(isspace(*++buf)) {
          last = buf;
          do ++buf;
          while(isspace(*buf));
          break;
        }
      }
      while(*buf && !isspace(*buf))
        ++buf;
      last = buf;
    }
    else
      break;
  }
  if(last)
    *last = '\0';
  *p = buf;
}

/*PAGE
 *
 * split_command
 *
 * Split command into command itself, options, and arguments. Command itself
 * is converted to upper case.
 *
 * Input parameters:
 *   buf - initial command string
 *
 * Output parameter:
 *   buf  - is modified by inserting '\0' at ends of split entities
 *   cmd  - upper-cased command code
 *   opts - string containing all the options
 *   args - string containing all the arguments
 */
void
split_command(char *buf, char **cmd, char **opts, char **args)
{
  char* eoc;
  char* p = buf;
  while(isspace(*p))
    ++p;
  *cmd = p;
  while(*p && !isspace(*p))
  {
    *p = toupper(*p);
    ++p;
  }
  eoc = p;
  if(*p)
    *p++ = '\0';
  while(isspace(*p))
    ++p;
  *opts = p;
  skip_options(&p);
  *args = p;
  if(*opts == p)
    *opts = eoc;
  while(*p && *p != '\r' && *p != '\n')
    ++p;
  if(*p)
    *p++ = '\0';
}

/*PAGE
 *
 * exec_command
 *
 * Parse and execute FTP command.
 *
 * FIXME: This section is somewhat of a hack.  We should have a better
 *        way to parse commands.
 *
 * Input parameters:
 *   info - corresponding SessionInfo structure
 *   cmd  - command to be executed (upper-case)
 *   args - arguments of the command
 *
 * Output parameters:
 *    NONE
 */
static void
exec_command(FTPD_SessionInfo_t *info, char* cmd, char* args)
{
  char fname[FTPD_BUFSIZE];
  int wrong_command = 0;

  fname[0] = '\0';

  if (!strcmp("PORT", cmd))
  {
    command_port(info, args);
  }
  else if (!strcmp("PASV", cmd))
  {
    command_pasv(info);
  }
  else if (!strcmp("RETR", cmd))
  {
    sscanf(args, "%254s", fname);
    command_retrieve(info, fname);
  }
  else if (!strcmp("STOR", cmd))
  {
    sscanf(args, "%254s", fname);
    command_store(info, fname);
  }
  else if (!strcmp("LIST", cmd))
  {
    sscanf(args, "%254s", fname);
    command_list(info, fname, 1);
  }
  else if (!strcmp("NLST", cmd))
  {
    sscanf(args, "%254s", fname);
    command_list(info, fname, 0);
  }
  else if (!strcmp("MDTM", cmd))
  {
    sscanf(args, "%254s", fname);
    command_mdtm(info, fname);
  }
  else if (!strcmp("SYST", cmd))
  {
    send_reply(info, 215, FTPD_SYSTYPE);
  }
  else if (!strcmp("TYPE", cmd))
  {
    if (args[0] == 'I')
    {
      info->xfer_mode = TYPE_I;
      send_reply(info, 200, "Type set to I.");
    }
    else if (args[0] == 'A')
    {
      info->xfer_mode = TYPE_A;
      send_reply(info, 200, "Type set to A.");
    }
    else
    {
      info->xfer_mode = TYPE_I;
      send_reply(info, 504, "Type not implemented.  Set to I.");
    }
  }
  else if (!strcmp("USER", cmd) || !strcmp("PASS", cmd))
  {
    send_reply(info, 230, "User logged in.");
  }
  else if (!strcmp("DELE", cmd))
  {
    if(!can_write())
    {
      send_reply(info, 550, "Access denied.");
    }
    else if (
      1 == sscanf(args, "%254s", fname) &&
      unlink(fname) == 0)
    {
      send_reply(info, 257, "DELE successful.");
    }
    else
    {
      send_reply(info, 550, "DELE failed.");
    }
  }
  else if (!strcmp("SITE", cmd))
  {
    char* opts;
    split_command(args, &cmd, &opts, &args);
    if(!strcmp("CHMOD", cmd))
    {
      int mask;

      if(!can_write())
      {
        send_reply(info, 550, "Access denied.");
      }
      else if(
        2 == sscanf(args, "%o %254s", &mask, fname) &&
        chmod(fname, (mode_t)mask) == 0)
      {
        send_reply(info, 257, "CHMOD successful.");
      }
      else
      {
        send_reply(info, 550, "CHMOD failed.");
      }
    }
    else
      wrong_command = 1;
  }
  else if (!strcmp("RMD", cmd))
  {
    if(!can_write())
    {
      send_reply(info, 550, "Access denied.");
    }
    else if (
      1 == sscanf(args, "%254s", fname) &&
      rmdir(fname) == 0)
    {
      send_reply(info, 257, "RMD successful.");
    }
    else
    {
      send_reply(info, 550, "RMD failed.");
    }
  }
  else if (!strcmp("MKD", cmd))
  {
    if(!can_write())
    {
      send_reply(info, 550, "Access denied.");
    }
    else if (
      1 == sscanf(args, "%254s", fname) &&
      mkdir(fname, S_IRWXU | S_IRWXG | S_IRWXO) == 0)
    {
      send_reply(info, 257, "MKD successful.");
    }
    else
    {
      send_reply(info, 550, "MKD failed.");
    }
  }
  else if (!strcmp("CWD", cmd))
  {
    sscanf(args, "%254s", fname);
    command_cwd(info, fname);
  }
  else if (!strcmp("CDUP", cmd))
  {
    command_cwd(info, "..");
  }
  else if (!strcmp("PWD", cmd))
  {
    command_pwd(info);
  }
  else
    wrong_command = 1;

  if(wrong_command)
    send_reply(info, 500, "Command not understood.");
}


/*PAGE
 *
 * session
 *
 * This task handles single session.  It is waked up when the FTP daemon gets a
 * service request from a remote machine.  Here, we watch for commands that
 * will come through the control connection.  These commands are then parsed
 * and executed until the connection is closed, either unintentionally or
 * intentionally with the "QUIT" command.
 *
 * Input parameters:
 *   arg - pointer to corresponding SessionInfo.
 *
 * Output parameters:
 *   NONE
 */
static void
session(rtems_task_argument arg)
{
  FTPD_SessionInfo_t  *const info = (FTPD_SessionInfo_t  *)arg;
  int chroot_made = 0;

  rtems_libio_set_private_env();

  /* chroot() can fail here because the directory may not exist yet. */
  chroot_made = chroot(ftpd_root) == 0;

  while(1)
  {
    rtems_event_set set;

    rtems_event_receive(FTPD_RTEMS_EVENT, RTEMS_EVENT_ANY, RTEMS_NO_TIMEOUT,
      &set);

    chroot_made = chroot_made || chroot(ftpd_root) == 0;
    chdir("/");

    errno = 0;

    send_reply(info, 220, FTPD_SERVER_MESSAGE);

    while (1)
    {
      char buf[FTPD_BUFSIZE];
      char *cmd, *opts, *args;

      if (fgets(buf, FTPD_BUFSIZE, info->ctrl_fp) == NULL)
      {
        syslog(LOG_INFO, "ftpd: Connection aborted.");
        break;
      }

      split_command(buf, &cmd, &opts, &args);

      if (!strcmp("QUIT", cmd))
      {
        send_reply(info, 221, "Goodbye.");
        break;
      }
      else
      {
        exec_command(info, cmd, args);
      }
    }

    /* Close connection and put ourselves back into the task pool. */
    close_data_socket(info);
    close_stream(info);
    task_pool_release(info);
  }
}


/*PAGE
 *
 * daemon
 *
 * This task runs forever.  It waits for service requests on the FTP port
 * (port 21 by default).  When a request is received, it opens a new session
 * to handle those requests until the connection is closed.
 *
 * Input parameters:
 *   NONE
 *
 * Output parameters:
 *   NONE
 */
static void
daemon()
{
  int                 s;
  int                 addrLen;
  struct sockaddr_in  addr;
  FTPD_SessionInfo_t  *info = NULL;


  s = socket(PF_INET, SOCK_STREAM, 0);
  if (s < 0)
    syslog(LOG_ERR, "ftpd: Error creating socket: %s", serr());

  addr.sin_family      = AF_INET;
  addr.sin_port        = htons(rtems_ftpd_configuration.port);
  addr.sin_addr.s_addr = htonl(INADDR_ANY);
  memset(addr.sin_zero, 0, sizeof(addr.sin_zero));

  if (0 > bind(s, (struct sockaddr *)&addr, sizeof(addr)))
    syslog(LOG_ERR, "ftpd: Error binding control socket: %s", serr());
  else if (0 > listen(s, 1))
    syslog(LOG_ERR, "ftpd: Error listening on control socket: %s", serr());
  else while (1)
  {
    int ss;
    addrLen = sizeof(addr);
    ss = accept(s, (struct sockaddr *)&addr, &addrLen);
    if (0 > ss)
      syslog(LOG_ERR, "ftpd: Error accepting control connection: %s", serr());
    else if(!set_socket_timeout(ss, ftpd_timeout))
      close_socket(ss);
    else
    {
      info = task_pool_obtain();
      if (NULL == info)
      {
        close_socket(ss);
      }
      else
      {
        info->ctrl_socket = ss;
        if ((info->ctrl_fp = fdopen(info->ctrl_socket, "r+")) == NULL)
        {
          syslog(LOG_ERR, "ftpd: fdopen() on socket failed: %s", serr());
          close_stream(info);
          task_pool_release(info);
        }
        else
        {
          /* Initialize corresponding SessionInfo structure */
          info->def_addr = addr;
          if(0 > getsockname(ss, (struct sockaddr *)&addr, &addrLen))
          {
            syslog(LOG_ERR, "ftpd: getsockname(): %s", serr());
            close_stream(info);
            task_pool_release(info);
          }
          else
          {
            info->use_default = 1;
            info->ctrl_addr  = addr;
            info->pasv_socket = -1;
            info->data_socket = -1;
            info->xfer_mode   = TYPE_A;
            info->data_addr.sin_port =
              htons(ntohs(info->ctrl_addr.sin_port) - 1);
            info->idle = ftpd_timeout;
            /* Wakeup the session task.  The task will call task_pool_release
               after it closes connection. */
            rtems_event_send(info->tid, FTPD_RTEMS_EVENT);
          }
        }
      }
    }
  }
  rtems_task_delete(RTEMS_SELF);
}


/*PAGE
 *
 * rtems_ftpd_start
 *
 * Here, we start the FTPD task which waits for FTP requests and services
 * them.  This procedure returns to its caller once the task is started.
 *
 *
 * Input parameters:
 *
 * Output parameters:
 *    returns RTEMS_SUCCESSFUL on successful start of the daemon.
 */
int
rtems_initialize_ftpd()
{
  rtems_status_code   sc;
  rtems_id            tid;
  rtems_task_priority priority;
  int count;

  if (rtems_ftpd_configuration.port == 0)
  {
    rtems_ftpd_configuration.port = FTPD_CONTROL_PORT;
  }

  if (rtems_ftpd_configuration.priority == 0)
  {
    rtems_ftpd_configuration.priority = 40;
  }
  priority = rtems_ftpd_configuration.priority;

  ftpd_timeout = rtems_ftpd_configuration.idle;
  if (ftpd_timeout < 0)
    ftpd_timeout = 0;
  rtems_ftpd_configuration.idle = ftpd_timeout;

  ftpd_access = rtems_ftpd_configuration.access;

  if (rtems_ftpd_configuration.tasks_count <= 0)
    rtems_ftpd_configuration.tasks_count = 1;
  count = rtems_ftpd_configuration.tasks_count;

  if (!task_pool_init(count, priority))
  {
    syslog(LOG_ERR, "ftpd: Could not initialize task pool.");
    return RTEMS_UNSATISFIED;
  }

  sc = rtems_task_create(rtems_build_name('F', 'T', 'P', 'D'),
    priority, FTPD_STACKSIZE,
    RTEMS_PREEMPT | RTEMS_NO_TIMESLICE | RTEMS_NO_ASR |
    RTEMS_INTERRUPT_LEVEL(0),
    RTEMS_NO_FLOATING_POINT | RTEMS_LOCAL,
    &tid);

  if (sc == RTEMS_SUCCESSFUL)
  {
    sc = rtems_task_start(tid, daemon, 0);
    if (sc != RTEMS_SUCCESSFUL)
      rtems_task_delete(tid);
  }

  if (sc != RTEMS_SUCCESSFUL)
  {
    task_pool_done(count);
    syslog(LOG_ERR, "ftpd: Could not create/start FTP daemon: %s",
      rtems_status_text(sc));
    return RTEMS_UNSATISFIED;
  }

  ftpd_root = "/";
  if (
    rtems_ftpd_configuration.root &&
    rtems_ftpd_configuration.root[0] == '/'
  )
    ftpd_root = rtems_ftpd_configuration.root;

  rtems_ftpd_configuration.root = ftpd_root;

  syslog(LOG_INFO, "ftpd: FTP daemon started (%d session%s max)",
    count, ((count > 1) ? "s" : ""));

  return RTEMS_SUCCESSFUL;
}