ftpgetput.c 8.7 KB

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