xref: /third_party/toybox/toys/net/ftpget.c (revision 0f66f451)
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
10USE_FTPGET(NEWTOY(ftpget, "<2>3P:cp:u:vgslLmMdD[-gs][!gslLmMdD][!clL]", TOYFLAG_USR|TOYFLAG_BIN))
11USE_FTPPUT(OLDTOY(ftpput, ftpget, TOYFLAG_USR|TOYFLAG_BIN))
12
13config FTPGET
14  bool "ftpget"
15  default y
16  help
17    usage: ftpget [-cvgslLmMdD] [-p PORT] [-P PASSWORD] [-u USER] HOST [LOCAL] REMOTE
18
19    Talk to ftp server. By default get REMOTE file via passive anonymous
20    transfer, optionally saving under a LOCAL name. Can also send, list, etc.
21
22    -c	Continue partial transfer
23    -p	Use PORT instead of "21"
24    -P	Use PASSWORD instead of "ftpget@"
25    -u	Use USER instead of "anonymous"
26    -v	Verbose
27
28    Ways to interact with FTP server:
29    -d	Delete file
30    -D	Remove directory
31    -g	Get file (default)
32    -l	List directory
33    -L	List (filenames only)
34    -m	Move file on server from LOCAL to REMOTE
35    -M	mkdir
36    -s	Send file
37
38config FTPPUT
39  bool "ftpput"
40  default y
41  help
42    An ftpget that defaults to -s instead of -g
43*/
44
45#define FOR_ftpget
46#include "toys.h"
47
48GLOBALS(
49  char *u, *p, *P;
50
51  int fd;
52  int datafd;
53  int filefd;
54)
55
56// we should get one line of data, but it may be in multiple chunks
57static int xread2line(int fd, char *buf, int len)
58{
59  int i, total = 0;
60
61  len--;
62  while (total<len && (i = xread(fd, buf, len-total))) {
63    if (i <= 0) {
64      error_msg("xread2line line %d, len %d total %d i %d toybox buf %s\r\n",
65        __LINE__, len, total, i, toybuf);
66        break;
67    }
68    total += i;
69    if (buf[total-1] == '\n') break;
70  }
71  if (total>=len) {
72    error_msg("xread2line line %d, len %d total %d toybox buf %s\r\n",
73      __LINE__, len, total, toybuf);
74    if (TT.fd >= 0) {
75      xclose(TT.fd);
76    }
77    if (TT.datafd >= 0) {
78      xclose(TT.datafd);
79    }
80    if (TT.filefd >= 0) {
81      xclose(TT.filefd);
82    }
83    error_exit("overflow");
84  }
85  while (total--)
86    if (buf[total]=='\r' || buf[total]=='\n') buf[total] = 0;
87    else break;
88  if (toys.optflags & FLAG_v) fprintf(stderr, "%s\n", toybuf);
89
90  return total+1;
91}
92
93static int ftp_line(char *cmd, char *arg, int must)
94{
95  int rc = 0;
96
97  if (cmd) {
98    char *s = "%s %s\r\n"+3*(!arg);
99    if (toys.optflags & FLAG_v) fprintf(stderr, s, cmd, arg);
100    dprintf(TT.fd, s, cmd, arg);
101  }
102  if (must>=0) {
103    xread2line(TT.fd, toybuf, sizeof(toybuf));
104    if (!sscanf(toybuf, "%d", &rc) || (must && rc != must)) {
105      error_msg("ftp_line line %d, must %d rc %d toybox buf %s\r\n",
106        __LINE__, must, rc, toybuf);
107      if (TT.fd >= 0) {
108        xclose(TT.fd);
109      }
110      if (TT.datafd >= 0) {
111        xclose(TT.datafd);
112      }
113      if (TT.filefd >= 0) {
114        xclose(TT.filefd);
115      }
116      error_exit_raw(toybuf);
117    }
118  }
119
120  return rc;
121}
122
123void ftpget_main(void)
124{
125  struct sockaddr_in6 si6;
126  int rc, ii = 1, port = 0;
127  socklen_t sl = sizeof(si6);
128  char *s, *remote = toys.optargs[2];
129  unsigned long long lenl = 0, lenr;
130  TT.fd = -1;
131  TT.datafd = -1;
132  TT.filefd = -1;
133  if (!(toys.optflags&(FLAG_v-1)))
134    toys.optflags |= (toys.which->name[3]=='g') ? FLAG_g : FLAG_s;
135
136  if (!TT.u) TT.u = "anonymous";
137  if (!TT.P) TT.P = "ftpget@";
138  if (!TT.p) TT.p = "21";
139  if (!remote) remote = toys.optargs[1];
140
141  // connect
142  TT.fd = xconnectany(xgetaddrinfo(*toys.optargs, TT.p, 0, SOCK_STREAM, 0,
143    AI_ADDRCONFIG));
144  if (getpeername(TT.fd, (void *)&si6, &sl)) perror_exit("getpeername");
145
146  // Login
147  ftp_line(0, 0, 220);
148  rc = ftp_line("USER", TT.u, 0);
149  if (rc == 331) rc = ftp_line("PASS", TT.P, 0);
150  if (rc != 230) {
151    error_msg("ftpget_main line %d, PASS ret %d toybox buf %s\r\n",
152      __LINE__, rc, toybuf);
153    if (TT.fd >= 0) {
154      xclose(TT.fd);
155    }
156    error_exit_raw(toybuf);
157  }
158
159  if (toys.optflags & FLAG_m) {
160    if (toys.optc != 3) {
161      error_msg("ftpget_main line %d, toys.optflags 0x%x toys.optc %d toybox buf %s\r\n",
162        __LINE__, toys.optflags, toys.optc, toybuf);
163      if (TT.fd >= 0) {
164        xclose(TT.fd);
165      }
166      error_exit("-m FROM TO");
167    }
168    ftp_line("RNFR", toys.optargs[1], 350);
169    ftp_line("RNTO", toys.optargs[2], 250);
170  } else if (toys.optflags & FLAG_M) ftp_line("MKD", toys.optargs[1], 257);
171  else if (toys.optflags & FLAG_d) ftp_line("DELE", toys.optargs[1], 250);
172  else if (toys.optflags & FLAG_D) ftp_line("RMD", toys.optargs[1], 250);
173  else {
174    int get = !(toys.optflags&FLAG_s), cnt = toys.optflags&FLAG_c;
175    char *cmd;
176
177    // Only do passive binary transfers
178    ftp_line("TYPE", "I", 0);
179    rc = ftp_line("PASV", 0, 0);
180
181    // PASV means the server opens a port you connect to instead of the server
182    // dialing back to the client. (Still insane, but less so.) So need port #
183
184    // PASV output is "227 PASV ok (x,x,x,x,p1,p2)" where x,x,x,x is the IP addr
185    // (must match the server you're talking to???) and port is (256*p1)+p2
186    s = 0;
187    if (rc==227) {
188      for (s = toybuf; (s = strchr(s, ',')); s++) {
189        int p1, got = 0;
190
191        sscanf(s, ",%u,%u)%n", &p1, &port, &got);
192        if (!got) continue;
193        port += 256*p1;
194        break;
195      }
196	}
197    if (!s || port<1 || port>65535) {
198      error_msg("ftpget_main line %d, port %d toybox buf %s\r\n", __LINE__, port, toybuf);
199      ftp_line("QUIT", 0, 0);
200      if (TT.fd >= 0) {
201        xclose(TT.fd);
202      }
203      error_exit_raw(toybuf);
204    }
205    si6.sin6_port = SWAP_BE16(port); // same field size/offset for v4 and v6
206    port = xsocket(si6.sin6_family, SOCK_STREAM, 0);
207    TT.datafd = port;
208    xconnect(port, (void *)&si6, sizeof(si6));
209
210    // RETR blocks until file data read from data port, so use SIZE to check
211    // if file exists before creating local copy
212    lenr = 0;
213    if (toys.optflags&(FLAG_s|FLAG_g)) {
214      if (ftp_line("SIZE", remote, 0) == 213) {
215        sscanf(toybuf, "%*u %llu", &lenr);
216      } else if (get) {
217        error_msg("ftpget_main line %d, port %d get %d toybox buf %s\r\n", __LINE__, port, get, toybuf);
218        ftp_line("QUIT", 0, 0);
219        if (TT.fd >= 0) {
220          xclose(TT.fd);
221        }
222        if (TT.datafd >= 0) {
223          xclose(TT.datafd);
224        }
225        error_exit("no %s", remote);
226      }
227    }
228
229    // Open file for reading or writing
230    if (toys.optflags & (FLAG_g|FLAG_s)) {
231      if (strcmp(toys.optargs[1], "-")) {
232        ii = xcreate(toys.optargs[1],
233          get ? (cnt ? O_APPEND : O_TRUNC)|O_CREAT|O_WRONLY : O_RDONLY, 0666);
234        TT.filefd = ii;
235      }
236      lenl = fdlength(ii);
237    }
238    if (get) {
239      cmd = "RETR";
240      if (toys.optflags&FLAG_l) cmd = "LIST";
241      if (toys.optflags&FLAG_L) cmd = "NLST";
242      if (cnt) {
243        char buf[32];
244
245        if (lenl>=lenr) goto done;
246        sprintf(buf, "%llu", lenl);
247        ftp_line("REST", buf, 350);
248      } else lenl = 0;
249
250      ftp_line(cmd, remote, -1);
251      lenl += xsendfile(port, ii);
252      ftp_line(0, 0, (toys.optflags&FLAG_g) ? 226 : 150);
253      close(port);
254      port = -1;
255      TT.datafd = -1;
256    } else if (toys.optflags & FLAG_s) {
257      cmd = "STOR";
258      if (cnt && lenr) {
259        cmd = "APPE";
260        xlseek(ii, lenl, SEEK_SET);
261      } else lenr = 0;
262      ftp_line(cmd, remote, 150);
263      lenr += xsendfile(ii, port);
264      close(port);
265      port = -1;
266      TT.datafd = -1;
267      ftp_line(0, 0, 226);
268    }
269    if (toys.optflags&(FLAG_g|FLAG_s)) {
270      if (lenl != lenr) {
271        error_msg("ftpget_main line %d, len local %d len remote %d toybox buf %s\r\n", __LINE__, lenl, lenr, toybuf);
272        ftp_line("QUIT", 0, ((port == -1) ? -1 : 0));
273        if (TT.fd >= 0) {
274          xclose(TT.fd);
275        }
276        if (TT.datafd >= 0) {
277          xclose(TT.datafd);
278        }
279        if (TT.filefd >= 0) {
280          xclose(TT.filefd);
281        }
282        error_exit("short %lld/%lld", lenl, lenr);
283      }
284    }
285  }
286
287done:
288  ftp_line("QUIT", 0, ((port == -1) ? -1 : 0));
289  if (ii!=1) xclose(ii);
290  if (port>=0) xclose(port);
291  if (TT.fd>=0) xclose(TT.fd);
292}
293