ftpget.c 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. /* ftpget.c - Fetch file(s) from ftp server
  2. *
  3. * Copyright 2016 Rob Landley <rob@landley.net>
  4. *
  5. * No standard for the command, but see https://www.ietf.org/rfc/rfc959.txt
  6. * TODO: local can be -
  7. * TEST: -g -s (when local and remote exist) -gc, -sc
  8. * zero length file
  9. USE_FTPGET(NEWTOY(ftpget, "<2>3P:cp:u:vgslLmMdD[-gs][!gslLmMdD][!clL]", TOYFLAG_USR|TOYFLAG_BIN))
  10. USE_FTPPUT(OLDTOY(ftpput, ftpget, TOYFLAG_USR|TOYFLAG_BIN))
  11. config FTPGET
  12. bool "ftpget"
  13. default y
  14. help
  15. usage: ftpget [-cvgslLmMdD] [-P PORT] [-p PASSWORD] [-u USER] HOST [LOCAL] REMOTE
  16. Talk to ftp server. By default get REMOTE file via passive anonymous
  17. transfer, optionally saving under a LOCAL name. Can also send, list, etc.
  18. -c Continue partial transfer
  19. -p Use PORT instead of "21"
  20. -P Use PASSWORD instead of "ftpget@"
  21. -u Use USER instead of "anonymous"
  22. -v Verbose
  23. Ways to interact with FTP server:
  24. -d Delete file
  25. -D Remove directory
  26. -g Get file (default)
  27. -l List directory
  28. -L List (filenames only)
  29. -m Move file on server from LOCAL to REMOTE
  30. -M mkdir
  31. -s Send file
  32. config FTPPUT
  33. bool "ftpput"
  34. default y
  35. help
  36. An ftpget that defaults to -s instead of -g
  37. */
  38. #define FOR_ftpget
  39. #include "toys.h"
  40. GLOBALS(
  41. char *u, *p, *P;
  42. int fd;
  43. )
  44. // we should get one line of data, but it may be in multiple chunks
  45. static int xread2line(int fd, char *buf, int len)
  46. {
  47. int i, total = 0;
  48. len--;
  49. while (total<len && (i = xread(fd, buf, len-total))) {
  50. total += i;
  51. if (buf[total-1] == '\n') break;
  52. }
  53. if (total>=len) error_exit("overflow");
  54. while (total--)
  55. if (buf[total]=='\r' || buf[total]=='\n') buf[total] = 0;
  56. else break;
  57. if (toys.optflags & FLAG_v) fprintf(stderr, "%s\n", toybuf);
  58. return total+1;
  59. }
  60. static int ftp_line(char *cmd, char *arg, int must)
  61. {
  62. int rc = 0;
  63. if (cmd) {
  64. char *s = "%s %s\r\n"+3*(!arg);
  65. if (toys.optflags & FLAG_v) fprintf(stderr, s, cmd, arg);
  66. dprintf(TT.fd, s, cmd, arg);
  67. }
  68. if (must>=0) {
  69. xread2line(TT.fd, toybuf, sizeof(toybuf));
  70. if (!sscanf(toybuf, "%d", &rc) || (must && rc != must))
  71. error_exit_raw(toybuf);
  72. }
  73. return rc;
  74. }
  75. void ftpget_main(void)
  76. {
  77. struct sockaddr_in6 si6;
  78. int rc, ii = 1, port = 0;
  79. socklen_t sl = sizeof(si6);
  80. char *s, *remote = toys.optargs[2];
  81. unsigned long long lenl = 0, lenr;
  82. if (!(toys.optflags&(FLAG_v-1)))
  83. toys.optflags |= (toys.which->name[3]=='g') ? FLAG_g : FLAG_s;
  84. if (!TT.u) TT.u = "anonymous";
  85. if (!TT.P) TT.P = "ftpget@";
  86. if (!TT.p) TT.p = "21";
  87. if (!remote) remote = toys.optargs[1];
  88. // connect
  89. TT.fd = xconnectany(xgetaddrinfo(*toys.optargs, TT.p, 0, SOCK_STREAM, 0,
  90. AI_ADDRCONFIG));
  91. if (getpeername(TT.fd, (void *)&si6, &sl)) perror_exit("getpeername");
  92. // Login
  93. ftp_line(0, 0, 220);
  94. rc = ftp_line("USER", TT.u, 0);
  95. if (rc == 331) rc = ftp_line("PASS", TT.P, 0);
  96. if (rc != 230) error_exit_raw(toybuf);
  97. if (toys.optflags & FLAG_m) {
  98. if (toys.optc != 3) error_exit("-m FROM TO");
  99. ftp_line("RNFR", toys.optargs[1], 350);
  100. ftp_line("RNTO", toys.optargs[2], 250);
  101. } else if (toys.optflags & FLAG_M) ftp_line("MKD", toys.optargs[1], 257);
  102. else if (toys.optflags & FLAG_d) ftp_line("DELE", toys.optargs[1], 250);
  103. else if (toys.optflags & FLAG_D) ftp_line("RMD", toys.optargs[1], 250);
  104. else {
  105. int get = !(toys.optflags&FLAG_s), cnt = toys.optflags&FLAG_c;
  106. char *cmd;
  107. // Only do passive binary transfers
  108. ftp_line("TYPE", "I", 0);
  109. rc = ftp_line("PASV", 0, 0);
  110. // PASV means the server opens a port you connect to instead of the server
  111. // dialing back to the client. (Still insane, but less so.) So need port #
  112. // PASV output is "227 PASV ok (x,x,x,x,p1,p2)" where x,x,x,x is the IP addr
  113. // (must match the server you're talking to???) and port is (256*p1)+p2
  114. s = 0;
  115. if (rc==227) for (s = toybuf; (s = strchr(s, ',')); s++) {
  116. int p1, got = 0;
  117. sscanf(s, ",%u,%u)%n", &p1, &port, &got);
  118. if (!got) continue;
  119. port += 256*p1;
  120. break;
  121. }
  122. if (!s || port<1 || port>65535) error_exit_raw(toybuf);
  123. si6.sin6_port = SWAP_BE16(port); // same field size/offset for v4 and v6
  124. port = xsocket(si6.sin6_family, SOCK_STREAM, 0);
  125. xconnect(port, (void *)&si6, sizeof(si6));
  126. // RETR blocks until file data read from data port, so use SIZE to check
  127. // if file exists before creating local copy
  128. lenr = 0;
  129. if (toys.optflags&(FLAG_s|FLAG_g)) {
  130. if (ftp_line("SIZE", remote, 0) == 213)
  131. sscanf(toybuf, "%*u %llu", &lenr);
  132. else if (get) error_exit("no %s", remote);
  133. }
  134. // Open file for reading or writing
  135. if (toys.optflags & (FLAG_g|FLAG_s)) {
  136. if (strcmp(toys.optargs[1], "-"))
  137. ii = xcreate(toys.optargs[1],
  138. get ? (cnt ? O_APPEND : O_TRUNC)|O_CREAT|O_WRONLY : O_RDONLY, 0666);
  139. lenl = fdlength(ii);
  140. }
  141. if (get) {
  142. cmd = "RETR";
  143. if (toys.optflags&FLAG_l) cmd = "LIST";
  144. if (toys.optflags&FLAG_L) cmd = "NLST";
  145. if (cnt) {
  146. char buf[32];
  147. if (lenl>=lenr) goto done;
  148. sprintf(buf, "%llu", lenl);
  149. ftp_line("REST", buf, 350);
  150. } else lenl = 0;
  151. ftp_line(cmd, remote, -1);
  152. lenl += xsendfile(port, ii);
  153. ftp_line(0, 0, (toys.optflags&FLAG_g) ? 226 : 150);
  154. } else if (toys.optflags & FLAG_s) {
  155. cmd = "STOR";
  156. if (cnt && lenr) {
  157. cmd = "APPE";
  158. xlseek(ii, lenl, SEEK_SET);
  159. } else lenr = 0;
  160. ftp_line(cmd, remote, 150);
  161. lenr += xsendfile(ii, port);
  162. close(port);
  163. }
  164. if (toys.optflags&(FLAG_g|FLAG_s))
  165. if (lenl != lenr) error_exit("short %lld/%lld", lenl, lenr);
  166. }
  167. ftp_line("QUIT", 0, 0);
  168. done:
  169. if (CFG_TOYBOX_FREE) {
  170. if (ii!=1) xclose(ii);
  171. xclose(port);
  172. xclose(TT.fd);
  173. }
  174. }