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