summaryrefslogblamecommitdiffstats
path: root/cpukit/libnetworking/lib/ftpfs.c
blob: 46ce18b9841222110f9e2ca3600b957899746b5c (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13

        
  
                                                   








                             




                                               
                          

                                   









                                                                              

   
                  
                  
                  
                   


                   
                   
                   
                   

                       
                       


                       
                  

                        

                               
                         
 



                                                 

      
   
                                              
   

                
                               

                  

     
                          
     
                  

     
                      
     

                    
 












































                                                                        
 


                                                             
 


                                                  
 



                                                        
 









                                
 

                          
 




                                              
 



                                          
 





















                                                  
 
 
                                                                                  
 





                                  
 







                                                                                 
 





















                                          
 
 


                                                 
 






                                

 




                                         
 







                                     
 







                              
 
                                
 


                                               

                   

 
                                                          




                                                                
 



                                                     
 

                                     
     



                                              
     



                                            
     






































                                                           

       










                                                      
       






                                                           
       


               

       
   








                                                              

                   


                                 





                                           
   





                                             



                                             

                                               

     










                                       
                                                                     
 



                                                  

                  
 
 







                                              

 










                                
 





                                     

 


                                      
 






























































                                                
   
 
     
 





                                                

 



                                            

 









                                
                                                                 
 

              
                                    

                                                                
 


                                            
                                  

                    


                                                                       




                                                  
                                 
                                                                      




                                           
     


                                               
                                  

                    
       
     


                               
   


                           

             






                                            


                               


             
              








                                                    
   












                                                      

     










                                                                                  

   





                                                                     








                                                         
   



                                                           
                                                                      

                                     
   

                         
                                                                            

                                     





                                     

                                       
     



                                                  
   

                                                                   
                                                                             

                                     
   







                                                   


                               




                                                    










                                                              
   










                                                  
   










                                                         
   





                                                                






                                    
    





                                   


                                     
   





                                                           
   

                                               





                                   


                                     
   
 












                                                         


                               








                            
   
 



                                      



                             























                                    
                                  

































                                                                             
     
   





                                                    


                               




                                                    












                                               

           


                                     
   













                                                                            
   








                            
   

                                               





                                   

                                     
   










                            


                              


                                                                







                                                                  
                                  
 



                                                               
                         
                                                  
   
 








                                 










                                                                             
   






                                                      







                                                      


                                 
                                                  
   





                                   


                             





                                         


                    
    
                 
                 
   





                                                 


             






                                                  


               
      
   





                                                                     


        


                 
                                                
                                      
 

                                               

 
                                
                     

               

 
                                    

                                                                
                    
                      
 









                                                    
                                                                      




                                           
       

                                                 
     


                        
   

                                  

 
                                 
                     

                     

 

                                    
                      
 




                                                     


                                                   

       


                        
   

                                  

 
                                                 
 
                                               
 




                                               


                                                      
                                                                          





                                 
                  



                                           
    

                                                                               
                                                                       
     




                                                        
                                           



           
                                                                            
 


                              

 

                                                           

 


                                      



                                         










                                                                          



                                                 






                                                                       

 










































                                                                



                                                                              
                                                                     



                                        

 





                               
                                                                      



                                                      

 

                                                           
                        




                                       
                                     


                                       
                                           



                      
  















                                                                      
  
















                                                                           
/**
 * @file
 *
 * File Transfer Protocol file system (FTP client).
 */

/*
 * Copyright (c) 2009
 * embedded brains GmbH
 * Obere Lagerstr. 30
 * D-82178 Puchheim
 * Germany
 * <rtems@embedded-brains.de>
 *
 * (c) Copyright 2002
 * Thomas Doerfler
 * IMD Ingenieurbuero fuer Microcomputertechnik
 * Herbststr. 8
 * 82178 Puchheim, Germany
 * <Thomas.Doerfler@imd-systems.de>
 *
 * Modified by Sebastian Huber <sebastian.huber@embedded-brains.de>.
 *
 * This code has been created after closly inspecting "tftpdriver.c" from Eric
 * Norum.
 *
 * The license and distribution terms for this file may be
 * found in the file LICENSE in this distribution or at
 * http://www.rtems.com/license/LICENSE.
 *
 * $Id$
 */

#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <malloc.h>
#include <netdb.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/types.h>

#include <rtems.h>
#include <rtems/ftpfs.h>
#include <rtems/imfs.h>
#include <rtems/libio.h>
#include <rtems/rtems_bsdnet.h>
#include <rtems/seterr.h>

#ifdef DEBUG
  #define DEBUG_PRINTF( ...) printf( __VA_ARGS__)
#else
  #define DEBUG_PRINTF( ...)
#endif

/**
 * Connection entry for each open file stream.
 */
typedef struct {
  /**
   * Control connection socket.
   */
  int ctrl_socket;

  /**
   * Data transfer socket.
   */
  int data_socket;

  /**
   * End of file flag.
   */
  bool eof;
} rtems_ftpfs_entry;

/**
 * Mount entry for each file system instance.
 */
typedef struct {
  /**
   * Verbose mode enabled or disabled.
   */
  bool verbose;

  /**
   * Timeout value
   */
  struct timeval timeout;
} rtems_ftpfs_mount_entry;

static const rtems_filesystem_file_handlers_r rtems_ftpfs_handlers;

static const rtems_filesystem_file_handlers_r rtems_ftpfs_root_handlers;

static bool rtems_ftpfs_use_timeout( const struct timeval *to)
{
  return to->tv_sec != 0 || to->tv_usec != 0;
}

static int rtems_ftpfs_set_connection_timeout(
  int socket,
  const struct timeval *to
)
{
  if (rtems_ftpfs_use_timeout( to)) {
    int rv = 0;
    
    rv = setsockopt( socket, SOL_SOCKET, SO_SNDTIMEO, to, sizeof( *to));
    if (rv != 0) {
      return EIO;
    }

    rv = setsockopt( socket, SOL_SOCKET, SO_RCVTIMEO, to, sizeof( *to));
    if (rv != 0) {
      return EIO;
    }
  }

  return 0;
}

rtems_status_code rtems_ftpfs_mount( const char *mount_point)
{
  int rv = 0;

  if (mount_point == NULL) {
    mount_point = RTEMS_FTPFS_MOUNT_POINT_DEFAULT;
  }

  rv = mkdir( mount_point, S_IRWXU | S_IRWXG | S_IRWXO);
  if (rv != 0) {
    return RTEMS_IO_ERROR;
  }

  rv = mount(
    NULL,
    &rtems_ftpfs_ops,
    RTEMS_FILESYSTEM_READ_WRITE,
    NULL,
    mount_point
  );
  if (rv != 0) {
    return RTEMS_IO_ERROR;
  }

  return RTEMS_SUCCESSFUL;
}

static rtems_status_code rtems_ftpfs_do_ioctl(
  const char *mount_point,
  int req,
  ...
)
{
  rtems_status_code sc = RTEMS_SUCCESSFUL;
  int rv = 0;
  int fd = 0;
  va_list ap;

  if (mount_point == NULL) {
    mount_point = RTEMS_FTPFS_MOUNT_POINT_DEFAULT;
  }

  fd = open( mount_point, O_RDWR);
  if (fd < 0) {
    return RTEMS_INVALID_NAME;
  }
  
  va_start( ap, req);
  rv = ioctl( fd, req, va_arg( ap, void *));
  va_end( ap);
  if (rv != 0) {
    sc = RTEMS_INVALID_NUMBER;
  }

  rv = close( fd);
  if (rv != 0 && sc == RTEMS_SUCCESSFUL) {
    sc = RTEMS_IO_ERROR;
  }
  
  return sc;
}

rtems_status_code rtems_ftpfs_get_verbose( const char *mount_point, bool *verbose)
{
  return rtems_ftpfs_do_ioctl(
    mount_point,
    RTEMS_FTPFS_IOCTL_GET_VERBOSE,
    verbose
  );
}

rtems_status_code rtems_ftpfs_set_verbose( const char *mount_point, bool verbose)
{
  return rtems_ftpfs_do_ioctl(
    mount_point,
    RTEMS_FTPFS_IOCTL_SET_VERBOSE,
    &verbose
  );
}

rtems_status_code rtems_ftpfs_get_timeout(
  const char *mount_point,
  struct timeval *timeout
)
{
  return rtems_ftpfs_do_ioctl(
    mount_point,
    RTEMS_FTPFS_IOCTL_GET_TIMEOUT,
    timeout
  );
}

rtems_status_code rtems_ftpfs_set_timeout(
  const char *mount_point,
  const struct timeval *timeout
)
{
  return rtems_ftpfs_do_ioctl(
    mount_point,
    RTEMS_FTPFS_IOCTL_SET_TIMEOUT,
    timeout
  );
}

int rtems_bsdnet_initialize_ftp_filesystem( void)
{
  rtems_status_code sc = RTEMS_SUCCESSFUL;

  sc = rtems_ftpfs_mount( NULL);

  if (sc == RTEMS_SUCCESSFUL) {
    return 0;
  } else {
    return -1;
  }
}

typedef void (*rtems_ftpfs_reply_parser)(
  const char * /* reply fragment */,
  size_t /* reply fragment length */,
  void * /* parser argument */
);

typedef enum {
  RTEMS_FTPFS_REPLY_START,
  RTEMS_FTPFS_REPLY_SINGLE_LINE,
  RTEMS_FTPFS_REPLY_SINGLE_LINE_DONE,
  RTEMS_FTPFS_REPLY_MULTI_LINE,
  RTEMS_FTPFS_REPLY_NEW_LINE,
  RTEMS_FTPFS_REPLY_NEW_LINE_START
} rtems_ftpfs_reply_state;

typedef enum {
  RTEMS_FTPFS_REPLY_ERROR = 0,
  RTEMS_FTPFS_REPLY_1 = '1',
  RTEMS_FTPFS_REPLY_2 = '2',
  RTEMS_FTPFS_REPLY_3 = '3',
  RTEMS_FTPFS_REPLY_4 = '4',
  RTEMS_FTPFS_REPLY_5 = '5'
} rtems_ftpfs_reply;

#define RTEMS_FTPFS_REPLY_SIZE 3

static rtems_ftpfs_reply rtems_ftpfs_get_reply(
  int socket,
  rtems_ftpfs_reply_parser parser,
  void *parser_arg,
  bool verbose
)
{
  rtems_ftpfs_reply_state state = RTEMS_FTPFS_REPLY_START;
  char reply_first [RTEMS_FTPFS_REPLY_SIZE] = { 'a', 'a', 'a' };
  char reply_last [RTEMS_FTPFS_REPLY_SIZE] = { 'b', 'b', 'b' };
  size_t reply_first_index = 0;
  size_t reply_last_index = 0;
  char buf [128];

  while (true) {
    /* Receive reply fragment from socket */
    ssize_t i = 0;
    ssize_t rv = recv( socket, buf, sizeof( buf), 0);

    if (rv <= 0) {
      return RTEMS_FTPFS_REPLY_ERROR;
    }

    /* Be verbose if necessary */
    if (verbose) {
      write( STDERR_FILENO, buf, (size_t) rv);
    }

    /* Invoke parser if necessary */
    if (parser != NULL) {
      parser( buf, (size_t) rv, parser_arg);
    }

    /* Parse reply fragment */
    for (i = 0; i < rv; ++i) {
      char c = buf [i];

      switch (state) {
        case RTEMS_FTPFS_REPLY_START:
          if (reply_first_index < RTEMS_FTPFS_REPLY_SIZE) {
            reply_first [reply_first_index] = c;
            ++reply_first_index;
          } else if (c == '-') {
            state = RTEMS_FTPFS_REPLY_MULTI_LINE;
          } else {
            state = RTEMS_FTPFS_REPLY_SINGLE_LINE;
          }
          break;
        case RTEMS_FTPFS_REPLY_SINGLE_LINE:
          if (c == '\n') {
            state = RTEMS_FTPFS_REPLY_SINGLE_LINE_DONE;
          }
          break;
        case RTEMS_FTPFS_REPLY_MULTI_LINE:
          if (c == '\n') {
            state = RTEMS_FTPFS_REPLY_NEW_LINE_START;
            reply_last_index = 0;
          }
          break;
        case RTEMS_FTPFS_REPLY_NEW_LINE:
        case RTEMS_FTPFS_REPLY_NEW_LINE_START:
          if (reply_last_index < RTEMS_FTPFS_REPLY_SIZE) {
            state = RTEMS_FTPFS_REPLY_NEW_LINE;
            reply_last [reply_last_index] = c;
            ++reply_last_index;
          } else {
            state = RTEMS_FTPFS_REPLY_MULTI_LINE;
          }
          break;
        default:
          return RTEMS_FTPFS_REPLY_ERROR;
      }
    }

    /* Check reply */
    if (state == RTEMS_FTPFS_REPLY_SINGLE_LINE_DONE) {
      if (
        isdigit( reply_first [0])
          && isdigit( reply_first [1])
          && isdigit( reply_first [2])
      ) {
        break;
      } else {
        return RTEMS_FTPFS_REPLY_ERROR;
      }
    } else if (state == RTEMS_FTPFS_REPLY_NEW_LINE_START) {
      bool ok = true;

      for (i = 0; i < RTEMS_FTPFS_REPLY_SIZE; ++i) {
        ok = ok
          && reply_first [i] == reply_last [i]
          && isdigit( reply_first [i]);
      }

      if (ok) {
        break;
      }
    }
  }

  return reply_first [0];
}

static rtems_ftpfs_reply rtems_ftpfs_send_command_with_parser(
  int socket,
  const char *cmd,
  const char *arg,
  rtems_ftpfs_reply_parser parser,
  void *parser_arg,
  bool verbose
)
{
  const char *const eol = "\r\n";
  int rv = 0;

  /* Send command */
  rv = send( socket, cmd, strlen( cmd), 0);
  if (rv < 0) {
    return RTEMS_FTPFS_REPLY_ERROR;
  }
  if (verbose) {
    write( STDERR_FILENO, cmd, strlen( cmd));
  }

  /* Send command argument if necessary */
  if (arg != NULL) {
    rv = send( socket, arg, strlen( arg), 0);
    if (rv < 0) {
      return RTEMS_FTPFS_REPLY_ERROR;
    }
    if (verbose) {
      write( STDERR_FILENO, arg, strlen( arg));
    }
  }

  /* Send end of line */
  rv = send( socket, eol, 2, 0);
  if (rv < 0) {
    return RTEMS_FTPFS_REPLY_ERROR;
  }
  if (verbose) {
    write( STDERR_FILENO, &eol [1], 1);
  }

  /* Return reply */
  return rtems_ftpfs_get_reply( socket, parser, parser_arg, verbose);
}

static rtems_ftpfs_reply rtems_ftpfs_send_command(
  int socket,
  const char *cmd,
  const char *arg,
  bool verbose
)
{
  return rtems_ftpfs_send_command_with_parser(
    socket,
    cmd,
    arg,
    NULL,
    NULL,
    verbose
  );
}

typedef enum {
  STATE_USER_NAME,
  STATE_START_PASSWORD,
  STATE_START_HOST_NAME,
  STATE_START_HOST_NAME_OR_PATH,
  STATE_START_PATH,
  STATE_PASSWORD,
  STATE_HOST_NAME,
  STATE_DONE,
  STATE_INVALID
} split_state;

static bool rtems_ftpfs_split_names (
  char *s,
  const char **user,
  const char **password,
  const char **hostname,
  const char **path
)
{
  split_state state = STATE_USER_NAME;
  size_t len = strlen( s);
  size_t i = 0;

  *user = s;
  *password = NULL;
  *hostname = NULL;
  *path = NULL;

  for (i = 0; i < len; ++i) {
    char c = s [i];

    switch (state) {
      case STATE_USER_NAME:
        if (c == ':') {
          state = STATE_START_PASSWORD;
          s [i] = '\0';
        } else if (c == '@') {
          state = STATE_START_HOST_NAME;
          s [i] = '\0';
        } else if (c == '/') {
          state = STATE_START_HOST_NAME_OR_PATH;
          s [i] = '\0';
        }
        break;
      case STATE_START_PASSWORD:
        state = STATE_PASSWORD;
        *password = &s [i];
        --i;
        break;
      case STATE_START_HOST_NAME:
        state = STATE_HOST_NAME;
        *hostname = &s [i];
        --i;
        break;
      case STATE_START_HOST_NAME_OR_PATH:
        if (c == '@') {
          state = STATE_START_HOST_NAME;
        } else {
          state = STATE_DONE;
          *path = &s [i];
          goto done;
        }
        break;
      case STATE_START_PATH:
        state = STATE_DONE;
        *path = &s [i];
        goto done;
      case STATE_PASSWORD:
        if (c == '@') {
          state = STATE_START_HOST_NAME;
          s [i] = '\0';
        } else if (c == '/') {
          state = STATE_START_HOST_NAME_OR_PATH;
          s [i] = '\0';
        }
        break;
      case STATE_HOST_NAME:
        if (c == '/') {
          state = STATE_START_PATH;
          s [i] = '\0';
        }
        break;
      default:
        state = STATE_INVALID;
        goto done;
    }
  }

done:

  /* If we have no password use the user name */
  if (*password == NULL) {
    *password = *user;
  }

  return state == STATE_DONE;
}

static socklen_t rtems_ftpfs_create_address(
  struct sockaddr_in *sa,
  unsigned long address,
  unsigned short port
)
{
  memset( sa, sizeof( *sa), 0);

  sa->sin_family = AF_INET;
  sa->sin_addr.s_addr = address;
  sa->sin_port = port;
  sa->sin_len = sizeof( *sa);

  return sizeof( *sa);
}

static int rtems_ftpfs_terminate( rtems_libio_t *iop, bool error)
{
  int eno = 0;
  int rv = 0;
  rtems_ftpfs_entry *e = iop->data1;
  rtems_ftpfs_mount_entry *me = iop->pathinfo.mt_entry->fs_info;
  bool verbose = me->verbose;

  if (e != NULL) {
    /* Close data connection if necessary */
    if (e->data_socket >= 0) {
      rv = close( e->data_socket);
      if (rv != 0) {
        eno = EIO;
      }

      /* For write connections we have to obtain the transfer reply  */
      if (
        e->ctrl_socket >= 0
          && (iop->flags & LIBIO_FLAGS_WRITE) != 0
          && !error
      ) {
        rtems_ftpfs_reply reply =
          rtems_ftpfs_get_reply( e->ctrl_socket, NULL, NULL, verbose);

        if (reply != RTEMS_FTPFS_REPLY_2) {
          eno = EIO;
        }
      }
    }

    /* Close control connection if necessary */
    if (e->ctrl_socket >= 0) {
      rv = close( e->ctrl_socket);
      if (rv != 0) {
        eno = EIO;
      }
    }

    /* Free connection entry */
    free( e);
  }

  /* Invalidate IO entry */
  iop->data1 = NULL;

  return eno;
}

static int rtems_ftpfs_open_ctrl_connection(
  rtems_ftpfs_entry *e,
  const char *user,
  const char *password,
  const char *hostname,
  uint32_t *client_address,
  bool verbose,
  const struct timeval *timeout
)
{
  int rv = 0;
  int eno = 0;
  rtems_ftpfs_reply reply = RTEMS_FTPFS_REPLY_ERROR;
  struct in_addr address = { .s_addr = 0 };
  struct sockaddr_in sa;
  socklen_t size = 0;

  /* Create the socket for the control connection */
  e->ctrl_socket = socket( AF_INET, SOCK_STREAM, 0);
  if (e->ctrl_socket < 0) {
    return ENOMEM;
  }

  /* Set up the server address from the hostname */
  if (hostname == NULL || strlen( hostname) == 0) {
    /* Default to BOOTP server address */
    address = rtems_bsdnet_bootp_server_address;
  } else if (inet_aton( hostname, &address) == 0) {
    /* Try to get the address by name */
    struct hostent *he = gethostbyname( hostname);

    if (he != NULL) {
      memcpy( &address, he->h_addr, sizeof( address));
    } else {
      return ENOENT;
    }
  }
  rtems_ftpfs_create_address( &sa, address.s_addr, htons( RTEMS_FTPFS_CTRL_PORT));
  DEBUG_PRINTF( "server = %s\n", inet_ntoa( sa.sin_addr));

  /* Open control connection */
  rv = connect(
    e->ctrl_socket,
    (struct sockaddr *) &sa,
    sizeof( sa)
  );
  if (rv != 0) {
    return ENOENT;
  }

  /* Set control connection timeout */
  eno = rtems_ftpfs_set_connection_timeout( e->ctrl_socket, timeout);
  if (eno != 0) {
    return eno;
  }

  /* Get client address */
  size = rtems_ftpfs_create_address( &sa, INADDR_ANY, 0);
  rv = getsockname(
    e->ctrl_socket,
    (struct sockaddr *) &sa,
    &size
  );
  if (rv != 0) {
    return ENOMEM;
  }
  *client_address = ntohl( sa.sin_addr.s_addr);
  DEBUG_PRINTF( "client = %s\n", inet_ntoa( sa.sin_addr));

  /* Now we should get a welcome message from the server */
  reply = rtems_ftpfs_get_reply( e->ctrl_socket, NULL, NULL, verbose);
  if (reply != RTEMS_FTPFS_REPLY_2) {
    return ENOENT;
  }

  /* Send USER command */
  reply = rtems_ftpfs_send_command( e->ctrl_socket, "USER ", user, verbose);
  if (reply == RTEMS_FTPFS_REPLY_3) {
    /* Send PASS command */
    reply = rtems_ftpfs_send_command(
      e->ctrl_socket,
      "PASS ",
      password,
      verbose
    );
    if (reply != RTEMS_FTPFS_REPLY_2) {
      return EACCES;
    }

    /* TODO: Some server may require an account */
  } else if (reply != RTEMS_FTPFS_REPLY_2) {
    return EACCES;
  }

  /* Send TYPE command to set binary mode for all data transfers */
  reply = rtems_ftpfs_send_command( e->ctrl_socket, "TYPE I", NULL, verbose);
  if (reply != RTEMS_FTPFS_REPLY_2) {
    return EIO;
  }

  return 0;
}

static int rtems_ftpfs_open_data_connection_active(
  rtems_ftpfs_entry *e,
  uint32_t client_address,
  const char *file_command,
  const char *filename,
  bool verbose,
  const struct timeval *timeout
)
{
  int rv = 0;
  int eno = 0;
  rtems_ftpfs_reply reply = RTEMS_FTPFS_REPLY_ERROR;
  struct sockaddr_in sa;
  socklen_t size = 0;
  int port_socket = -1;
  char port_command [] = "PORT 000,000,000,000,000,000";
  uint16_t data_port = 0;

  /* Create port socket to establish a data data connection */
  port_socket = socket( AF_INET, SOCK_STREAM, 0);
  if (port_socket < 0) {
    eno = ENOMEM;
    goto cleanup;
  }

  /* Bind port socket */
  rtems_ftpfs_create_address( &sa, INADDR_ANY, 0);
  rv = bind(
    port_socket,
    (struct sockaddr *) &sa,
    sizeof( sa)
  );
  if (rv != 0) {
    eno = EBUSY;
    goto cleanup;
  }

  /* Get port number for data socket */
  size = rtems_ftpfs_create_address( &sa, INADDR_ANY, 0);
  rv = getsockname(
    port_socket,
    (struct sockaddr *) &sa,
    &size
  );
  if (rv != 0) {
    eno = ENOMEM;
    goto cleanup;
  }
  data_port = ntohs( sa.sin_port);

  /* Send PORT command to set data connection port for server */
  snprintf(
    port_command,
    sizeof( port_command),
    "PORT %lu,%lu,%lu,%lu,%lu,%lu",
    (client_address >> 24) & 0xffUL,
    (client_address >> 16) & 0xffUL,
    (client_address >> 8) & 0xffUL,
    (client_address >> 0) & 0xffUL,
    (data_port >> 8) & 0xffUL,
    (data_port >> 0) & 0xffUL
  );
  reply = rtems_ftpfs_send_command(
    e->ctrl_socket,
    port_command,
    NULL,
    verbose
  );
  if (reply != RTEMS_FTPFS_REPLY_2) {
    eno = ENOTSUP;
    goto cleanup;
  }

  /* Listen on port socket for incoming data connections */
  rv = listen( port_socket, 1);
  if (rv != 0) {
    eno = EBUSY;
    goto cleanup;
  }

  /* Send RETR or STOR command with filename */
  reply = rtems_ftpfs_send_command(
    e->ctrl_socket,
    file_command,
    filename,
    verbose
  );
  if (reply != RTEMS_FTPFS_REPLY_1) {
    eno = EIO;
    goto cleanup;
  }

  /* Wait for connect on data connection if necessary */
  if (rtems_ftpfs_use_timeout( timeout)) {
    struct timeval to = *timeout;
    fd_set fds;

    FD_ZERO( &fds);
    FD_SET( port_socket, &fds);

    rv = select( port_socket + 1, &fds, NULL, NULL, &to);
    if (rv <= 0) {
      eno = EIO;
      goto cleanup;
    }
  }

  /* Accept data connection  */
  size = sizeof( sa);
  e->data_socket = accept(
    port_socket,
    (struct sockaddr *) &sa,
    &size
  );
  if (e->data_socket < 0) {
    eno = EIO;
    goto cleanup;
  }

cleanup:

  /* Close port socket if necessary */
  if (port_socket >= 0) {
    rv = close( port_socket);
    if (rv != 0) {
      eno = EIO;
    }
  }

  return eno;
}

typedef enum {
  RTEMS_FTPFS_PASV_START = 0,
  RTEMS_FTPFS_PASV_JUNK,
  RTEMS_FTPFS_PASV_DATA,
  RTEMS_FTPFS_PASV_DONE
} rtems_ftpfs_pasv_state;

typedef struct {
  rtems_ftpfs_pasv_state state;
  uint8_t data [6];
  size_t index;
} rtems_ftpfs_pasv_entry;

static void rtems_ftpfs_pasv_parser(
  const char* buf,
  size_t len,
  void *arg
)
{
  rtems_ftpfs_pasv_entry *e = arg;
  size_t i = 0;

  for (i = 0; i < len; ++i) {
    char c = buf [i];

    switch (e->state) {
      case RTEMS_FTPFS_PASV_START:
        if (!isdigit( c)) {
          e->state = RTEMS_FTPFS_PASV_JUNK;
          e->index = 0;
        }
        break;
      case RTEMS_FTPFS_PASV_JUNK:
        if (isdigit( c)) {
          e->state = RTEMS_FTPFS_PASV_DATA;
          e->data [e->index] = (uint8_t) (c - '0');
        }
        break;
      case RTEMS_FTPFS_PASV_DATA:
        if (isdigit( c)) {
          e->data [e->index] = (uint8_t) (e->data [e->index] * 10 + c - '0');
        } else if (c == ',') {
          ++e->index;
          if (e->index < sizeof( e->data)) {
            e->data [e->index] = 0;
          } else {
            e->state = RTEMS_FTPFS_PASV_DONE;
          }
        } else {
          e->state = RTEMS_FTPFS_PASV_DONE;
        }
        break;
      default:
        return;
    }
  }
}

static int rtems_ftpfs_open_data_connection_passive(
  rtems_ftpfs_entry *e,
  uint32_t client_address,
  const char *file_command,
  const char *filename,
  bool verbose,
  const struct timeval *timeout
)
{
  int rv = 0;
  rtems_ftpfs_reply reply = RTEMS_FTPFS_REPLY_ERROR;
  struct sockaddr_in sa;
  uint32_t data_address = 0;
  uint16_t data_port = 0;

  rtems_ftpfs_pasv_entry pe = {
    .state = RTEMS_FTPFS_PASV_START
  };

  /* Send PASV command */
  reply = rtems_ftpfs_send_command_with_parser(
    e->ctrl_socket,
    "PASV",
    NULL,
    rtems_ftpfs_pasv_parser,
    &pe,
    verbose
  );
  if (reply != RTEMS_FTPFS_REPLY_2) {
    return ENOTSUP;
  }
  data_address = (uint32_t) ((pe.data [0] << 24) + (pe.data [1] << 16)
    + (pe.data [2] << 8) + pe.data [3]);
  data_port = (uint16_t) ((pe.data [4] << 8) + pe.data [5]);
  rtems_ftpfs_create_address( &sa, htonl( data_address), htons( data_port));
  DEBUG_PRINTF(
    "server data = %s:%u\n",
    inet_ntoa( sa.sin_addr),
    (unsigned) ntohs( sa.sin_port)
  );

  /* Create data socket */
  e->data_socket = socket( AF_INET, SOCK_STREAM, 0);
  if (e->data_socket < 0) {
    return ENOMEM;
  }

  /* Open data connection */
  rv = connect(
    e->data_socket,
    (struct sockaddr *) &sa,
    sizeof( sa)
  );
  if (rv != 0) {
    return EIO;
  }

  /* Send RETR or STOR command with filename */
  reply = rtems_ftpfs_send_command(
    e->ctrl_socket,
    file_command,
    filename,
    verbose
  );
  if (reply != RTEMS_FTPFS_REPLY_1) {
    return EIO;
  }

  return 0;
}

static int rtems_ftpfs_open(
  rtems_libio_t *iop,
  const char *path,
  uint32_t flags,
  uint32_t mode
)
{
  int eno = 0;
  bool ok = false;
  rtems_ftpfs_entry *e = NULL;
  rtems_ftpfs_mount_entry *me = iop->pathinfo.mt_entry->fs_info;
  bool verbose = me->verbose;
  const struct timeval *timeout = &me->timeout;
  const char *user = NULL;
  const char *password = NULL;
  const char *hostname = NULL;
  const char *filename = NULL;
  const char *file_command = (iop->flags & LIBIO_FLAGS_WRITE) != 0
    ? "STOR "
    : "RETR ";
  uint32_t client_address = 0;
  char *location = iop->file_info;

  /* Invalidate data handle */
  iop->data1 = NULL;

  /* Check location, it was allocated during path evaluation */
  if (location == NULL) {
    rtems_set_errno_and_return_minus_one( ENOMEM);
  }

  /* Split location into parts */
  ok = rtems_ftpfs_split_names(
      location,
      &user,
      &password,
      &hostname,
      &filename
  );
  if (!ok) {
    if (strlen( location) == 0) {
      /*
       * This is an access to the root node that will be used for file system
       * option settings.
       */
      iop->handlers = &rtems_ftpfs_root_handlers;

      return 0;
    } else {
      rtems_set_errno_and_return_minus_one( ENOENT);
    }
  }
  DEBUG_PRINTF(
    "user = '%s', password = '%s', filename = '%s'\n",
    user,
    password,
    filename
  );

  /* Check for either read-only or write-only flags */
  if (
    (iop->flags & LIBIO_FLAGS_WRITE) != 0
      && (iop->flags & LIBIO_FLAGS_READ) != 0
  ) {
    rtems_set_errno_and_return_minus_one( ENOTSUP);
  }

  /* Allocate connection entry */
  e = malloc( sizeof( *e));
  if (e == NULL) {
    rtems_set_errno_and_return_minus_one( ENOMEM);
  }

  /* Initialize connection entry */
  e->ctrl_socket = -1;
  e->data_socket = -1;
  e->eof = false;

  /* Save connection state */
  iop->data1 = e;

  /* Open control connection */
  eno = rtems_ftpfs_open_ctrl_connection(
    e,
    user,
    password,
    hostname,
    &client_address,
    verbose,
    timeout
  );
  if (eno != 0) {
    goto cleanup;
  }

  /* Open passive data connection */
  eno = rtems_ftpfs_open_data_connection_passive(
    e,
    client_address,
    file_command,
    filename,
    verbose,
    timeout
  );
  if (eno == ENOTSUP) {
    /* Open active data connection */
    eno = rtems_ftpfs_open_data_connection_active(
      e,
      client_address,
      file_command,
      filename,
      verbose,
      timeout
    );
  }
  if (eno != 0) {
    goto cleanup;
  }

  /* Set data connection timeout */
  eno = rtems_ftpfs_set_connection_timeout( e->data_socket, timeout);

cleanup:

  if (eno == 0) {
    return 0;
  } else {
    /* Free all resources if an error occured */
    rtems_ftpfs_terminate( iop, true);

    rtems_set_errno_and_return_minus_one( eno);
  }
}

static ssize_t rtems_ftpfs_read(
  rtems_libio_t *iop,
  void *buffer,
  size_t count
)
{
  rtems_ftpfs_entry *e = iop->data1;
  rtems_ftpfs_mount_entry *me = iop->pathinfo.mt_entry->fs_info;
  bool verbose = me->verbose;
  char *in = buffer;
  size_t todo = count;

  if (e->eof) {
    return 0;
  }

  while (todo > 0) {
    ssize_t rv = recv( e->data_socket, in, todo, 0);

    if (rv <= 0) {
      if (rv == 0) {
        rtems_ftpfs_reply reply =
          rtems_ftpfs_get_reply( e->ctrl_socket, NULL, NULL, verbose);

        if (reply == RTEMS_FTPFS_REPLY_2) {
          e->eof = true;
          break;
        }
      }

      rtems_set_errno_and_return_minus_one( EIO);
    }

    in += rv;
    todo -= (size_t) rv;
  }

  return (ssize_t) (count - todo);
}

static ssize_t rtems_ftpfs_write(
  rtems_libio_t *iop,
  const void *buffer,
  size_t count
)
{
  rtems_ftpfs_entry *e = iop->data1;
  const char *out = buffer;
  size_t todo = count;

  while (todo > 0) {
    ssize_t rv = send( e->data_socket, out, todo, 0);

    if (rv <= 0) {
      if (rv == 0) {
        break;
      } else {
        rtems_set_errno_and_return_minus_one( EIO);
      }
    }

    out += rv;
    todo -= (size_t) rv;
  }

  return (ssize_t) (count - todo);
}

static int rtems_ftpfs_close( rtems_libio_t *iop)
{
  int eno = rtems_ftpfs_terminate( iop, false);

  if (eno == 0) {
    return 0;
  } else {
    rtems_set_errno_and_return_minus_one( eno);
  }
}

/* Dummy version to let fopen( *,"w") work properly */
static int rtems_ftpfs_ftruncate( rtems_libio_t *iop, rtems_off64_t count)
{
  return 0;
}

static int rtems_ftpfs_eval_path(
  const char *pathname,
  int pathnamelen,
  int flags,
  rtems_filesystem_location_info_t *pathloc
)
{
  /*
   * The caller of this routine has striped off the mount prefix from the path.
   * We need to store this path here or otherwise we would have to do this job
   * again.  The path is used in rtems_ftpfs_open() via iop->file_info.
   */
  pathloc->node_access = malloc(pathnamelen + 1);
  if (pathloc->node_access) {
    memset(pathloc->node_access, 0, pathnamelen + 1);
    memcpy(pathloc->node_access, pathname, pathnamelen);
  }    
  pathloc->node_access = strdup( pathname);

  return 0;
}

static int rtems_ftpfs_free_node( rtems_filesystem_location_info_t *pathloc)
{
  free( pathloc->node_access);

  return 0;
}

static rtems_filesystem_node_types_t rtems_ftpfs_node_type(
  rtems_filesystem_location_info_t *pathloc
)
{
  return RTEMS_FILESYSTEM_MEMORY_FILE;
}

static int rtems_ftpfs_mount_me(
  rtems_filesystem_mount_table_entry_t *e
)
{
  rtems_ftpfs_mount_entry *me = malloc( sizeof( rtems_ftpfs_mount_entry));

  /* Mount entry for FTP file system instance */
  e->fs_info = me;
  if (e->fs_info == NULL) {
    rtems_set_errno_and_return_minus_one( ENOMEM);
  }
  me->verbose = false;
  me->timeout.tv_sec = 0;
  me->timeout.tv_usec = 0;

  /* Set handler and oparations table */
  e->mt_fs_root.handlers = &rtems_ftpfs_handlers;
  e->mt_fs_root.ops = &rtems_ftpfs_ops;

  /* We maintain no real file system nodes, so there is no real root */
  e->mt_fs_root.node_access = NULL;

  /* Just use the limits from IMFS */
  e->pathconf_limits_and_options = IMFS_LIMITS_AND_OPTIONS;

  return 0;
}

static int rtems_ftpfs_unmount_me(
  rtems_filesystem_mount_table_entry_t *e
)
{
  free( e->fs_info);

  return 0;
}

static int rtems_ftpfs_ioctl(
  rtems_libio_t *iop,
  uint32_t command,
  void *arg
)
{
  rtems_ftpfs_mount_entry *me = iop->pathinfo.mt_entry->fs_info;
  bool *verbose = arg;
  struct timeval *timeout = arg;

  if (arg == NULL) {
    rtems_set_errno_and_return_minus_one( EINVAL);
  }

  switch (command) {
    case RTEMS_FTPFS_IOCTL_GET_VERBOSE:
      *verbose = me->verbose;
      break;
    case RTEMS_FTPFS_IOCTL_SET_VERBOSE:
      me->verbose = *verbose;
      break;
    case RTEMS_FTPFS_IOCTL_GET_TIMEOUT:
      *timeout = me->timeout;
      break;
    case RTEMS_FTPFS_IOCTL_SET_TIMEOUT:
      me->timeout = *timeout;
      break;
    default:
      rtems_set_errno_and_return_minus_one( EBADRQC);
  }

  return 0;
}

/*
 * The stat() support is intended only for the cp shell command.  Each request
 * will return that we have a regular file with read, write and execute
 * permissions for every one.  The node index uses a global counter to support
 * a remote to remote copy.  This is not a very sophisticated method.
 */
static int rtems_ftpfs_fstat(
  rtems_filesystem_location_info_t *loc,
  struct stat *st
)
{
  static unsigned ino = 0;

  memset( st, 0, sizeof( *st));

  /* FIXME */
  st->st_ino = ++ino;
  st->st_dev = rtems_filesystem_make_dev_t( 0xcc494cd6U, 0x1d970b4dU);

  st->st_mode = S_IFREG | S_IRWXU | S_IRWXG | S_IRWXO;

  return 0;
}

const rtems_filesystem_operations_table rtems_ftpfs_ops = {
  .evalpath_h = rtems_ftpfs_eval_path,
  .evalformake_h = NULL,
  .link_h = NULL,
  .unlink_h = NULL,
  .node_type_h = rtems_ftpfs_node_type,
  .mknod_h = NULL,
  .chown_h = NULL,
  .freenod_h = rtems_ftpfs_free_node,
  .mount_h = NULL,
  .fsmount_me_h = rtems_ftpfs_mount_me,
  .unmount_h = NULL,
  .fsunmount_me_h = rtems_ftpfs_unmount_me,
  .utime_h = NULL,
  .eval_link_h = NULL,
  .symlink_h = NULL,
  .readlink_h = NULL
};

static const rtems_filesystem_file_handlers_r rtems_ftpfs_handlers = {
  .open_h = rtems_ftpfs_open,
  .close_h = rtems_ftpfs_close,
  .read_h = rtems_ftpfs_read,
  .write_h = rtems_ftpfs_write,
  .ioctl_h = NULL,
  .lseek_h = NULL,
  .fstat_h = rtems_ftpfs_fstat,
  .fchmod_h = NULL,
  .ftruncate_h = rtems_ftpfs_ftruncate,
  .fpathconf_h = NULL,
  .fsync_h = NULL,
  .fdatasync_h = NULL,
  .fcntl_h = NULL,
  .rmnod_h = NULL
};

static const rtems_filesystem_file_handlers_r rtems_ftpfs_root_handlers = {
  .open_h = NULL,
  .close_h = NULL,
  .read_h = NULL,
  .write_h = NULL,
  .ioctl_h = rtems_ftpfs_ioctl,
  .lseek_h = NULL,
  .fstat_h = NULL,
  .fchmod_h = NULL,
  .ftruncate_h = NULL,
  .fpathconf_h = NULL,
  .fsync_h = NULL,
  .fdatasync_h = NULL,
  .fcntl_h = NULL,
  .rmnod_h = NULL
};