ftpgetput.c 9.4 KB

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