Home | History | Annotate | Download | only in pending
      1 /* ftpget.c - Get a remote file from FTP.
      2  *
      3  * Copyright 2013 Ranjan Kumar <ranjankumar.bth (at) gmail.com>
      4  * Copyright 2013 Kyungwan Han <asura321 (at) gmail.com>
      5  *
      6  * No Standard.
      7  *
      8 USE_FTPGET(NEWTOY(ftpget, "<2cvu:p:P#<0=21>65535", TOYFLAG_BIN))
      9 USE_FTPGET(OLDTOY(ftpput, ftpget, TOYFLAG_BIN))
     10 
     11 config FTPGET
     12   bool "ftpget/ftpput"
     13   default n
     14   help
     15     usage: ftpget [-cv] [-u USER -p PASSWORD -P PORT] HOST_NAME [LOCAL_FILENAME] REMOTE_FILENAME
     16     usage: ftpput [-v] [-u USER -p PASSWORD -P PORT] HOST_NAME [REMOTE_FILENAME] LOCAL_FILENAME
     17 
     18     ftpget - Get a remote file from FTP.
     19     ftpput - Upload a local file on remote machine through FTP.
     20 
     21     -c Continue previous transfer.
     22     -v Verbose.
     23     -u User name.
     24     -p Password.
     25     -P Port Number (default 21).
     26 */
     27 #define FOR_ftpget
     28 #include "toys.h"
     29 
     30 GLOBALS(
     31   long port; //  char *port;
     32   char *password;
     33   char *username;
     34 
     35   FILE *sockfp;
     36   int c;
     37   int isget;
     38   char buf[sizeof(struct sockaddr_storage)];
     39 )
     40 
     41 #define DATACONNECTION_OPENED   125
     42 #define FTPFILE_STATUSOKAY      150
     43 #define FTP_COMMAND_OKAY        200
     44 #define FTPFILE_STATUS          213
     45 #define FTPSERVER_READY         220
     46 #define CLOSE_DATACONECTION     226
     47 #define PASSIVE_MODE            227
     48 #define USERLOGGED_SUCCESS      230
     49 #define PASSWORD_REQUEST        331
     50 #define REQUESTED_PENDINGACTION 350
     51 
     52 
     53 static void setport(unsigned port_num)
     54 {
     55   int af = ((struct sockaddr *)TT.buf)->sa_family;
     56 
     57   if (af == AF_INET) ((struct sockaddr_in*)TT.buf)->sin_port = port_num;
     58   else if (af == AF_INET6) ((struct sockaddr_in6*)TT.buf)->sin6_port = port_num;
     59 }
     60 
     61 static int connect_to_stream()
     62 {
     63   int sockfd, af = ((struct sockaddr *)TT.buf)->sa_family;
     64 
     65   sockfd = xsocket(af, SOCK_STREAM, 0);
     66   if (connect(sockfd, (struct sockaddr*)TT.buf,((af == AF_INET)?
     67           sizeof(struct sockaddr_in):sizeof(struct sockaddr_in6))) < 0) {
     68     close(sockfd);
     69     perror_exit("can't connect to remote host");
     70   }
     71   return sockfd;
     72 }
     73 
     74 //close ftp connection and print the message.
     75 static void close_stream(char *msg_str)
     76 {
     77   char *str = toybuf; //toybuf holds response data.
     78 
     79   //Remove garbage chars (from ' ' space to '\x7f') DEL remote server response.
     80   while ((*str >= 0x20) && (*str < 0x7f)) str++;
     81   *str = '\0';
     82   if (TT.sockfp) fclose(TT.sockfp);
     83   error_exit("%s server response: %s", (msg_str) ? msg_str:"", toybuf);
     84 }
     85 
     86 //send command to ftp and get return status.
     87 static int get_ftp_response(char *command, char *param)
     88 {
     89   unsigned cmd_status = 0;
     90   char *fmt = "%s %s\r\n";
     91 
     92   if (command) {
     93     if (!param) fmt += 3;
     94     fprintf(TT.sockfp, fmt, command, param);
     95     fflush(TT.sockfp);
     96     if (toys.optflags & FLAG_v)
     97       fprintf(stderr, "FTP Request: %s %s\r\n", command, param);
     98   }
     99 
    100   do {
    101     if (!fgets(toybuf, sizeof(toybuf)-1, TT.sockfp)) close_stream(NULL);
    102   } while (!isdigit(toybuf[0]) || toybuf[3] != ' ');
    103 
    104   toybuf[3] = '\0';
    105   cmd_status = atolx_range(toybuf, 0, INT_MAX);
    106   toybuf[3] = ' ';
    107   return cmd_status;
    108 }
    109 
    110 static void send_requests(void)
    111 {
    112   int cmd_status = 0;
    113 
    114   //FTP connection request.
    115   if (get_ftp_response(NULL, NULL) != FTPSERVER_READY) close_stream(NULL);
    116 
    117   //230 User authenticated, password please; 331 Password request.
    118   cmd_status = get_ftp_response("USER", TT.username);
    119   if (cmd_status == PASSWORD_REQUEST) { //user logged in. Need Password.
    120     if (get_ftp_response("PASS", TT.password) != USERLOGGED_SUCCESS)
    121       close_stream("PASS");
    122   } else if (cmd_status == USERLOGGED_SUCCESS); //do nothing
    123   else close_stream("USER");
    124   //200 Type Binary. Command okay.
    125   if (get_ftp_response("TYPE I", NULL) != FTP_COMMAND_OKAY)
    126     close_stream("TYPE I");
    127 }
    128 
    129 static void get_sockaddr(char *host)
    130 {
    131   struct addrinfo hints, *result;
    132   char port[6];
    133   int status;
    134 
    135   errno = 0;
    136   snprintf(port, 6, "%ld", TT.port);
    137 
    138   memset(&hints, 0 , sizeof(struct addrinfo));
    139   hints.ai_family = AF_UNSPEC;
    140   hints.ai_socktype = SOCK_STREAM;
    141 
    142   status = getaddrinfo(host, port, &hints, &result);
    143   if (status) error_exit("bad address '%s' : %s", host, gai_strerror(status));
    144 
    145   memcpy(TT.buf, result->ai_addr, result->ai_addrlen);
    146   freeaddrinfo(result);
    147 }
    148 
    149 // send commands to ftp fo PASV mode.
    150 static void verify_pasv_mode(char *r_filename)
    151 {
    152   char *pch;
    153   unsigned portnum;
    154 
    155   //vsftpd reply like:- "227 Entering Passive Mode (125,19,39,117,43,39)".
    156   if (get_ftp_response("PASV", NULL) != PASSIVE_MODE) goto close_stream;
    157 
    158   //Response is "NNN <some text> (N1,N2,N3,N4,P1,P2) garbage.
    159   //Server's IP is N1.N2.N3.N4
    160   //Server's port for data connection is P1*256+P2.
    161   if (!(pch = strrchr(toybuf, ')'))) goto close_stream;
    162   *pch = '\0';
    163   if (!(pch = strrchr(toybuf, ','))) goto close_stream;
    164   *pch = '\0';
    165 
    166   portnum = atolx_range(pch + 1, 0, 255);
    167 
    168   if (!(pch = strrchr(toybuf, ','))) goto close_stream;
    169   *pch = '\0';
    170   portnum = portnum + (atolx_range(pch + 1, 0, 255) * 256);
    171   setport(htons(portnum));
    172 
    173   if (TT.isget && get_ftp_response("SIZE", r_filename) != FTPFILE_STATUS)
    174     TT.c = 0;
    175   return;
    176 
    177 close_stream:
    178   close_stream("PASV");
    179 }
    180 
    181 /*
    182  * verify the local file presence.
    183  * if present, get the size of the file.
    184  */
    185 static void is_localfile_present(char *l_filename)
    186 {
    187   struct stat sb;
    188 
    189   if (stat(l_filename, &sb) < 0) perror_exit("stat");
    190   //if local file present, then request for pending file action.
    191   if (sb.st_size > 0) {
    192     sprintf(toybuf, "REST %lu", (unsigned long) sb.st_size);
    193     if (get_ftp_response(toybuf, NULL) != REQUESTED_PENDINGACTION) TT.c = 0;
    194   } else TT.c = 0;
    195 }
    196 
    197 static void transfer_file(int local_fd, int remote_fd)
    198 {
    199   int len, rfd = (TT.isget)?remote_fd:local_fd,
    200       wfd = (TT.isget)?local_fd:remote_fd;
    201 
    202   if (rfd < 0 || wfd < 0) error_exit("Error in file creation:");
    203   while ((len = xread(rfd, toybuf, sizeof(toybuf)))) xwrite(wfd, toybuf, len);
    204 }
    205 
    206 static void get_file(char *l_filename, char *r_filename)
    207 {
    208   int local_fd = -1, remote_fd;
    209 
    210   verify_pasv_mode(r_filename);
    211   remote_fd = connect_to_stream(); //Connect to data socket.
    212 
    213   //if local file name will be '-' then local fd will be stdout.
    214   if ((l_filename[0] == '-') && !l_filename[1]) {
    215     local_fd = 1; //file descriptor will become stdout.
    216     TT.c = 0;
    217   }
    218 
    219   //if continue, check for local file existance.
    220   if (TT.c) is_localfile_present(l_filename);
    221 
    222   //verify the remote file presence.
    223   if (get_ftp_response("RETR", r_filename) > FTPFILE_STATUSOKAY)
    224     close_stream("RETR");
    225 
    226   //if local fd is not stdout, create a file descriptor.
    227   if (local_fd == -1) {
    228     int flags = O_WRONLY;
    229 
    230     flags |= (TT.c)? O_APPEND : (O_CREAT | O_TRUNC);
    231     local_fd = xcreate((char *)l_filename, flags, 0666);
    232   }
    233   transfer_file(local_fd, remote_fd);
    234   xclose(remote_fd);
    235   xclose(local_fd);
    236   if (get_ftp_response(NULL, NULL) != CLOSE_DATACONECTION) close_stream(NULL);
    237   get_ftp_response("QUIT", NULL);
    238   toys.exitval = EXIT_SUCCESS;
    239 }
    240 
    241 static void put_file(char *r_filename, char *l_filename)
    242 {
    243   int local_fd = 0, remote_fd;
    244   unsigned cmd_status = 0;
    245 
    246   verify_pasv_mode(r_filename);
    247   remote_fd = connect_to_stream(); //Connect to data socket.
    248 
    249   //open the local file for transfer.
    250   if ((l_filename[0] != '-') || l_filename[1])
    251     local_fd = xcreate((char *)l_filename, O_RDONLY, 0666);
    252 
    253   //verify for the remote file status, Ok or Open: transfer File.
    254   cmd_status = get_ftp_response("STOR", r_filename);
    255   if ( (cmd_status == DATACONNECTION_OPENED) ||
    256       (cmd_status == FTPFILE_STATUSOKAY)) {
    257     transfer_file(local_fd, remote_fd);
    258     if (get_ftp_response(NULL, NULL) != CLOSE_DATACONECTION) close_stream(NULL);
    259     get_ftp_response("QUIT", NULL);
    260     toys.exitval = EXIT_SUCCESS;
    261   } else {
    262     toys.exitval = EXIT_FAILURE;
    263     close_stream("STOR");
    264   }
    265   xclose(remote_fd);
    266   xclose(local_fd);
    267 }
    268 
    269 void ftpget_main(void)
    270 {
    271   char **argv = toys.optargs; //host name + file name.
    272 
    273   TT.isget = toys.which->name[3] == 'g';
    274   TT.c = 1;
    275   //if user name is not specified.
    276   if (!(toys.optflags & FLAG_u) && (toys.optflags & FLAG_p))
    277     error_exit("Missing username:");
    278   //if user name and password is not specified in command line.
    279   if (!(toys.optflags & FLAG_u) && !(toys.optflags & FLAG_p))
    280     TT.username = TT.password ="anonymous";
    281 
    282   //if continue is not in the command line argument.
    283   if (TT.isget && !(toys.optflags & FLAG_c)) TT.c = 0;
    284 
    285   if (toys.optflags & FLAG_v) fprintf(stderr, "Connecting to %s\n", argv[0]);
    286   get_sockaddr(argv[0]);
    287 
    288   TT.sockfp = xfdopen(connect_to_stream(), "r+");
    289   send_requests();
    290 
    291   if (TT.isget) get_file(argv[1], argv[2] ? argv[2] : argv[1]);
    292   else put_file(argv[1], argv[2] ? argv[2] : argv[1]);
    293 }
    294