ftpgetput.c 7.4 KB

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