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