• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
10 USE_FTPGET(NEWTOY(ftpget, "<2>3P:cp:u:vgslLmMdD[-gs][!gslLmMdD][!clL]", TOYFLAG_USR|TOYFLAG_BIN))
11 USE_FTPPUT(OLDTOY(ftpput, ftpget, TOYFLAG_USR|TOYFLAG_BIN))
12 
13 config 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 
38 config 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 
48 GLOBALS(
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
xread2line(int fd,char * buf,int len)57 static 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 
ftp_line(char * cmd,char * arg,int must)93 static 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 
ftpget_main(void)123 void 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, -1);
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, -1);
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 
287 done:
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