ftpgetput.c 8.6 KB

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