busybox/networking/ftpgetput.c
<<
>>
Prefs
   1/* vi: set sw=4 ts=4: */
   2/*
   3 * ftpget
   4 *
   5 * Mini implementation of FTP to retrieve a remote file.
   6 *
   7 * Copyright (C) 2002 Jeff Angielski, The PTR Group <jeff@theptrgroup.com>
   8 * Copyright (C) 2002 Glenn McGrath
   9 *
  10 * Based on wget.c by Chip Rosenthal Covad Communications
  11 * <chip@laserlink.net>
  12 *
  13 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
  14 */
  15
  16//usage:#define ftpget_trivial_usage
  17//usage:       "[OPTIONS] HOST [LOCAL_FILE] REMOTE_FILE"
  18//usage:#define ftpget_full_usage "\n\n"
  19//usage:       "Download a file via FTP\n"
  20//usage:        IF_FEATURE_FTPGETPUT_LONG_OPTIONS(
  21//usage:     "\n        -c,--continue           Continue previous transfer"
  22//usage:     "\n        -v,--verbose            Verbose"
  23//usage:     "\n        -u,--username USER      Username"
  24//usage:     "\n        -p,--password PASS      Password"
  25//usage:     "\n        -P,--port NUM           Port"
  26//usage:        )
  27//usage:        IF_NOT_FEATURE_FTPGETPUT_LONG_OPTIONS(
  28//usage:     "\n        -c      Continue previous transfer"
  29//usage:     "\n        -v      Verbose"
  30//usage:     "\n        -u USER Username"
  31//usage:     "\n        -p PASS Password"
  32//usage:     "\n        -P NUM  Port"
  33//usage:        )
  34//usage:
  35//usage:#define ftpput_trivial_usage
  36//usage:       "[OPTIONS] HOST [REMOTE_FILE] LOCAL_FILE"
  37//usage:#define ftpput_full_usage "\n\n"
  38//usage:       "Upload a file to a FTP server\n"
  39//usage:        IF_FEATURE_FTPGETPUT_LONG_OPTIONS(
  40//usage:     "\n        -v,--verbose            Verbose"
  41//usage:     "\n        -u,--username USER      Username"
  42//usage:     "\n        -p,--password PASS      Password"
  43//usage:     "\n        -P,--port NUM           Port"
  44//usage:        )
  45//usage:        IF_NOT_FEATURE_FTPGETPUT_LONG_OPTIONS(
  46//usage:     "\n        -v      Verbose"
  47//usage:     "\n        -u USER Username"
  48//usage:     "\n        -p PASS Password"
  49//usage:     "\n        -P NUM  Port number"
  50//usage:        )
  51
  52#include "libbb.h"
  53
  54struct globals {
  55        const char *user;
  56        const char *password;
  57        struct len_and_sockaddr *lsa;
  58        FILE *control_stream;
  59        int verbose_flag;
  60        int do_continue;
  61        char buf[4]; /* actually [BUFSZ] */
  62} FIX_ALIASING;
  63#define G (*(struct globals*)&bb_common_bufsiz1)
  64enum { BUFSZ = COMMON_BUFSIZE - offsetof(struct globals, buf) };
  65struct BUG_G_too_big {
  66        char BUG_G_too_big[sizeof(G) <= COMMON_BUFSIZE ? 1 : -1];
  67};
  68#define user           (G.user          )
  69#define password       (G.password      )
  70#define lsa            (G.lsa           )
  71#define control_stream (G.control_stream)
  72#define verbose_flag   (G.verbose_flag  )
  73#define do_continue    (G.do_continue   )
  74#define buf            (G.buf           )
  75#define INIT_G() do { } while (0)
  76
  77
  78static void ftp_die(const char *msg) NORETURN;
  79static void ftp_die(const char *msg)
  80{
  81        char *cp = buf; /* buf holds peer's response */
  82
  83        /* Guard against garbage from remote server */
  84        while (*cp >= ' ' && *cp < '\x7f')
  85                cp++;
  86        *cp = '\0';
  87        bb_error_msg_and_die("unexpected server response%s%s: %s",
  88                        (msg ? " to " : ""), (msg ? msg : ""), buf);
  89}
  90
  91static int ftpcmd(const char *s1, const char *s2)
  92{
  93        unsigned n;
  94
  95        if (verbose_flag) {
  96                bb_error_msg("cmd %s %s", s1, s2);
  97        }
  98
  99        if (s1) {
 100                fprintf(control_stream, (s2 ? "%s %s\r\n" : "%s %s\r\n"+3),
 101                                                s1, s2);
 102                fflush(control_stream);
 103        }
 104
 105        do {
 106                strcpy(buf, "EOF"); /* for ftp_die */
 107                if (fgets(buf, BUFSZ - 2, control_stream) == NULL) {
 108                        ftp_die(NULL);
 109                }
 110        } while (!isdigit(buf[0]) || buf[3] != ' ');
 111
 112        buf[3] = '\0';
 113        n = xatou(buf);
 114        buf[3] = ' ';
 115        return n;
 116}
 117
 118static void ftp_login(void)
 119{
 120        /* Connect to the command socket */
 121        control_stream = fdopen(xconnect_stream(lsa), "r+");
 122        if (control_stream == NULL) {
 123                /* fdopen failed - extremely unlikely */
 124                bb_perror_nomsg_and_die();
 125        }
 126
 127        if (ftpcmd(NULL, NULL) != 220) {
 128                ftp_die(NULL);
 129        }
 130
 131        /*  Login to the server */
 132        switch (ftpcmd("USER", user)) {
 133        case 230:
 134                break;
 135        case 331:
 136                if (ftpcmd("PASS", password) != 230) {
 137                        ftp_die("PASS");
 138                }
 139                break;
 140        default:
 141                ftp_die("USER");
 142        }
 143
 144        ftpcmd("TYPE I", NULL);
 145}
 146
 147static int xconnect_ftpdata(void)
 148{
 149        char *buf_ptr;
 150        unsigned port_num;
 151
 152/*
 153TODO: PASV command will not work for IPv6. RFC2428 describes
 154IPv6-capable "extended PASV" - EPSV.
 155
 156"EPSV [protocol]" asks server to bind to and listen on a data port
 157in specified protocol. Protocol is 1 for IPv4, 2 for IPv6.
 158If not specified, defaults to "same as used for control connection".
 159If server understood you, it should answer "229 <some text>(|||port|)"
 160where "|" are literal pipe chars and "port" is ASCII decimal port#.
 161
 162There is also an IPv6-capable replacement for PORT (EPRT),
 163but we don't need that.
 164
 165NB: PASV may still work for some servers even over IPv6.
 166For example, vsftp happily answers
 167"227 Entering Passive Mode (0,0,0,0,n,n)" and proceeds as usual.
 168
 169TODO2: need to stop ignoring IP address in PASV response.
 170*/
 171
 172        if (ftpcmd("PASV", NULL) != 227) {
 173                ftp_die("PASV");
 174        }
 175
 176        /* Response is "NNN garbageN1,N2,N3,N4,P1,P2[)garbage]
 177         * Server's IP is N1.N2.N3.N4 (we ignore it)
 178         * Server's port for data connection is P1*256+P2 */
 179        buf_ptr = strrchr(buf, ')');
 180        if (buf_ptr) *buf_ptr = '\0';
 181
 182        buf_ptr = strrchr(buf, ',');
 183        *buf_ptr = '\0';
 184        port_num = xatoul_range(buf_ptr + 1, 0, 255);
 185
 186        buf_ptr = strrchr(buf, ',');
 187        *buf_ptr = '\0';
 188        port_num += xatoul_range(buf_ptr + 1, 0, 255) * 256;
 189
 190        set_nport(&lsa->u.sa, htons(port_num));
 191        return xconnect_stream(lsa);
 192}
 193
 194static int pump_data_and_QUIT(int from, int to)
 195{
 196        /* copy the file */
 197        if (bb_copyfd_eof(from, to) == -1) {
 198                /* error msg is already printed by bb_copyfd_eof */
 199                return EXIT_FAILURE;
 200        }
 201
 202        /* close data connection */
 203        close(from); /* don't know which one is that, so we close both */
 204        close(to);
 205
 206        /* does server confirm that transfer is finished? */
 207        if (ftpcmd(NULL, NULL) != 226) {
 208                ftp_die(NULL);
 209        }
 210        ftpcmd("QUIT", NULL);
 211
 212        return EXIT_SUCCESS;
 213}
 214
 215#if !ENABLE_FTPGET
 216int ftp_receive(const char *local_path, char *server_path);
 217#else
 218static
 219int ftp_receive(const char *local_path, char *server_path)
 220{
 221        int fd_data;
 222        int fd_local = -1;
 223        off_t beg_range = 0;
 224
 225        /* connect to the data socket */
 226        fd_data = xconnect_ftpdata();
 227
 228        if (ftpcmd("SIZE", server_path) != 213) {
 229                do_continue = 0;
 230        }
 231
 232        if (LONE_DASH(local_path)) {
 233                fd_local = STDOUT_FILENO;
 234                do_continue = 0;
 235        }
 236
 237        if (do_continue) {
 238                struct stat sbuf;
 239                /* lstat would be wrong here! */
 240                if (stat(local_path, &sbuf) < 0) {
 241                        bb_perror_msg_and_die("stat");
 242                }
 243                if (sbuf.st_size > 0) {
 244                        beg_range = sbuf.st_size;
 245                } else {
 246                        do_continue = 0;
 247                }
 248        }
 249
 250        if (do_continue) {
 251                sprintf(buf, "REST %"OFF_FMT"u", beg_range);
 252                if (ftpcmd(buf, NULL) != 350) {
 253                        do_continue = 0;
 254                }
 255        }
 256
 257        if (ftpcmd("RETR", server_path) > 150) {
 258                ftp_die("RETR");
 259        }
 260
 261        /* create local file _after_ we know that remote file exists */
 262        if (fd_local == -1) {
 263                fd_local = xopen(local_path,
 264                        do_continue ? (O_APPEND | O_WRONLY)
 265                                    : (O_CREAT | O_TRUNC | O_WRONLY)
 266                );
 267        }
 268
 269        return pump_data_and_QUIT(fd_data, fd_local);
 270}
 271#endif
 272
 273#if !ENABLE_FTPPUT
 274int ftp_send(const char *server_path, char *local_path);
 275#else
 276static
 277int ftp_send(const char *server_path, char *local_path)
 278{
 279        int fd_data;
 280        int fd_local;
 281        int response;
 282
 283        /* connect to the data socket */
 284        fd_data = xconnect_ftpdata();
 285
 286        /* get the local file */
 287        fd_local = STDIN_FILENO;
 288        if (NOT_LONE_DASH(local_path))
 289                fd_local = xopen(local_path, O_RDONLY);
 290
 291        response = ftpcmd("STOR", server_path);
 292        switch (response) {
 293        case 125:
 294        case 150:
 295                break;
 296        default:
 297                ftp_die("STOR");
 298        }
 299
 300        return pump_data_and_QUIT(fd_local, fd_data);
 301}
 302#endif
 303
 304#if ENABLE_FEATURE_FTPGETPUT_LONG_OPTIONS
 305static const char ftpgetput_longopts[] ALIGN1 =
 306        "continue\0" Required_argument "c"
 307        "verbose\0"  No_argument       "v"
 308        "username\0" Required_argument "u"
 309        "password\0" Required_argument "p"
 310        "port\0"     Required_argument "P"
 311        ;
 312#endif
 313
 314int ftpgetput_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 315int ftpgetput_main(int argc UNUSED_PARAM, char **argv)
 316{
 317        const char *port = "ftp";
 318        /* socket to ftp server */
 319
 320#if ENABLE_FTPPUT && !ENABLE_FTPGET
 321# define ftp_action ftp_send
 322#elif ENABLE_FTPGET && !ENABLE_FTPPUT
 323# define ftp_action ftp_receive
 324#else
 325        int (*ftp_action)(const char *, char *) = ftp_send;
 326
 327        /* Check to see if the command is ftpget or ftput */
 328        if (applet_name[3] == 'g') {
 329                ftp_action = ftp_receive;
 330        }
 331#endif
 332
 333        INIT_G();
 334        /* Set default values */
 335        user = "anonymous";
 336        password = "busybox@";
 337
 338        /*
 339         * Decipher the command line
 340         */
 341#if ENABLE_FEATURE_FTPGETPUT_LONG_OPTIONS
 342        applet_long_options = ftpgetput_longopts;
 343#endif
 344        opt_complementary = "-2:vv:cc"; /* must have 2 to 3 params; -v and -c count */
 345        getopt32(argv, "cvu:p:P:", &user, &password, &port,
 346                                        &verbose_flag, &do_continue);
 347        argv += optind;
 348
 349        /* We want to do exactly _one_ DNS lookup, since some
 350         * sites (i.e. ftp.us.debian.org) use round-robin DNS
 351         * and we want to connect to only one IP... */
 352        lsa = xhost2sockaddr(argv[0], bb_lookup_port(port, "tcp", 21));
 353        if (verbose_flag) {
 354                printf("Connecting to %s (%s)\n", argv[0],
 355                        xmalloc_sockaddr2dotted(&lsa->u.sa));
 356        }
 357
 358        ftp_login();
 359        return ftp_action(argv[1], argv[2] ? argv[2] : argv[1]);
 360}
 361