[821] | 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 <bug1@iinet.net.au>
|
---|
| 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 <sys/ioctl.h>
|
---|
| 17 |
|
---|
| 18 | #include <ctype.h>
|
---|
| 19 | #include <errno.h>
|
---|
| 20 | #include <fcntl.h>
|
---|
| 21 | #include <getopt.h>
|
---|
| 22 | #include <signal.h>
|
---|
| 23 | #include <string.h>
|
---|
| 24 | #include <unistd.h>
|
---|
| 25 |
|
---|
| 26 | #include <sys/socket.h>
|
---|
| 27 |
|
---|
| 28 | #include "busybox.h"
|
---|
| 29 |
|
---|
| 30 | typedef struct ftp_host_info_s {
|
---|
| 31 | char *user;
|
---|
| 32 | char *password;
|
---|
| 33 | struct sockaddr_in *s_in;
|
---|
| 34 | } ftp_host_info_t;
|
---|
| 35 |
|
---|
| 36 | static char verbose_flag = 0;
|
---|
| 37 | static char do_continue = 0;
|
---|
| 38 |
|
---|
| 39 | static int ftpcmd(const char *s1, const char *s2, FILE *stream, char *buf)
|
---|
| 40 | {
|
---|
| 41 | if (verbose_flag) {
|
---|
| 42 | bb_error_msg("cmd %s%s", s1, s2);
|
---|
| 43 | }
|
---|
| 44 |
|
---|
| 45 | if (s1) {
|
---|
| 46 | if (s2) {
|
---|
| 47 | fprintf(stream, "%s%s\r\n", s1, s2);
|
---|
| 48 | } else {
|
---|
| 49 | fprintf(stream, "%s\r\n", s1);
|
---|
| 50 | }
|
---|
| 51 | }
|
---|
| 52 | do {
|
---|
| 53 | char *buf_ptr;
|
---|
| 54 |
|
---|
| 55 | if (fgets(buf, 510, stream) == NULL) {
|
---|
| 56 | bb_perror_msg_and_die("fgets()");
|
---|
| 57 | }
|
---|
| 58 | buf_ptr = strstr(buf, "\r\n");
|
---|
| 59 | if (buf_ptr) {
|
---|
| 60 | *buf_ptr = '\0';
|
---|
| 61 | }
|
---|
| 62 | } while (! isdigit(buf[0]) || buf[3] != ' ');
|
---|
| 63 |
|
---|
| 64 | return atoi(buf);
|
---|
| 65 | }
|
---|
| 66 |
|
---|
| 67 | static int xconnect_ftpdata(ftp_host_info_t *server, const char *buf)
|
---|
| 68 | {
|
---|
| 69 | char *buf_ptr;
|
---|
| 70 | unsigned short port_num;
|
---|
| 71 |
|
---|
| 72 | buf_ptr = strrchr(buf, ',');
|
---|
| 73 | *buf_ptr = '\0';
|
---|
| 74 | port_num = atoi(buf_ptr + 1);
|
---|
| 75 |
|
---|
| 76 | buf_ptr = strrchr(buf, ',');
|
---|
| 77 | *buf_ptr = '\0';
|
---|
| 78 | port_num += atoi(buf_ptr + 1) * 256;
|
---|
| 79 |
|
---|
| 80 | server->s_in->sin_port=htons(port_num);
|
---|
| 81 | return(xconnect(server->s_in));
|
---|
| 82 | }
|
---|
| 83 |
|
---|
| 84 | static FILE *ftp_login(ftp_host_info_t *server)
|
---|
| 85 | {
|
---|
| 86 | FILE *control_stream;
|
---|
| 87 | char buf[512];
|
---|
| 88 |
|
---|
| 89 | /* Connect to the command socket */
|
---|
| 90 | control_stream = fdopen(xconnect(server->s_in), "r+");
|
---|
| 91 | if (control_stream == NULL) {
|
---|
| 92 | bb_perror_msg_and_die("Couldnt open control stream");
|
---|
| 93 | }
|
---|
| 94 |
|
---|
| 95 | if (ftpcmd(NULL, NULL, control_stream, buf) != 220) {
|
---|
| 96 | bb_error_msg_and_die("%s", buf + 4);
|
---|
| 97 | }
|
---|
| 98 |
|
---|
| 99 | /* Login to the server */
|
---|
| 100 | switch (ftpcmd("USER ", server->user, control_stream, buf)) {
|
---|
| 101 | case 230:
|
---|
| 102 | break;
|
---|
| 103 | case 331:
|
---|
| 104 | if (ftpcmd("PASS ", server->password, control_stream, buf) != 230) {
|
---|
| 105 | bb_error_msg_and_die("PASS error: %s", buf + 4);
|
---|
| 106 | }
|
---|
| 107 | break;
|
---|
| 108 | default:
|
---|
| 109 | bb_error_msg_and_die("USER error: %s", buf + 4);
|
---|
| 110 | }
|
---|
| 111 |
|
---|
| 112 | ftpcmd("TYPE I", NULL, control_stream, buf);
|
---|
| 113 |
|
---|
| 114 | return(control_stream);
|
---|
| 115 | }
|
---|
| 116 |
|
---|
| 117 | #if !ENABLE_FTPGET
|
---|
| 118 | #define ftp_receive 0
|
---|
| 119 | #else
|
---|
| 120 | static int ftp_receive(ftp_host_info_t *server, FILE *control_stream,
|
---|
| 121 | const char *local_path, char *server_path)
|
---|
| 122 | {
|
---|
| 123 | char buf[512];
|
---|
| 124 | off_t filesize = 0;
|
---|
| 125 | int fd_data;
|
---|
| 126 | int fd_local = -1;
|
---|
| 127 | off_t beg_range = 0;
|
---|
| 128 |
|
---|
| 129 | /* Connect to the data socket */
|
---|
| 130 | if (ftpcmd("PASV", NULL, control_stream, buf) != 227) {
|
---|
| 131 | bb_error_msg_and_die("PASV error: %s", buf + 4);
|
---|
| 132 | }
|
---|
| 133 | fd_data = xconnect_ftpdata(server, buf);
|
---|
| 134 |
|
---|
| 135 | if (ftpcmd("SIZE ", server_path, control_stream, buf) == 213) {
|
---|
| 136 | unsigned long value=filesize;
|
---|
| 137 | if (safe_strtoul(buf + 4, &value))
|
---|
| 138 | bb_error_msg_and_die("SIZE error: %s", buf + 4);
|
---|
| 139 | filesize = value;
|
---|
| 140 | } else {
|
---|
| 141 | filesize = -1;
|
---|
| 142 | do_continue = 0;
|
---|
| 143 | }
|
---|
| 144 |
|
---|
| 145 | if ((local_path[0] == '-') && (local_path[1] == '\0')) {
|
---|
| 146 | fd_local = STDOUT_FILENO;
|
---|
| 147 | do_continue = 0;
|
---|
| 148 | }
|
---|
| 149 |
|
---|
| 150 | if (do_continue) {
|
---|
| 151 | struct stat sbuf;
|
---|
| 152 | if (lstat(local_path, &sbuf) < 0) {
|
---|
| 153 | bb_perror_msg_and_die("fstat()");
|
---|
| 154 | }
|
---|
| 155 | if (sbuf.st_size > 0) {
|
---|
| 156 | beg_range = sbuf.st_size;
|
---|
| 157 | } else {
|
---|
| 158 | do_continue = 0;
|
---|
| 159 | }
|
---|
| 160 | }
|
---|
| 161 |
|
---|
| 162 | if (do_continue) {
|
---|
| 163 | sprintf(buf, "REST %ld", (long)beg_range);
|
---|
| 164 | if (ftpcmd(buf, NULL, control_stream, buf) != 350) {
|
---|
| 165 | do_continue = 0;
|
---|
| 166 | } else {
|
---|
| 167 | filesize -= beg_range;
|
---|
| 168 | }
|
---|
| 169 | }
|
---|
| 170 |
|
---|
| 171 | if (ftpcmd("RETR ", server_path, control_stream, buf) > 150) {
|
---|
| 172 | bb_error_msg_and_die("RETR error: %s", buf + 4);
|
---|
| 173 | }
|
---|
| 174 |
|
---|
| 175 | /* only make a local file if we know that one exists on the remote server */
|
---|
| 176 | if (fd_local == -1) {
|
---|
| 177 | if (do_continue) {
|
---|
| 178 | fd_local = bb_xopen(local_path, O_APPEND | O_WRONLY);
|
---|
| 179 | } else {
|
---|
| 180 | fd_local = bb_xopen(local_path, O_CREAT | O_TRUNC | O_WRONLY);
|
---|
| 181 | }
|
---|
| 182 | }
|
---|
| 183 |
|
---|
| 184 | /* Copy the file */
|
---|
| 185 | if (filesize != -1) {
|
---|
| 186 | if (-1 == bb_copyfd_size(fd_data, fd_local, filesize))
|
---|
| 187 | exit(EXIT_FAILURE);
|
---|
| 188 | } else {
|
---|
| 189 | if (-1 == bb_copyfd_eof(fd_data, fd_local))
|
---|
| 190 | exit(EXIT_FAILURE);
|
---|
| 191 | }
|
---|
| 192 |
|
---|
| 193 | /* close it all down */
|
---|
| 194 | close(fd_data);
|
---|
| 195 | if (ftpcmd(NULL, NULL, control_stream, buf) != 226) {
|
---|
| 196 | bb_error_msg_and_die("ftp error: %s", buf + 4);
|
---|
| 197 | }
|
---|
| 198 | ftpcmd("QUIT", NULL, control_stream, buf);
|
---|
| 199 |
|
---|
| 200 | return(EXIT_SUCCESS);
|
---|
| 201 | }
|
---|
| 202 | #endif
|
---|
| 203 |
|
---|
| 204 | #if !ENABLE_FTPPUT
|
---|
| 205 | #define ftp_send 0
|
---|
| 206 | #else
|
---|
| 207 | static int ftp_send(ftp_host_info_t *server, FILE *control_stream,
|
---|
| 208 | const char *server_path, char *local_path)
|
---|
| 209 | {
|
---|
| 210 | struct stat sbuf;
|
---|
| 211 | char buf[512];
|
---|
| 212 | int fd_data;
|
---|
| 213 | int fd_local;
|
---|
| 214 | int response;
|
---|
| 215 |
|
---|
| 216 | /* Connect to the data socket */
|
---|
| 217 | if (ftpcmd("PASV", NULL, control_stream, buf) != 227) {
|
---|
| 218 | bb_error_msg_and_die("PASV error: %s", buf + 4);
|
---|
| 219 | }
|
---|
| 220 | fd_data = xconnect_ftpdata(server, buf);
|
---|
| 221 |
|
---|
| 222 | /* get the local file */
|
---|
| 223 | if ((local_path[0] == '-') && (local_path[1] == '\0')) {
|
---|
| 224 | fd_local = STDIN_FILENO;
|
---|
| 225 | } else {
|
---|
| 226 | fd_local = bb_xopen(local_path, O_RDONLY);
|
---|
| 227 | fstat(fd_local, &sbuf);
|
---|
| 228 |
|
---|
| 229 | sprintf(buf, "ALLO %lu", (unsigned long)sbuf.st_size);
|
---|
| 230 | response = ftpcmd(buf, NULL, control_stream, buf);
|
---|
| 231 | switch (response) {
|
---|
| 232 | case 200:
|
---|
| 233 | case 202:
|
---|
| 234 | break;
|
---|
| 235 | default:
|
---|
| 236 | close(fd_local);
|
---|
| 237 | bb_error_msg_and_die("ALLO error: %s", buf + 4);
|
---|
| 238 | break;
|
---|
| 239 | }
|
---|
| 240 | }
|
---|
| 241 | response = ftpcmd("STOR ", server_path, control_stream, buf);
|
---|
| 242 | switch (response) {
|
---|
| 243 | case 125:
|
---|
| 244 | case 150:
|
---|
| 245 | break;
|
---|
| 246 | default:
|
---|
| 247 | close(fd_local);
|
---|
| 248 | bb_error_msg_and_die("STOR error: %s", buf + 4);
|
---|
| 249 | }
|
---|
| 250 |
|
---|
| 251 | /* transfer the file */
|
---|
| 252 | if (bb_copyfd_eof(fd_local, fd_data) == -1) {
|
---|
| 253 | exit(EXIT_FAILURE);
|
---|
| 254 | }
|
---|
| 255 |
|
---|
| 256 | /* close it all down */
|
---|
| 257 | close(fd_data);
|
---|
| 258 | if (ftpcmd(NULL, NULL, control_stream, buf) != 226) {
|
---|
| 259 | bb_error_msg_and_die("error: %s", buf + 4);
|
---|
| 260 | }
|
---|
| 261 | ftpcmd("QUIT", NULL, control_stream, buf);
|
---|
| 262 |
|
---|
| 263 | return(EXIT_SUCCESS);
|
---|
| 264 | }
|
---|
| 265 | #endif
|
---|
| 266 |
|
---|
| 267 | #define FTPGETPUT_OPT_CONTINUE 1
|
---|
| 268 | #define FTPGETPUT_OPT_VERBOSE 2
|
---|
| 269 | #define FTPGETPUT_OPT_USER 4
|
---|
| 270 | #define FTPGETPUT_OPT_PASSWORD 8
|
---|
| 271 | #define FTPGETPUT_OPT_PORT 16
|
---|
| 272 |
|
---|
| 273 | #if ENABLE_FEATURE_FTPGETPUT_LONG_OPTIONS
|
---|
| 274 | static const struct option ftpgetput_long_options[] = {
|
---|
| 275 | {"continue", 1, NULL, 'c'},
|
---|
| 276 | {"verbose", 0, NULL, 'v'},
|
---|
| 277 | {"username", 1, NULL, 'u'},
|
---|
| 278 | {"password", 1, NULL, 'p'},
|
---|
| 279 | {"port", 1, NULL, 'P'},
|
---|
| 280 | {0, 0, 0, 0}
|
---|
| 281 | };
|
---|
| 282 | #else
|
---|
| 283 | #define ftpgetput_long_options 0
|
---|
| 284 | #endif
|
---|
| 285 |
|
---|
| 286 | int ftpgetput_main(int argc, char **argv)
|
---|
| 287 | {
|
---|
| 288 | /* content-length of the file */
|
---|
| 289 | unsigned long opt;
|
---|
| 290 | char *port = "ftp";
|
---|
| 291 |
|
---|
| 292 | /* socket to ftp server */
|
---|
| 293 | FILE *control_stream;
|
---|
| 294 | struct sockaddr_in s_in;
|
---|
| 295 |
|
---|
| 296 | /* continue a prev transfer (-c) */
|
---|
| 297 | ftp_host_info_t *server;
|
---|
| 298 |
|
---|
| 299 | int (*ftp_action)(ftp_host_info_t *, FILE *, const char *, char *) = NULL;
|
---|
| 300 |
|
---|
| 301 | /* Check to see if the command is ftpget or ftput */
|
---|
| 302 | if (ENABLE_FTPPUT && (!ENABLE_FTPGET || bb_applet_name[3] == 'p')) {
|
---|
| 303 | ftp_action = ftp_send;
|
---|
| 304 | }
|
---|
| 305 | if (ENABLE_FTPGET && (!ENABLE_FTPPUT || bb_applet_name[3] == 'g')) {
|
---|
| 306 | ftp_action = ftp_receive;
|
---|
| 307 | }
|
---|
| 308 |
|
---|
| 309 | /* Set default values */
|
---|
| 310 | server = xmalloc(sizeof(ftp_host_info_t));
|
---|
| 311 | server->user = "anonymous";
|
---|
| 312 | server->password = "busybox@";
|
---|
| 313 | verbose_flag = 0;
|
---|
| 314 |
|
---|
| 315 | /*
|
---|
| 316 | * Decipher the command line
|
---|
| 317 | */
|
---|
| 318 | if (ENABLE_FEATURE_FTPGETPUT_LONG_OPTIONS)
|
---|
| 319 | bb_applet_long_options = ftpgetput_long_options;
|
---|
| 320 |
|
---|
| 321 | opt = bb_getopt_ulflags(argc, argv, "cvu:p:P:", &server->user, &server->password, &port);
|
---|
| 322 |
|
---|
| 323 | /* Process the non-option command line arguments */
|
---|
| 324 | if (argc - optind != 3) {
|
---|
| 325 | bb_show_usage();
|
---|
| 326 | }
|
---|
| 327 |
|
---|
| 328 | if (opt & FTPGETPUT_OPT_CONTINUE) {
|
---|
| 329 | do_continue = 1;
|
---|
| 330 | }
|
---|
| 331 | if (opt & FTPGETPUT_OPT_VERBOSE) {
|
---|
| 332 | verbose_flag = 1;
|
---|
| 333 | }
|
---|
| 334 |
|
---|
| 335 | /* We want to do exactly _one_ DNS lookup, since some
|
---|
| 336 | * sites (i.e. ftp.us.debian.org) use round-robin DNS
|
---|
| 337 | * and we want to connect to only one IP... */
|
---|
| 338 | server->s_in = &s_in;
|
---|
| 339 | bb_lookup_host(&s_in, argv[optind]);
|
---|
| 340 | s_in.sin_port = bb_lookup_port(port, "tcp", 21);
|
---|
| 341 | if (verbose_flag) {
|
---|
| 342 | printf("Connecting to %s[%s]:%d\n",
|
---|
| 343 | argv[optind], inet_ntoa(s_in.sin_addr), ntohs(s_in.sin_port));
|
---|
| 344 | }
|
---|
| 345 |
|
---|
| 346 | /* Connect/Setup/Configure the FTP session */
|
---|
| 347 | control_stream = ftp_login(server);
|
---|
| 348 |
|
---|
| 349 | return(ftp_action(server, control_stream, argv[optind + 1], argv[optind + 2]));
|
---|
| 350 | }
|
---|