ftpgetput.c 8.8 KB

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