|
1 /* |
|
2 * Copyright (c) 2015 Sunil Nimmagadda <sunil@openbsd.org> |
|
3 * |
|
4 * Permission to use, copy, modify, and distribute this software for any |
|
5 * purpose with or without fee is hereby granted, provided that the above |
|
6 * copyright notice and this permission notice appear in all copies. |
|
7 * |
|
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
|
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
|
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
|
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
|
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
|
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
|
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
|
15 */ |
|
16 |
|
17 #include <sys/socket.h> |
|
18 |
|
19 #include <arpa/inet.h> |
|
20 #include <netinet/in.h> |
|
21 |
|
22 #include <err.h> |
|
23 #include <errno.h> |
|
24 #include <libgen.h> |
|
25 #include <limits.h> |
|
26 #include <netdb.h> |
|
27 #include <stdarg.h> |
|
28 #include <stdio.h> |
|
29 #include <stdint.h> |
|
30 #include <stdlib.h> |
|
31 #include <string.h> |
|
32 #include <unistd.h> |
|
33 |
|
34 #include "ftp.h" |
|
35 #include "xmalloc.h" |
|
36 |
|
37 static FILE *ctrl_fp; |
|
38 static int data_fd; |
|
39 |
|
40 void |
|
41 ftp_connect(struct url *url, int timeout) |
|
42 { |
|
43 char *buf = NULL; |
|
44 size_t n = 0; |
|
45 int sock; |
|
46 |
|
47 if ((sock = tcp_connect(url->host, url->port, timeout)) == -1) |
|
48 exit(1); |
|
49 |
|
50 if ((ctrl_fp = fdopen(sock, "r+")) == NULL) |
|
51 err(1, "%s: fdopen", __func__); |
|
52 |
|
53 /* greeting */ |
|
54 if (ftp_getline(&buf, &n, 0, ctrl_fp) != P_OK) { |
|
55 warnx("Can't connect to host `%s'", url->host); |
|
56 ftp_command(ctrl_fp, "QUIT"); |
|
57 exit(1); |
|
58 } |
|
59 |
|
60 free(buf); |
|
61 log_info("Connected to %s\n", url->host); |
|
62 if (ftp_auth(ctrl_fp, NULL, NULL) != P_OK) { |
|
63 warnx("Can't login to host `%s'", url->host); |
|
64 ftp_command(ctrl_fp, "QUIT"); |
|
65 exit(1); |
|
66 } |
|
67 } |
|
68 |
|
69 struct url * |
|
70 ftp_get(struct url *url, off_t *offset, off_t *sz) |
|
71 { |
|
72 char *buf = NULL, *dir, *file; |
|
73 |
|
74 log_info("Using binary mode to transfer files.\n"); |
|
75 if (ftp_command(ctrl_fp, "TYPE I") != P_OK) |
|
76 errx(1, "Failed to set mode to binary"); |
|
77 |
|
78 dir = dirname(url->path); |
|
79 if (ftp_command(ctrl_fp, "CWD %s", dir) != P_OK) |
|
80 errx(1, "CWD command failed"); |
|
81 |
|
82 log_info("Retrieving %s\n", url->path); |
|
83 file = basename(url->path); |
|
84 if (oarg && strcmp(oarg, "-") == 0) |
|
85 log_info("remote: %s\n", file); |
|
86 else |
|
87 log_info("local: %s remote: %s\n", oarg ? oarg : file , file); |
|
88 |
|
89 if (ftp_size(ctrl_fp, file, sz, &buf) != P_OK) { |
|
90 fprintf(stderr, "%s", buf); |
|
91 ftp_command(ctrl_fp, "QUIT"); |
|
92 exit(1); |
|
93 } |
|
94 free(buf); |
|
95 |
|
96 if (activemode) |
|
97 data_fd = ftp_eprt(ctrl_fp); |
|
98 else if ((data_fd = ftp_epsv(ctrl_fp)) == -1) |
|
99 data_fd = ftp_eprt(ctrl_fp); |
|
100 |
|
101 if (data_fd == -1) |
|
102 errx(1, "Failed to establish data connection"); |
|
103 |
|
104 if (*offset && ftp_command(ctrl_fp, "REST %lld", *offset) != P_INTER) |
|
105 errx(1, "REST command failed"); |
|
106 |
|
107 if (ftp_command(ctrl_fp, "RETR %s", file) != P_PRE) { |
|
108 ftp_command(ctrl_fp, "QUIT"); |
|
109 exit(1); |
|
110 } |
|
111 |
|
112 return url; |
|
113 } |
|
114 |
|
115 void |
|
116 ftp_save(struct url *url, FILE *dst_fp, off_t *offset) |
|
117 { |
|
118 struct sockaddr_storage ss; |
|
119 FILE *data_fp; |
|
120 socklen_t len; |
|
121 int s; |
|
122 |
|
123 if (activemode) { |
|
124 len = sizeof(ss); |
|
125 if ((s = accept(data_fd, (struct sockaddr *)&ss, &len)) == -1) |
|
126 err(1, "%s: accept", __func__); |
|
127 |
|
128 close(data_fd); |
|
129 data_fd = s; |
|
130 } |
|
131 |
|
132 if ((data_fp = fdopen(data_fd, "r")) == NULL) |
|
133 err(1, "%s: fdopen data_fd", __func__); |
|
134 |
|
135 copy_file(dst_fp, data_fp, offset); |
|
136 fclose(data_fp); |
|
137 } |
|
138 |
|
139 void |
|
140 ftp_close(struct url *url) |
|
141 { |
|
142 char *buf = NULL; |
|
143 size_t n = 0; |
|
144 |
|
145 /* |
|
146 * Reading reply here after progressmeter stops. |
|
147 */ |
|
148 if (ftp_getline(&buf, &n, 0, ctrl_fp) != P_OK) |
|
149 errx(1, "%s: %s", __func__, buf); |
|
150 |
|
151 free(buf); |
|
152 ftp_command(ctrl_fp, "QUIT"); |
|
153 fclose(ctrl_fp); |
|
154 } |
|
155 |
|
156 int |
|
157 ftp_getline(char **lineptr, size_t *n, int suppress_output, FILE *fp) |
|
158 { |
|
159 ssize_t len; |
|
160 char *bufp, code[4]; |
|
161 const char *errstr; |
|
162 int lookup[] = { P_PRE, P_OK, P_INTER, N_TRANS, N_PERM }; |
|
163 |
|
164 |
|
165 if ((len = getline(lineptr, n, fp)) == -1) |
|
166 err(1, "%s: getline", __func__); |
|
167 |
|
168 bufp = *lineptr; |
|
169 if (!suppress_output) |
|
170 log_info("%s", bufp); |
|
171 |
|
172 if (len < 4) |
|
173 errx(1, "%s: line too short", __func__); |
|
174 |
|
175 (void)strlcpy(code, bufp, sizeof code); |
|
176 if (bufp[3] == ' ') |
|
177 goto done; |
|
178 |
|
179 /* multi-line reply */ |
|
180 while (!(strncmp(code, bufp, 3) == 0 && bufp[3] == ' ')) { |
|
181 if ((len = getline(lineptr, n, fp)) == -1) |
|
182 err(1, "%s: getline", __func__); |
|
183 |
|
184 bufp = *lineptr; |
|
185 if (!suppress_output) |
|
186 log_info("%s", bufp); |
|
187 |
|
188 if (len < 4) |
|
189 continue; |
|
190 } |
|
191 |
|
192 done: |
|
193 (void)strtonum(code, 100, 553, &errstr); |
|
194 if (errstr) |
|
195 errx(1, "%s: Response code is %s: %s", __func__, errstr, code); |
|
196 |
|
197 return lookup[code[0] - '1']; |
|
198 } |
|
199 |
|
200 int |
|
201 ftp_command(FILE *fp, const char *fmt, ...) |
|
202 { |
|
203 va_list ap; |
|
204 char *buf = NULL, *cmd; |
|
205 size_t n = 0; |
|
206 int r; |
|
207 |
|
208 va_start(ap, fmt); |
|
209 r = vasprintf(&cmd, fmt, ap); |
|
210 va_end(ap); |
|
211 if (r < 0) |
|
212 errx(1, "%s: vasprintf", __func__); |
|
213 |
|
214 if (io_debug) |
|
215 fprintf(stderr, ">>> %s\n", cmd); |
|
216 |
|
217 if (fprintf(fp, "%s\r\n", cmd) < 0) |
|
218 errx(1, "%s: fprintf", __func__); |
|
219 |
|
220 (void)fflush(fp); |
|
221 free(cmd); |
|
222 r = ftp_getline(&buf, &n, 0, fp); |
|
223 free(buf); |
|
224 return r; |
|
225 |
|
226 } |
|
227 |
|
228 int |
|
229 ftp_auth(FILE *fp, const char *user, const char *pass) |
|
230 { |
|
231 char *addr = NULL, hn[HOST_NAME_MAX+1], *un; |
|
232 int code; |
|
233 |
|
234 code = ftp_command(fp, "USER %s", user ? user : "anonymous"); |
|
235 if (code != P_OK && code != P_INTER) |
|
236 return code; |
|
237 |
|
238 if (pass == NULL) { |
|
239 if (gethostname(hn, sizeof hn) == -1) |
|
240 err(1, "%s: gethostname", __func__); |
|
241 |
|
242 un = getlogin(); |
|
243 xasprintf(&addr, "%s@%s", un ? un : "anonymous", hn); |
|
244 } |
|
245 |
|
246 code = ftp_command(fp, "PASS %s", pass ? pass : addr); |
|
247 free(addr); |
|
248 return code; |
|
249 } |
|
250 |
|
251 int |
|
252 ftp_size(FILE *fp, const char *fn, off_t *sizep, char **buf) |
|
253 { |
|
254 size_t n = 0; |
|
255 off_t file_sz; |
|
256 int code; |
|
257 |
|
258 if (io_debug) |
|
259 fprintf(stderr, ">>> SIZE %s\n", fn); |
|
260 |
|
261 if (fprintf(fp, "SIZE %s\r\n", fn) < 0) |
|
262 errx(1, "%s: fprintf", __func__); |
|
263 |
|
264 (void)fflush(fp); |
|
265 if ((code = ftp_getline(buf, &n, 1, fp)) != P_OK) |
|
266 return code; |
|
267 |
|
268 if (sscanf(*buf, "%*u %lld", &file_sz) != 1) |
|
269 errx(1, "%s: sscanf size", __func__); |
|
270 |
|
271 if (file_sz < 0 || file_sz > INT64_MAX) |
|
272 errx(1, "%s: size out of bounds: %lld", __func__, file_sz); |
|
273 |
|
274 if (sizep) |
|
275 *sizep = file_sz; |
|
276 |
|
277 return code; |
|
278 } |
|
279 |
|
280 int |
|
281 ftp_eprt(FILE *fp) |
|
282 { |
|
283 struct sockaddr_storage ss; |
|
284 char addr[NI_MAXHOST], port[NI_MAXSERV], *eprt; |
|
285 socklen_t len; |
|
286 int e, on, ret, sock; |
|
287 |
|
288 len = sizeof(ss); |
|
289 memset(&ss, 0, len); |
|
290 if (getsockname(fileno(fp), (struct sockaddr *)&ss, &len) == -1) { |
|
291 warn("%s: getsockname", __func__); |
|
292 return -1; |
|
293 } |
|
294 |
|
295 /* pick a free port */ |
|
296 switch (ss.ss_family) { |
|
297 case AF_INET: |
|
298 ((struct sockaddr_in *)&ss)->sin_port = 0; |
|
299 break; |
|
300 case AF_INET6: |
|
301 ((struct sockaddr_in6 *)&ss)->sin6_port = 0; |
|
302 break; |
|
303 default: |
|
304 errx(1, "%s: Invalid socket family", __func__); |
|
305 } |
|
306 |
|
307 if ((sock = socket(ss.ss_family, SOCK_STREAM, 0)) == -1) { |
|
308 warn("%s: socket", __func__); |
|
309 return -1; |
|
310 } |
|
311 |
|
312 switch (ss.ss_family) { |
|
313 case AF_INET: |
|
314 on = IP_PORTRANGE_HIGH; |
|
315 if (setsockopt(sock, IPPROTO_IP, IP_PORTRANGE, |
|
316 (char *)&on, sizeof(on)) < 0) |
|
317 warn("setsockopt IP_PORTRANGE (ignored)"); |
|
318 break; |
|
319 case AF_INET6: |
|
320 on = IPV6_PORTRANGE_HIGH; |
|
321 if (setsockopt(sock, IPPROTO_IPV6, IPV6_PORTRANGE, |
|
322 (char *)&on, sizeof(on)) < 0) |
|
323 warn("setsockopt IPV6_PORTRANGE (ignored)"); |
|
324 break; |
|
325 } |
|
326 |
|
327 if (bind(sock, (struct sockaddr *)&ss, len) == -1) { |
|
328 close(sock); |
|
329 warn("%s: bind", __func__); |
|
330 return -1; |
|
331 } |
|
332 |
|
333 if (listen(sock, 1) == -1) { |
|
334 close(sock); |
|
335 warn("%s: listen", __func__); |
|
336 return -1; |
|
337 } |
|
338 |
|
339 /* Find out the ephemeral port chosen */ |
|
340 len = sizeof(ss); |
|
341 memset(&ss, 0, len); |
|
342 if (getsockname(sock, (struct sockaddr *)&ss, &len) == -1) { |
|
343 close(sock); |
|
344 warn("%s: getsockname", __func__); |
|
345 return -1; |
|
346 } |
|
347 |
|
348 if ((e = getnameinfo((struct sockaddr *)&ss, len, |
|
349 addr, sizeof(addr), port, sizeof(port), |
|
350 NI_NUMERICHOST | NI_NUMERICSERV)) != 0) { |
|
351 close(sock); |
|
352 warn("%s: getnameinfo: %s", __func__, gai_strerror(e)); |
|
353 return -1; |
|
354 } |
|
355 |
|
356 xasprintf(&eprt, "EPRT |%d|%s|%s|", |
|
357 ss.ss_family == AF_INET ? 1 : 2, addr, port); |
|
358 |
|
359 ret = ftp_command(fp, "%s", eprt); |
|
360 free(eprt); |
|
361 if (ret != P_OK) { |
|
362 close(sock); |
|
363 return -1; |
|
364 } |
|
365 |
|
366 return sock; |
|
367 } |
|
368 |
|
369 int |
|
370 ftp_epsv(FILE *fp) |
|
371 { |
|
372 struct sockaddr_storage ss; |
|
373 char *buf = NULL, delim[4], *s, *e; |
|
374 size_t n = 0; |
|
375 socklen_t len; |
|
376 int error, port, sock; |
|
377 |
|
378 if (io_debug) |
|
379 fprintf(stderr, ">>> EPSV\n"); |
|
380 |
|
381 if (fprintf(fp, "EPSV\r\n") < 0) |
|
382 errx(1, "%s: fprintf", __func__); |
|
383 |
|
384 (void)fflush(fp); |
|
385 if (ftp_getline(&buf, &n, 1, fp) != P_OK) { |
|
386 free(buf); |
|
387 return -1; |
|
388 } |
|
389 |
|
390 if ((s = strchr(buf, '(')) == NULL || (e = strchr(s, ')')) == NULL) { |
|
391 warnx("Malformed EPSV reply"); |
|
392 free(buf); |
|
393 return -1; |
|
394 } |
|
395 |
|
396 s++; |
|
397 *e = '\0'; |
|
398 if (sscanf(s, "%c%c%c%d%c", &delim[0], &delim[1], &delim[2], |
|
399 &port, &delim[3]) != 5) { |
|
400 warnx("EPSV parse error"); |
|
401 free(buf); |
|
402 return -1; |
|
403 } |
|
404 free(buf); |
|
405 |
|
406 if (delim[0] != delim[1] || delim[0] != delim[2] |
|
407 || delim[0] != delim[3]) { |
|
408 warnx("EPSV parse error"); |
|
409 return -1; |
|
410 } |
|
411 |
|
412 len = sizeof(ss); |
|
413 memset(&ss, 0, len); |
|
414 if (getpeername(fileno(fp), (struct sockaddr *)&ss, &len) == -1) { |
|
415 warn("%s: getpeername", __func__); |
|
416 return -1; |
|
417 } |
|
418 |
|
419 switch (ss.ss_family) { |
|
420 case AF_INET: |
|
421 ((struct sockaddr_in *)&ss)->sin_port = htons(port); |
|
422 break; |
|
423 case AF_INET6: |
|
424 ((struct sockaddr_in6 *)&ss)->sin6_port = htons(port); |
|
425 break; |
|
426 default: |
|
427 errx(1, "%s: Invalid socket family", __func__); |
|
428 } |
|
429 |
|
430 if ((sock = socket(ss.ss_family, SOCK_STREAM, 0)) == -1) { |
|
431 warn("%s: socket", __func__); |
|
432 return -1; |
|
433 } |
|
434 |
|
435 for (error = connect(sock, (struct sockaddr *)&ss, len); |
|
436 error != 0 && errno == EINTR; error = connect_wait(sock)) |
|
437 continue; |
|
438 |
|
439 if (error != 0) { |
|
440 warn("%s: connect", __func__); |
|
441 return -1; |
|
442 } |
|
443 |
|
444 return sock; |
|
445 } |