1 /* $OpenBSD: fetch.c,v 1.218 2024/04/23 08:50:38 sthen Exp $ */
2 /* $NetBSD: fetch.c,v 1.14 1997/08/18 10:20:20 lukem Exp $ */
3
4 /*-
5 * Copyright (c) 1997 The NetBSD Foundation, Inc.
6 * All rights reserved.
7 *
8 * This code is derived from software contributed to The NetBSD Foundation
9 * by Jason Thorpe and Luke Mewburn.
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
21 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
24 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30 * POSSIBILITY OF SUCH DAMAGE.
31 */
32
33 /*
34 * FTP User Program -- Command line file retrieval
35 */
36
37 #include <sys/types.h>
38 #include <sys/socket.h>
39 #include <sys/stat.h>
40
41 #include <netinet/in.h>
42
43 #include <arpa/ftp.h>
44 #include <arpa/inet.h>
45
46 #include <ctype.h>
47 #include <err.h>
48 #include <libgen.h>
49 #include <netdb.h>
50 #include <fcntl.h>
51 #include <signal.h>
52 #include <vis.h>
53 #include <stdio.h>
54 #include <stdarg.h>
55 #include <errno.h>
56 #include <stdlib.h>
57 #include <string.h>
58 #include <unistd.h>
59 #include <resolv.h>
60 #include <utime.h>
61
62 #ifndef NOSSL
63 #include <tls.h>
64 #else /* !NOSSL */
65 struct tls;
66 #endif /* !NOSSL */
67
68 #include "ftp_var.h"
69 #include "cmds.h"
70
71 static int file_get(const char *, const char *);
72 static int url_get(const char *, const char *, const char *, int);
73 static int save_chunked(FILE *, struct tls *, int , char *, size_t);
74 static void aborthttp(int);
75 static char hextochar(const char *);
76 static char *urldecode(const char *);
77 static char *recode_credentials(const char *_userinfo);
78 static void ftp_close(FILE **, struct tls **, int *);
79 static const char *sockerror(struct tls *);
80 #ifdef SMALL
81 #define ftp_printf(fp, ...) fprintf(fp, __VA_ARGS__)
82 #else
83 static int ftp_printf(FILE *, const char *, ...);
84 #endif /* SMALL */
85 #ifndef NOSSL
86 static int proxy_connect(int, char *, char *);
87 static int stdio_tls_write_wrapper(void *, const char *, int);
88 static int stdio_tls_read_wrapper(void *, char *, int);
89 #endif /* !NOSSL */
90
91 #define FTP_URL "ftp://" /* ftp URL prefix */
92 #define HTTP_URL "http://" /* http URL prefix */
93 #define HTTPS_URL "https://" /* https URL prefix */
94 #define FILE_URL "file:" /* file URL prefix */
95 #define FTP_PROXY "ftp_proxy" /* env var with ftp proxy location */
96 #define HTTP_PROXY "http_proxy" /* env var with http proxy location */
97
98 #define EMPTYSTRING(x) ((x) == NULL || (*(x) == '\0'))
99
100 static const char at_encoding_warning[] =
101 "Extra `@' characters in usernames and passwords should be encoded as %%40";
102
103 static jmp_buf httpabort;
104
105 static int redirect_loop;
106 static int retried;
107
108 /*
109 * Determine whether the character needs encoding, per RFC2396.
110 */
111 static int
to_encode(const char * c0)112 to_encode(const char *c0)
113 {
114 /* 2.4.3. Excluded US-ASCII Characters */
115 const char *excluded_chars =
116 " " /* space */
117 "<>#\"" /* delims (modulo "%", see below) */
118 "{}|\\^[]`" /* unwise */
119 ;
120 const unsigned char *c = (const unsigned char *)c0;
121
122 /*
123 * No corresponding graphic US-ASCII.
124 * Control characters and octets not used in US-ASCII.
125 */
126 return (iscntrl(*c) || !isascii(*c) ||
127
128 /*
129 * '%' is also reserved, if is not followed by two
130 * hexadecimal digits.
131 */
132 strchr(excluded_chars, *c) != NULL ||
133 (*c == '%' && (!isxdigit(c[1]) || !isxdigit(c[2]))));
134 }
135
136 /*
137 * Encode given URL, per RFC2396.
138 * Allocate and return string to the caller.
139 */
140 static char *
url_encode(const char * path)141 url_encode(const char *path)
142 {
143 size_t i, length, new_length;
144 char *epath, *epathp;
145
146 length = new_length = strlen(path);
147
148 /*
149 * First pass:
150 * Count characters to encode and determine length of the final URL.
151 */
152 for (i = 0; i < length; i++)
153 if (to_encode(path + i))
154 new_length += 2;
155
156 epath = epathp = malloc(new_length + 1); /* One more for '\0'. */
157 if (epath == NULL)
158 err(1, "Can't allocate memory for URL encoding");
159
160 /*
161 * Second pass:
162 * Encode, and copy final URL.
163 */
164 for (i = 0; i < length; i++)
165 if (to_encode(path + i)) {
166 snprintf(epathp, 4, "%%" "%02x",
167 (unsigned char)path[i]);
168 epathp += 3;
169 } else
170 *(epathp++) = path[i];
171
172 *epathp = '\0';
173 return (epath);
174 }
175
176 /*
177 * Copy a local file (used by the OpenBSD installer).
178 * Returns -1 on failure, 0 on success
179 */
180 static int
file_get(const char * path,const char * outfile)181 file_get(const char *path, const char *outfile)
182 {
183 struct stat st;
184 int fd, out = -1, rval = -1, save_errno;
185 volatile sig_t oldintr, oldinti;
186 const char *savefile;
187 char *buf = NULL, *cp, *pathbuf = NULL;
188 const size_t buflen = 128 * 1024;
189 off_t hashbytes;
190 ssize_t len, wlen;
191
192 direction = "received";
193
194 fd = open(path, O_RDONLY);
195 if (fd == -1) {
196 warn("Can't open file %s", path);
197 return -1;
198 }
199
200 if (fstat(fd, &st) == -1)
201 filesize = -1;
202 else
203 filesize = st.st_size;
204
205 if (outfile != NULL)
206 savefile = outfile;
207 else {
208 if (path[strlen(path) - 1] == '/') /* Consider no file */
209 savefile = NULL; /* after dir invalid. */
210 else {
211 pathbuf = strdup(path);
212 if (pathbuf == NULL)
213 errx(1, "Can't allocate memory for filename");
214 savefile = basename(pathbuf);
215 }
216 }
217
218 if (EMPTYSTRING(savefile)) {
219 warnx("No filename after directory (use -o): %s", path);
220 goto cleanup_copy;
221 }
222
223 /* Open the output file. */
224 if (!pipeout) {
225 out = open(savefile, O_CREAT | O_WRONLY | O_TRUNC, 0666);
226 if (out == -1) {
227 warn("Can't open %s", savefile);
228 goto cleanup_copy;
229 }
230 } else
231 out = fileno(stdout);
232
233 if ((buf = malloc(buflen)) == NULL)
234 errx(1, "Can't allocate memory for transfer buffer");
235
236 /* Trap signals */
237 oldintr = NULL;
238 oldinti = NULL;
239 if (setjmp(httpabort)) {
240 if (oldintr)
241 (void)signal(SIGINT, oldintr);
242 if (oldinti)
243 (void)signal(SIGINFO, oldinti);
244 goto cleanup_copy;
245 }
246 oldintr = signal(SIGINT, aborthttp);
247
248 bytes = 0;
249 hashbytes = mark;
250 progressmeter(-1, path);
251
252 /* Finally, suck down the file. */
253 oldinti = signal(SIGINFO, psummary);
254 while ((len = read(fd, buf, buflen)) > 0) {
255 bytes += len;
256 for (cp = buf; len > 0; len -= wlen, cp += wlen) {
257 if ((wlen = write(out, cp, len)) == -1) {
258 warn("Writing %s", savefile);
259 signal(SIGINT, oldintr);
260 signal(SIGINFO, oldinti);
261 goto cleanup_copy;
262 }
263 }
264 if (hash && !progress) {
265 while (bytes >= hashbytes) {
266 (void)putc('#', ttyout);
267 hashbytes += mark;
268 }
269 (void)fflush(ttyout);
270 }
271 }
272 save_errno = errno;
273 signal(SIGINT, oldintr);
274 signal(SIGINFO, oldinti);
275 if (hash && !progress && bytes > 0) {
276 if (bytes < mark)
277 (void)putc('#', ttyout);
278 (void)putc('\n', ttyout);
279 (void)fflush(ttyout);
280 }
281 if (len == -1) {
282 warnc(save_errno, "Reading from file");
283 goto cleanup_copy;
284 }
285 progressmeter(1, NULL);
286 if (verbose)
287 ptransfer(0);
288
289 rval = 0;
290
291 cleanup_copy:
292 free(buf);
293 free(pathbuf);
294 if (out >= 0 && out != fileno(stdout))
295 close(out);
296 close(fd);
297
298 return rval;
299 }
300
301 /*
302 * Retrieve URL, via the proxy in $proxyvar if necessary.
303 * Returns -1 on failure, 0 on success
304 */
305 static int
url_get(const char * origline,const char * proxyenv,const char * outfile,int lastfile)306 url_get(const char *origline, const char *proxyenv, const char *outfile, int lastfile)
307 {
308 char pbuf[NI_MAXSERV], hbuf[NI_MAXHOST], *cp, *portnum, *path, ststr[4];
309 char *hosttail, *cause = "unknown", *newline, *host, *port, *buf = NULL;
310 char *epath, *redirurl, *loctail, *h, *p, gerror[200];
311 int error, isftpurl = 0, isredirect = 0, rval = -1;
312 int isunavail = 0, retryafter = -1;
313 struct addrinfo hints, *res0, *res;
314 const char *savefile;
315 char *pathbuf = NULL;
316 char *proxyurl = NULL;
317 char *credentials = NULL, *proxy_credentials = NULL;
318 int fd = -1, out = -1;
319 volatile sig_t oldintr, oldinti;
320 FILE *fin = NULL;
321 off_t hashbytes;
322 const char *errstr;
323 ssize_t len, wlen;
324 size_t bufsize;
325 char *proxyhost = NULL;
326 #ifndef NOSSL
327 char *sslpath = NULL, *sslhost = NULL;
328 int ishttpsurl = 0;
329 #endif /* !NOSSL */
330 #ifndef SMALL
331 char *full_host = NULL;
332 const char *scheme;
333 char *locbase;
334 struct addrinfo *ares = NULL;
335 char tmbuf[32];
336 time_t mtime = 0;
337 struct stat stbuf;
338 struct tm lmt = { 0 };
339 struct timespec ts[2];
340 #endif /* !SMALL */
341 struct tls *tls = NULL;
342 int status;
343 int save_errno;
344 const size_t buflen = 128 * 1024;
345 int chunked = 0;
346
347 direction = "received";
348
349 newline = strdup(origline);
350 if (newline == NULL)
351 errx(1, "Can't allocate memory to parse URL");
352 if (strncasecmp(newline, HTTP_URL, sizeof(HTTP_URL) - 1) == 0) {
353 host = newline + sizeof(HTTP_URL) - 1;
354 #ifndef SMALL
355 scheme = HTTP_URL;
356 #endif /* !SMALL */
357 } else if (strncasecmp(newline, FTP_URL, sizeof(FTP_URL) - 1) == 0) {
358 host = newline + sizeof(FTP_URL) - 1;
359 isftpurl = 1;
360 #ifndef SMALL
361 scheme = FTP_URL;
362 #endif /* !SMALL */
363 } else if (strncasecmp(newline, HTTPS_URL, sizeof(HTTPS_URL) - 1) == 0) {
364 #ifndef NOSSL
365 host = newline + sizeof(HTTPS_URL) - 1;
366 ishttpsurl = 1;
367 #else
368 errx(1, "%s: No HTTPS support", newline);
369 #endif /* !NOSSL */
370 #ifndef SMALL
371 scheme = HTTPS_URL;
372 #endif /* !SMALL */
373 } else
374 errx(1, "%s: URL not permitted", newline);
375
376 path = strchr(host, '/'); /* Find path */
377
378 /*
379 * Look for auth header in host.
380 * Basic auth from RFC 2617, valid characters for path are in
381 * RFC 3986 section 3.3.
382 */
383 if (!isftpurl) {
384 p = strchr(host, '@');
385 if (p != NULL && (path == NULL || p < path)) {
386 *p++ = '\0';
387 credentials = recode_credentials(host);
388
389 /* Overwrite userinfo */
390 memmove(host, p, strlen(p) + 1);
391 path = strchr(host, '/');
392 }
393 }
394
395 if (EMPTYSTRING(path)) {
396 if (outfile) { /* No slash, but */
397 path = strchr(host,'\0'); /* we have outfile. */
398 goto noslash;
399 }
400 if (isftpurl)
401 goto noftpautologin;
402 warnx("No `/' after host (use -o): %s", origline);
403 goto cleanup_url_get;
404 }
405 *path++ = '\0';
406 if (EMPTYSTRING(path) && !outfile) {
407 if (isftpurl)
408 goto noftpautologin;
409 warnx("No filename after host (use -o): %s", origline);
410 goto cleanup_url_get;
411 }
412
413 noslash:
414 if (outfile)
415 savefile = outfile;
416 else {
417 if (path[strlen(path) - 1] == '/') /* Consider no file */
418 savefile = NULL; /* after dir invalid. */
419 else {
420 pathbuf = strdup(path);
421 if (pathbuf == NULL)
422 errx(1, "Can't allocate memory for filename");
423 savefile = basename(pathbuf);
424 }
425 }
426
427 if (EMPTYSTRING(savefile)) {
428 if (isftpurl)
429 goto noftpautologin;
430 warnx("No filename after directory (use -o): %s", origline);
431 goto cleanup_url_get;
432 }
433
434 #ifndef SMALL
435 if (resume && pipeout) {
436 warnx("can't append to stdout");
437 goto cleanup_url_get;
438 }
439 #endif /* !SMALL */
440
441 if (proxyenv != NULL) { /* use proxy */
442 #ifndef NOSSL
443 if (ishttpsurl) {
444 sslpath = strdup(path);
445 sslhost = strdup(host);
446 if (! sslpath || ! sslhost)
447 errx(1, "Can't allocate memory for https path/host.");
448 }
449 #endif /* !NOSSL */
450 proxyhost = strdup(host);
451 if (proxyhost == NULL)
452 errx(1, "Can't allocate memory for proxy host.");
453 proxyurl = strdup(proxyenv);
454 if (proxyurl == NULL)
455 errx(1, "Can't allocate memory for proxy URL.");
456 if (strncasecmp(proxyurl, HTTP_URL, sizeof(HTTP_URL) - 1) == 0)
457 host = proxyurl + sizeof(HTTP_URL) - 1;
458 else if (strncasecmp(proxyurl, FTP_URL, sizeof(FTP_URL) - 1) == 0)
459 host = proxyurl + sizeof(FTP_URL) - 1;
460 else {
461 warnx("Malformed proxy URL: %s", proxyenv);
462 goto cleanup_url_get;
463 }
464 if (EMPTYSTRING(host)) {
465 warnx("Malformed proxy URL: %s", proxyenv);
466 goto cleanup_url_get;
467 }
468 if (*--path == '\0')
469 *path = '/'; /* add / back to real path */
470 path = strchr(host, '/'); /* remove trailing / on host */
471 if (!EMPTYSTRING(path))
472 *path++ = '\0'; /* i guess this ++ is useless */
473
474 path = strchr(host, '@'); /* look for credentials in proxy */
475 if (!EMPTYSTRING(path)) {
476 *path = '\0';
477 if (strchr(host, ':') == NULL) {
478 warnx("Malformed proxy URL: %s", proxyenv);
479 goto cleanup_url_get;
480 }
481 proxy_credentials = recode_credentials(host);
482 *path = '@'; /* restore @ in proxyurl */
483
484 /*
485 * This removes the password from proxyurl,
486 * filling with stars
487 */
488 for (host = 1 + strchr(proxyurl + 5, ':'); *host != '@';
489 host++)
490 *host = '*';
491
492 host = path + 1;
493 }
494
495 path = newline;
496 }
497
498 if (*host == '[' && (hosttail = strrchr(host, ']')) != NULL &&
499 (hosttail[1] == '\0' || hosttail[1] == ':')) {
500 host++;
501 *hosttail++ = '\0';
502 #ifndef SMALL
503 if (asprintf(&full_host, "[%s]", host) == -1)
504 errx(1, "Cannot allocate memory for hostname");
505 #endif /* !SMALL */
506 } else
507 hosttail = host;
508
509 portnum = strrchr(hosttail, ':'); /* find portnum */
510 if (portnum != NULL)
511 *portnum++ = '\0';
512 #ifndef NOSSL
513 port = portnum ? portnum : (ishttpsurl ? httpsport : httpport);
514 #else /* !NOSSL */
515 port = portnum ? portnum : httpport;
516 #endif /* !NOSSL */
517
518 #ifndef SMALL
519 if (full_host == NULL)
520 if ((full_host = strdup(host)) == NULL)
521 errx(1, "Cannot allocate memory for hostname");
522 if (debug)
523 fprintf(ttyout, "host %s, port %s, path %s, "
524 "save as %s, auth %s.\n", host, port, path,
525 savefile, credentials ? credentials : "none");
526 #endif /* !SMALL */
527
528 memset(&hints, 0, sizeof(hints));
529 hints.ai_family = family;
530 hints.ai_socktype = SOCK_STREAM;
531 error = getaddrinfo(host, port, &hints, &res0);
532 /*
533 * If the services file is corrupt/missing, fall back
534 * on our hard-coded defines.
535 */
536 if (error == EAI_SERVICE && port == httpport) {
537 snprintf(pbuf, sizeof(pbuf), "%d", HTTP_PORT);
538 error = getaddrinfo(host, pbuf, &hints, &res0);
539 #ifndef NOSSL
540 } else if (error == EAI_SERVICE && port == httpsport) {
541 snprintf(pbuf, sizeof(pbuf), "%d", HTTPS_PORT);
542 error = getaddrinfo(host, pbuf, &hints, &res0);
543 #endif /* !NOSSL */
544 }
545 if (error) {
546 warnx("%s: %s", host, gai_strerror(error));
547 goto cleanup_url_get;
548 }
549
550 #ifndef SMALL
551 if (srcaddr) {
552 hints.ai_flags |= AI_NUMERICHOST;
553 error = getaddrinfo(srcaddr, NULL, &hints, &ares);
554 if (error) {
555 warnx("%s: %s", srcaddr, gai_strerror(error));
556 goto cleanup_url_get;
557 }
558 }
559 #endif /* !SMALL */
560
561 /* ensure consistent order of the output */
562 if (verbose)
563 setvbuf(ttyout, NULL, _IOLBF, 0);
564
565 fd = -1;
566 for (res = res0; res; res = res->ai_next) {
567 if (getnameinfo(res->ai_addr, res->ai_addrlen, hbuf,
568 sizeof(hbuf), NULL, 0, NI_NUMERICHOST) != 0)
569 strlcpy(hbuf, "(unknown)", sizeof(hbuf));
570 if (verbose)
571 fprintf(ttyout, "Trying %s...\n", hbuf);
572
573 fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
574 if (fd == -1) {
575 cause = "socket";
576 continue;
577 }
578
579 #ifndef SMALL
580 if (srcaddr) {
581 if (ares->ai_family != res->ai_family) {
582 close(fd);
583 fd = -1;
584 errno = EINVAL;
585 cause = "bind";
586 continue;
587 }
588 if (bind(fd, ares->ai_addr, ares->ai_addrlen) == -1) {
589 save_errno = errno;
590 close(fd);
591 errno = save_errno;
592 fd = -1;
593 cause = "bind";
594 continue;
595 }
596 }
597 #endif /* !SMALL */
598
599 error = timed_connect(fd, res->ai_addr, res->ai_addrlen,
600 connect_timeout);
601 if (error != 0) {
602 save_errno = errno;
603 close(fd);
604 errno = save_errno;
605 fd = -1;
606 cause = "connect";
607 continue;
608 }
609
610 /* get port in numeric */
611 if (getnameinfo(res->ai_addr, res->ai_addrlen, NULL, 0,
612 pbuf, sizeof(pbuf), NI_NUMERICSERV) == 0)
613 port = pbuf;
614 else
615 port = NULL;
616
617 #ifndef NOSSL
618 if (proxyenv && sslhost)
619 proxy_connect(fd, sslhost, proxy_credentials);
620 #endif /* !NOSSL */
621 break;
622 }
623 freeaddrinfo(res0);
624 #ifndef SMALL
625 if (srcaddr)
626 freeaddrinfo(ares);
627 #endif /* !SMALL */
628 if (fd < 0) {
629 warn("%s", cause);
630 goto cleanup_url_get;
631 }
632
633 #ifndef NOSSL
634 if (ishttpsurl) {
635 ssize_t ret;
636 if (proxyenv && sslpath) {
637 ishttpsurl = 0;
638 proxyurl = NULL;
639 path = sslpath;
640 }
641 if (sslhost == NULL) {
642 sslhost = strdup(host);
643 if (sslhost == NULL)
644 errx(1, "Can't allocate memory for https host.");
645 }
646 if ((tls = tls_client()) == NULL) {
647 fprintf(ttyout, "failed to create SSL client\n");
648 goto cleanup_url_get;
649 }
650 if (tls_configure(tls, tls_config) != 0) {
651 fprintf(ttyout, "TLS configuration failure: %s\n",
652 tls_error(tls));
653 goto cleanup_url_get;
654 }
655 if (tls_connect_socket(tls, fd, sslhost) != 0) {
656 fprintf(ttyout, "TLS connect failure: %s\n", tls_error(tls));
657 goto cleanup_url_get;
658 }
659 do {
660 ret = tls_handshake(tls);
661 } while (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT);
662 if (ret != 0) {
663 fprintf(ttyout, "TLS handshake failure: %s\n", tls_error(tls));
664 goto cleanup_url_get;
665 }
666 fin = funopen(tls, stdio_tls_read_wrapper,
667 stdio_tls_write_wrapper, NULL, NULL);
668 } else {
669 fin = fdopen(fd, "r+");
670 fd = -1;
671 }
672 #else /* !NOSSL */
673 fin = fdopen(fd, "r+");
674 fd = -1;
675 #endif /* !NOSSL */
676
677 #ifdef SMALL
678 if (lastfile) {
679 if (pipeout) {
680 if (pledge("stdio rpath inet dns tty", NULL) == -1)
681 err(1, "pledge");
682 } else {
683 if (pledge("stdio rpath wpath cpath inet dns tty", NULL) == -1)
684 err(1, "pledge");
685 }
686 }
687 #endif
688
689 /*
690 * Construct and send the request. Proxy requests don't want leading /.
691 */
692 #ifndef NOSSL
693 cookie_get(host, path, ishttpsurl, &buf);
694 #endif /* !NOSSL */
695
696 epath = url_encode(path);
697 if (proxyurl) {
698 if (verbose) {
699 fprintf(ttyout, "Requesting %s (via %s)\n",
700 origline, proxyurl);
701 }
702 /*
703 * Host: directive must use the destination host address for
704 * the original URI (path).
705 */
706 ftp_printf(fin, "GET %s HTTP/1.1\r\n"
707 "Connection: close\r\n"
708 "Host: %s\r\n%s%s\r\n"
709 "Accept: */*\r\n",
710 epath, proxyhost, buf ? buf : "", httpuseragent);
711 if (credentials)
712 ftp_printf(fin, "Authorization: Basic %s\r\n",
713 credentials);
714 if (proxy_credentials)
715 ftp_printf(fin, "Proxy-Authorization: Basic %s\r\n",
716 proxy_credentials);
717 ftp_printf(fin, "\r\n");
718 } else {
719 if (verbose)
720 fprintf(ttyout, "Requesting %s\n", origline);
721 #ifndef SMALL
722 if (resume || timestamp) {
723 if (stat(savefile, &stbuf) == 0) {
724 if (resume)
725 restart_point = stbuf.st_size;
726 if (timestamp)
727 mtime = stbuf.st_mtime;
728 } else {
729 restart_point = 0;
730 mtime = 0;
731 }
732 }
733 #endif /* SMALL */
734 ftp_printf(fin,
735 "GET /%s HTTP/1.1\r\n"
736 "Connection: close\r\n"
737 "Host: ", epath);
738 if (proxyhost) {
739 ftp_printf(fin, "%s", proxyhost);
740 port = NULL;
741 } else if (strchr(host, ':')) {
742 /*
743 * strip off scoped address portion, since it's
744 * local to node
745 */
746 h = strdup(host);
747 if (h == NULL)
748 errx(1, "Can't allocate memory.");
749 if ((p = strchr(h, '%')) != NULL)
750 *p = '\0';
751 ftp_printf(fin, "[%s]", h);
752 free(h);
753 } else
754 ftp_printf(fin, "%s", host);
755
756 /*
757 * Send port number only if it's specified and does not equal
758 * 80. Some broken HTTP servers get confused if you explicitly
759 * send them the port number.
760 */
761 #ifndef NOSSL
762 if (port && strcmp(port, (ishttpsurl ? "443" : "80")) != 0)
763 ftp_printf(fin, ":%s", port);
764 if (restart_point)
765 ftp_printf(fin, "\r\nRange: bytes=%lld-",
766 (long long)restart_point);
767 #else /* !NOSSL */
768 if (port && strcmp(port, "80") != 0)
769 ftp_printf(fin, ":%s", port);
770 #endif /* !NOSSL */
771
772 #ifndef SMALL
773 if (mtime && (http_time(mtime, tmbuf, sizeof(tmbuf)) != 0))
774 ftp_printf(fin, "\r\nIf-Modified-Since: %s", tmbuf);
775 #endif /* SMALL */
776
777 ftp_printf(fin, "\r\n%s%s\r\n", buf ? buf : "", httpuseragent);
778 ftp_printf(fin, "Accept: */*\r\n");
779 if (credentials)
780 ftp_printf(fin, "Authorization: Basic %s\r\n",
781 credentials);
782 ftp_printf(fin, "\r\n");
783 }
784 free(epath);
785
786 #ifndef NOSSL
787 free(buf);
788 #endif /* !NOSSL */
789 buf = NULL;
790 bufsize = 0;
791
792 if (fflush(fin) == EOF) {
793 warnx("Writing HTTP request: %s", sockerror(tls));
794 goto cleanup_url_get;
795 }
796 if ((len = getline(&buf, &bufsize, fin)) == -1) {
797 warnx("Receiving HTTP reply: %s", sockerror(tls));
798 goto cleanup_url_get;
799 }
800
801 while (len > 0 && (buf[len-1] == '\r' || buf[len-1] == '\n'))
802 buf[--len] = '\0';
803 #ifndef SMALL
804 if (debug)
805 fprintf(ttyout, "received '%s'\n", buf);
806 #endif /* !SMALL */
807
808 cp = strchr(buf, ' ');
809 if (cp == NULL)
810 goto improper;
811 else
812 cp++;
813
814 strlcpy(ststr, cp, sizeof(ststr));
815 status = strtonum(ststr, 200, 503, &errstr);
816 if (errstr) {
817 strnvis(gerror, cp, sizeof gerror, VIS_SAFE);
818 warnx("Error retrieving %s: %s", origline, gerror);
819 goto cleanup_url_get;
820 }
821
822 switch (status) {
823 case 200: /* OK */
824 #ifndef SMALL
825 /*
826 * When we request a partial file, and we receive an HTTP 200
827 * it is a good indication that the server doesn't support
828 * range requests, and is about to send us the entire file.
829 * If the restart_point == 0, then we are not actually
830 * requesting a partial file, and an HTTP 200 is appropriate.
831 */
832 if (resume && restart_point != 0) {
833 warnx("Server does not support resume.");
834 restart_point = resume = 0;
835 }
836 /* FALLTHROUGH */
837 case 206: /* Partial Content */
838 #endif /* !SMALL */
839 break;
840 case 301: /* Moved Permanently */
841 case 302: /* Found */
842 case 303: /* See Other */
843 case 307: /* Temporary Redirect */
844 case 308: /* Permanent Redirect (RFC 7538) */
845 isredirect++;
846 if (redirect_loop++ > 10) {
847 warnx("Too many redirections requested");
848 goto cleanup_url_get;
849 }
850 break;
851 #ifndef SMALL
852 case 304: /* Not Modified */
853 warnx("File is not modified on the server");
854 goto cleanup_url_get;
855 case 416: /* Requested Range Not Satisfiable */
856 warnx("File is already fully retrieved.");
857 goto cleanup_url_get;
858 #endif /* !SMALL */
859 case 503:
860 isunavail = 1;
861 break;
862 default:
863 strnvis(gerror, cp, sizeof gerror, VIS_SAFE);
864 warnx("Error retrieving %s: %s", origline, gerror);
865 goto cleanup_url_get;
866 }
867
868 /*
869 * Read the rest of the header.
870 */
871 filesize = -1;
872
873 for (;;) {
874 if ((len = getline(&buf, &bufsize, fin)) == -1) {
875 warnx("Receiving HTTP reply: %s", sockerror(tls));
876 goto cleanup_url_get;
877 }
878
879 while (len > 0 && (buf[len-1] == '\r' || buf[len-1] == '\n' ||
880 buf[len-1] == ' ' || buf[len-1] == '\t'))
881 buf[--len] = '\0';
882 if (len == 0)
883 break;
884 #ifndef SMALL
885 if (debug)
886 fprintf(ttyout, "received '%s'\n", buf);
887 #endif /* !SMALL */
888
889 /* Look for some headers */
890 cp = buf;
891 #define CONTENTLEN "Content-Length:"
892 if (strncasecmp(cp, CONTENTLEN, sizeof(CONTENTLEN) - 1) == 0) {
893 cp += sizeof(CONTENTLEN) - 1;
894 cp += strspn(cp, " \t");
895 filesize = strtonum(cp, 0, LLONG_MAX, &errstr);
896 if (errstr != NULL)
897 goto improper;
898 #ifndef SMALL
899 if (restart_point)
900 filesize += restart_point;
901 #endif /* !SMALL */
902 #define LOCATION "Location:"
903 } else if (isredirect &&
904 strncasecmp(cp, LOCATION, sizeof(LOCATION) - 1) == 0) {
905 cp += sizeof(LOCATION) - 1;
906 cp += strspn(cp, " \t");
907 /*
908 * If there is a colon before the first slash, this URI
909 * is not relative. RFC 3986 4.2
910 */
911 if (cp[strcspn(cp, ":/")] != ':') {
912 #ifdef SMALL
913 errx(1, "Relative redirect not supported");
914 #else /* SMALL */
915 /* XXX doesn't handle protocol-relative URIs */
916 if (*cp == '/') {
917 locbase = NULL;
918 cp++;
919 } else {
920 locbase = strdup(path);
921 if (locbase == NULL)
922 errx(1, "Can't allocate memory"
923 " for location base");
924 loctail = strchr(locbase, '#');
925 if (loctail != NULL)
926 *loctail = '\0';
927 loctail = strchr(locbase, '?');
928 if (loctail != NULL)
929 *loctail = '\0';
930 loctail = strrchr(locbase, '/');
931 if (loctail == NULL) {
932 free(locbase);
933 locbase = NULL;
934 } else
935 loctail[1] = '\0';
936 }
937 /* Construct URL from relative redirect */
938 if (asprintf(&redirurl, "%s%s%s%s/%s%s",
939 scheme, full_host,
940 portnum ? ":" : "",
941 portnum ? portnum : "",
942 locbase ? locbase : "",
943 cp) == -1)
944 errx(1, "Cannot build "
945 "redirect URL");
946 free(locbase);
947 #endif /* SMALL */
948 } else if ((redirurl = strdup(cp)) == NULL)
949 errx(1, "Cannot allocate memory for URL");
950 loctail = strchr(redirurl, '#');
951 if (loctail != NULL)
952 *loctail = '\0';
953 if (verbose) {
954 char *visbuf;
955 if (stravis(&visbuf, redirurl, VIS_SAFE) == -1)
956 err(1, "Cannot vis redirect URL");
957 fprintf(ttyout, "Redirected to %s\n", visbuf);
958 free(visbuf);
959 }
960 ftp_close(&fin, &tls, &fd);
961 rval = url_get(redirurl, proxyenv, savefile, lastfile);
962 free(redirurl);
963 goto cleanup_url_get;
964 #define RETRYAFTER "Retry-After:"
965 } else if (isunavail &&
966 strncasecmp(cp, RETRYAFTER, sizeof(RETRYAFTER) - 1) == 0) {
967 cp += sizeof(RETRYAFTER) - 1;
968 cp += strspn(cp, " \t");
969 retryafter = strtonum(cp, 0, 0, &errstr);
970 if (errstr != NULL)
971 retryafter = -1;
972 #define TRANSFER_ENCODING "Transfer-Encoding:"
973 } else if (strncasecmp(cp, TRANSFER_ENCODING,
974 sizeof(TRANSFER_ENCODING) - 1) == 0) {
975 cp += sizeof(TRANSFER_ENCODING) - 1;
976 cp += strspn(cp, " \t");
977 if (strcasecmp(cp, "chunked") == 0)
978 chunked = 1;
979 #ifndef SMALL
980 #define LAST_MODIFIED "Last-Modified:"
981 } else if (strncasecmp(cp, LAST_MODIFIED,
982 sizeof(LAST_MODIFIED) - 1) == 0) {
983 cp += sizeof(LAST_MODIFIED) - 1;
984 cp += strspn(cp, " \t");
985 if (strptime(cp, "%a, %d %h %Y %T %Z", &lmt) == NULL)
986 server_timestamps = 0;
987 #endif /* !SMALL */
988 }
989 }
990 free(buf);
991 buf = NULL;
992
993 /* Content-Length should be ignored for Transfer-Encoding: chunked */
994 if (chunked)
995 filesize = -1;
996
997 if (isunavail) {
998 if (retried || retryafter != 0)
999 warnx("Error retrieving %s: 503 Service Unavailable",
1000 origline);
1001 else {
1002 if (verbose)
1003 fprintf(ttyout, "Retrying %s\n", origline);
1004 retried = 1;
1005 ftp_close(&fin, &tls, &fd);
1006 rval = url_get(origline, proxyenv, savefile, lastfile);
1007 }
1008 goto cleanup_url_get;
1009 }
1010
1011 /* Open the output file. */
1012 if (!pipeout) {
1013 #ifndef SMALL
1014 if (resume)
1015 out = open(savefile, O_CREAT | O_WRONLY | O_APPEND,
1016 0666);
1017 else
1018 #endif /* !SMALL */
1019 out = open(savefile, O_CREAT | O_WRONLY | O_TRUNC,
1020 0666);
1021 if (out == -1) {
1022 warn("Can't open %s", savefile);
1023 goto cleanup_url_get;
1024 }
1025 } else {
1026 out = fileno(stdout);
1027 #ifdef SMALL
1028 if (lastfile) {
1029 if (pledge("stdio tty", NULL) == -1)
1030 err(1, "pledge");
1031 }
1032 #endif
1033 }
1034
1035 if ((buf = malloc(buflen)) == NULL)
1036 errx(1, "Can't allocate memory for transfer buffer");
1037
1038 /* Trap signals */
1039 oldintr = NULL;
1040 oldinti = NULL;
1041 if (setjmp(httpabort)) {
1042 if (oldintr)
1043 (void)signal(SIGINT, oldintr);
1044 if (oldinti)
1045 (void)signal(SIGINFO, oldinti);
1046 goto cleanup_url_get;
1047 }
1048 oldintr = signal(SIGINT, aborthttp);
1049
1050 bytes = 0;
1051 hashbytes = mark;
1052 progressmeter(-1, path);
1053
1054 /* Finally, suck down the file. */
1055 oldinti = signal(SIGINFO, psummary);
1056 if (chunked) {
1057 error = save_chunked(fin, tls, out, buf, buflen);
1058 signal(SIGINT, oldintr);
1059 signal(SIGINFO, oldinti);
1060 if (error == -1)
1061 goto cleanup_url_get;
1062 } else {
1063 while ((len = fread(buf, 1, buflen, fin)) > 0) {
1064 bytes += len;
1065 for (cp = buf; len > 0; len -= wlen, cp += wlen) {
1066 if ((wlen = write(out, cp, len)) == -1) {
1067 warn("Writing %s", savefile);
1068 signal(SIGINT, oldintr);
1069 signal(SIGINFO, oldinti);
1070 goto cleanup_url_get;
1071 }
1072 }
1073 if (hash && !progress) {
1074 while (bytes >= hashbytes) {
1075 (void)putc('#', ttyout);
1076 hashbytes += mark;
1077 }
1078 (void)fflush(ttyout);
1079 }
1080 }
1081 save_errno = errno;
1082 signal(SIGINT, oldintr);
1083 signal(SIGINFO, oldinti);
1084 if (hash && !progress && bytes > 0) {
1085 if (bytes < mark)
1086 (void)putc('#', ttyout);
1087 (void)putc('\n', ttyout);
1088 (void)fflush(ttyout);
1089 }
1090 if (len == 0 && ferror(fin)) {
1091 errno = save_errno;
1092 warnx("Reading from socket: %s", sockerror(tls));
1093 goto cleanup_url_get;
1094 }
1095 }
1096 progressmeter(1, NULL);
1097 if (
1098 #ifndef SMALL
1099 !resume &&
1100 #endif /* !SMALL */
1101 filesize != -1 && len == 0 && bytes != filesize) {
1102 if (verbose)
1103 fputs("Read short file.\n", ttyout);
1104 goto cleanup_url_get;
1105 }
1106
1107 if (verbose)
1108 ptransfer(0);
1109
1110 rval = 0;
1111 goto cleanup_url_get;
1112
1113 noftpautologin:
1114 warnx(
1115 "Auto-login using ftp URLs isn't supported when using $ftp_proxy");
1116 goto cleanup_url_get;
1117
1118 improper:
1119 warnx("Improper response from %s", host);
1120
1121 cleanup_url_get:
1122 #ifndef SMALL
1123 free(full_host);
1124 #endif /* !SMALL */
1125 #ifndef NOSSL
1126 free(sslhost);
1127 #endif /* !NOSSL */
1128 ftp_close(&fin, &tls, &fd);
1129 if (out >= 0 && out != fileno(stdout)) {
1130 #ifndef SMALL
1131 if (server_timestamps && lmt.tm_zone != 0 &&
1132 fstat(out, &stbuf) == 0 && S_ISREG(stbuf.st_mode) != 0) {
1133 ts[0].tv_nsec = UTIME_NOW;
1134 ts[1].tv_nsec = 0;
1135 setenv("TZ", lmt.tm_zone, 1);
1136 if (((ts[1].tv_sec = mktime(&lmt)) != -1) &&
1137 (futimens(out, ts) == -1))
1138 warnx("Unable to set file modification time");
1139 }
1140 #endif /* !SMALL */
1141 close(out);
1142 }
1143 free(buf);
1144 free(pathbuf);
1145 free(proxyhost);
1146 free(proxyurl);
1147 free(newline);
1148 free(credentials);
1149 free(proxy_credentials);
1150 return (rval);
1151 }
1152
1153 static int
save_chunked(FILE * fin,struct tls * tls,int out,char * buf,size_t buflen)1154 save_chunked(FILE *fin, struct tls *tls, int out, char *buf, size_t buflen)
1155 {
1156
1157 char *header = NULL, *end, *cp;
1158 unsigned long chunksize;
1159 size_t hsize = 0, rlen, wlen;
1160 ssize_t written;
1161 char cr, lf;
1162
1163 for (;;) {
1164 if (getline(&header, &hsize, fin) == -1)
1165 break;
1166 /* strip CRLF and any optional chunk extension */
1167 header[strcspn(header, "; \t\r\n")] = '\0';
1168 errno = 0;
1169 chunksize = strtoul(header, &end, 16);
1170 if (errno || header[0] == '\0' || *end != '\0' ||
1171 chunksize > INT_MAX) {
1172 warnx("Invalid chunk size '%s'", header);
1173 free(header);
1174 return -1;
1175 }
1176
1177 if (chunksize == 0) {
1178 /* We're done. Ignore optional trailer. */
1179 free(header);
1180 return 0;
1181 }
1182
1183 for (written = 0; chunksize != 0; chunksize -= rlen) {
1184 rlen = (chunksize < buflen) ? chunksize : buflen;
1185 rlen = fread(buf, 1, rlen, fin);
1186 if (rlen == 0)
1187 break;
1188 bytes += rlen;
1189 for (cp = buf, wlen = rlen; wlen > 0;
1190 wlen -= written, cp += written) {
1191 if ((written = write(out, cp, wlen)) == -1) {
1192 warn("Writing output file");
1193 free(header);
1194 return -1;
1195 }
1196 }
1197 }
1198
1199 if (rlen == 0 ||
1200 fread(&cr, 1, 1, fin) != 1 ||
1201 fread(&lf, 1, 1, fin) != 1)
1202 break;
1203
1204 if (cr != '\r' || lf != '\n') {
1205 warnx("Invalid chunked encoding");
1206 free(header);
1207 return -1;
1208 }
1209 }
1210 free(header);
1211
1212 if (ferror(fin))
1213 warnx("Error while reading from socket: %s", sockerror(tls));
1214 else
1215 warnx("Invalid chunked encoding: short read");
1216
1217 return -1;
1218 }
1219
1220 /*
1221 * Abort a http retrieval
1222 */
1223 static void
aborthttp(int signo)1224 aborthttp(int signo)
1225 {
1226 const char errmsg[] = "\nfetch aborted.\n";
1227
1228 write(fileno(ttyout), errmsg, sizeof(errmsg) - 1);
1229 longjmp(httpabort, 1);
1230 }
1231
1232 /*
1233 * Retrieve multiple files from the command line, transferring
1234 * files of the form "host:path", "ftp://host/path" using the
1235 * ftp protocol, and files of the form "http://host/path" using
1236 * the http protocol.
1237 * If path has a trailing "/", then return (-1);
1238 * the path will be cd-ed into and the connection remains open,
1239 * and the function will return -1 (to indicate the connection
1240 * is alive).
1241 * If an error occurs the return value will be the offset+1 in
1242 * argv[] of the file that caused a problem (i.e, argv[x]
1243 * returns x+1)
1244 * Otherwise, 0 is returned if all files retrieved successfully.
1245 */
1246 int
auto_fetch(int argc,char * argv[],char * outfile)1247 auto_fetch(int argc, char *argv[], char *outfile)
1248 {
1249 char *xargv[5];
1250 char *cp, *url, *host, *dir, *file, *portnum;
1251 char *username, *pass, *pathstart;
1252 char *ftpproxy, *httpproxy;
1253 int rval, xargc, lastfile;
1254 volatile int argpos;
1255 int dirhasglob, filehasglob, oautologin;
1256 char rempath[PATH_MAX];
1257
1258 argpos = 0;
1259
1260 if (setjmp(toplevel)) {
1261 if (connected)
1262 disconnect(0, NULL);
1263 return (argpos + 1);
1264 }
1265 (void)signal(SIGINT, (sig_t)intr);
1266 (void)signal(SIGPIPE, (sig_t)lostpeer);
1267
1268 if ((ftpproxy = getenv(FTP_PROXY)) != NULL && *ftpproxy == '\0')
1269 ftpproxy = NULL;
1270 if ((httpproxy = getenv(HTTP_PROXY)) != NULL && *httpproxy == '\0')
1271 httpproxy = NULL;
1272
1273 /*
1274 * Loop through as long as there's files to fetch.
1275 */
1276 username = pass = NULL;
1277 for (rval = 0; (rval == 0) && (argpos < argc); free(url), argpos++) {
1278 if (strchr(argv[argpos], ':') == NULL)
1279 break;
1280
1281 free(username);
1282 free(pass);
1283 host = dir = file = portnum = username = pass = NULL;
1284
1285 lastfile = (argv[argpos+1] == NULL);
1286
1287 /*
1288 * We muck with the string, so we make a copy.
1289 */
1290 url = strdup(argv[argpos]);
1291 if (url == NULL)
1292 errx(1, "Can't allocate memory for auto-fetch.");
1293
1294 if (strncasecmp(url, FILE_URL, sizeof(FILE_URL) - 1) == 0) {
1295 if (file_get(url + sizeof(FILE_URL) - 1, outfile) == -1)
1296 rval = argpos + 1;
1297 continue;
1298 }
1299
1300 /*
1301 * Try HTTP URL-style arguments next.
1302 */
1303 if (strncasecmp(url, HTTP_URL, sizeof(HTTP_URL) - 1) == 0 ||
1304 strncasecmp(url, HTTPS_URL, sizeof(HTTPS_URL) -1) == 0) {
1305 redirect_loop = 0;
1306 retried = 0;
1307 if (url_get(url, httpproxy, outfile, lastfile) == -1)
1308 rval = argpos + 1;
1309 continue;
1310 }
1311
1312 /*
1313 * Try FTP URL-style arguments next. If ftpproxy is
1314 * set, use url_get() instead of standard ftp.
1315 * Finally, try host:file.
1316 */
1317 host = url;
1318 if (strncasecmp(url, FTP_URL, sizeof(FTP_URL) - 1) == 0) {
1319 char *passend, *passagain, *userend;
1320
1321 if (ftpproxy) {
1322 if (url_get(url, ftpproxy, outfile, lastfile) == -1)
1323 rval = argpos + 1;
1324 continue;
1325 }
1326 host += sizeof(FTP_URL) - 1;
1327 dir = strchr(host, '/');
1328
1329 /* Look for [user:pass@]host[:port] */
1330
1331 /* check if we have "user:pass@" */
1332 userend = strchr(host, ':');
1333 passend = strchr(host, '@');
1334 if (passend && userend && userend < passend &&
1335 (!dir || passend < dir)) {
1336 username = host;
1337 pass = userend + 1;
1338 host = passend + 1;
1339 *userend = *passend = '\0';
1340 passagain = strchr(host, '@');
1341 if (strchr(pass, '@') != NULL ||
1342 (passagain != NULL && passagain < dir)) {
1343 warnx(at_encoding_warning);
1344 username = pass = NULL;
1345 goto bad_ftp_url;
1346 }
1347
1348 if (EMPTYSTRING(username)) {
1349 bad_ftp_url:
1350 warnx("Invalid URL: %s", argv[argpos]);
1351 rval = argpos + 1;
1352 username = pass = NULL;
1353 continue;
1354 }
1355 username = urldecode(username);
1356 pass = urldecode(pass);
1357 }
1358
1359 /* check [host]:port, or [host] */
1360 if (host[0] == '[') {
1361 cp = strchr(host, ']');
1362 if (cp && (!dir || cp < dir)) {
1363 if (cp + 1 == dir || cp[1] == ':') {
1364 host++;
1365 *cp++ = '\0';
1366 } else
1367 cp = NULL;
1368 } else
1369 cp = host;
1370 } else
1371 cp = host;
1372
1373 /* split off host[:port] if there is */
1374 if (cp) {
1375 portnum = strchr(cp, ':');
1376 pathstart = strchr(cp, '/');
1377 /* : in path is not a port # indicator */
1378 if (portnum && pathstart &&
1379 pathstart < portnum)
1380 portnum = NULL;
1381
1382 if (!portnum)
1383 ;
1384 else {
1385 if (!dir)
1386 ;
1387 else if (portnum + 1 < dir) {
1388 *portnum++ = '\0';
1389 /*
1390 * XXX should check if portnum
1391 * is decimal number
1392 */
1393 } else {
1394 /* empty portnum */
1395 goto bad_ftp_url;
1396 }
1397 }
1398 } else
1399 portnum = NULL;
1400 } else { /* classic style `host:file' */
1401 dir = strchr(host, ':');
1402 }
1403 if (EMPTYSTRING(host)) {
1404 rval = argpos + 1;
1405 continue;
1406 }
1407
1408 /*
1409 * If dir is NULL, the file wasn't specified
1410 * (URL looked something like ftp://host)
1411 */
1412 if (dir != NULL)
1413 *dir++ = '\0';
1414
1415 /*
1416 * Extract the file and (if present) directory name.
1417 */
1418 if (!EMPTYSTRING(dir)) {
1419 cp = strrchr(dir, '/');
1420 if (cp != NULL) {
1421 *cp++ = '\0';
1422 file = cp;
1423 } else {
1424 file = dir;
1425 dir = NULL;
1426 }
1427 }
1428 #ifndef SMALL
1429 if (debug)
1430 fprintf(ttyout,
1431 "user %s:%s host %s port %s dir %s file %s\n",
1432 username, pass ? "XXXX" : NULL, host, portnum,
1433 dir, file);
1434 #endif /* !SMALL */
1435
1436 /*
1437 * Set up the connection.
1438 */
1439 if (connected)
1440 disconnect(0, NULL);
1441 xargv[0] = __progname;
1442 xargv[1] = host;
1443 xargv[2] = NULL;
1444 xargc = 2;
1445 if (!EMPTYSTRING(portnum)) {
1446 xargv[2] = portnum;
1447 xargv[3] = NULL;
1448 xargc = 3;
1449 }
1450 oautologin = autologin;
1451 if (username == NULL)
1452 anonftp = 1;
1453 else {
1454 anonftp = 0;
1455 autologin = 0;
1456 }
1457 setpeer(xargc, xargv);
1458 autologin = oautologin;
1459 if (connected == 0 ||
1460 (connected == 1 && autologin && (username == NULL ||
1461 !ftp_login(host, username, pass)))) {
1462 warnx("Can't connect or login to host `%s'", host);
1463 rval = argpos + 1;
1464 continue;
1465 }
1466
1467 /* Always use binary transfers. */
1468 setbinary(0, NULL);
1469
1470 dirhasglob = filehasglob = 0;
1471 if (doglob) {
1472 if (!EMPTYSTRING(dir) &&
1473 strpbrk(dir, "*?[]{}") != NULL)
1474 dirhasglob = 1;
1475 if (!EMPTYSTRING(file) &&
1476 strpbrk(file, "*?[]{}") != NULL)
1477 filehasglob = 1;
1478 }
1479
1480 /* Change directories, if necessary. */
1481 if (!EMPTYSTRING(dir) && !dirhasglob) {
1482 xargv[0] = "cd";
1483 xargv[1] = dir;
1484 xargv[2] = NULL;
1485 cd(2, xargv);
1486 if (!dirchange) {
1487 rval = argpos + 1;
1488 continue;
1489 }
1490 }
1491
1492 if (EMPTYSTRING(file)) {
1493 #ifndef SMALL
1494 rval = -1;
1495 #else /* !SMALL */
1496 recvrequest("NLST", "-", NULL, "w", 0, 0);
1497 rval = 0;
1498 #endif /* !SMALL */
1499 continue;
1500 }
1501
1502 if (verbose)
1503 fprintf(ttyout, "Retrieving %s/%s\n", dir ? dir : "", file);
1504
1505 if (dirhasglob) {
1506 snprintf(rempath, sizeof(rempath), "%s/%s", dir, file);
1507 file = rempath;
1508 }
1509
1510 /* Fetch the file(s). */
1511 xargc = 2;
1512 xargv[0] = "get";
1513 xargv[1] = file;
1514 xargv[2] = NULL;
1515 if (dirhasglob || filehasglob) {
1516 int ointeractive;
1517
1518 ointeractive = interactive;
1519 interactive = 0;
1520 xargv[0] = "mget";
1521 #ifndef SMALL
1522 if (resume) {
1523 xargc = 3;
1524 xargv[1] = "-c";
1525 xargv[2] = file;
1526 xargv[3] = NULL;
1527 }
1528 #endif /* !SMALL */
1529 mget(xargc, xargv);
1530 interactive = ointeractive;
1531 } else {
1532 if (outfile != NULL) {
1533 xargv[2] = outfile;
1534 xargv[3] = NULL;
1535 xargc++;
1536 }
1537 #ifndef SMALL
1538 if (resume)
1539 reget(xargc, xargv);
1540 else
1541 #endif /* !SMALL */
1542 get(xargc, xargv);
1543 }
1544
1545 if ((code / 100) != COMPLETE)
1546 rval = argpos + 1;
1547 }
1548 if (connected && rval != -1)
1549 disconnect(0, NULL);
1550 return (rval);
1551 }
1552
1553 char *
urldecode(const char * str)1554 urldecode(const char *str)
1555 {
1556 char *ret, c;
1557 int i, reallen;
1558
1559 if (str == NULL)
1560 return NULL;
1561 if ((ret = malloc(strlen(str) + 1)) == NULL)
1562 err(1, "Can't allocate memory for URL decoding");
1563 for (i = 0, reallen = 0; str[i] != '\0'; i++, reallen++, ret++) {
1564 c = str[i];
1565 if (c == '+') {
1566 *ret = ' ';
1567 continue;
1568 }
1569
1570 /* Cannot use strtol here because next char
1571 * after %xx may be a digit.
1572 */
1573 if (c == '%' && isxdigit((unsigned char)str[i + 1]) &&
1574 isxdigit((unsigned char)str[i + 2])) {
1575 *ret = hextochar(&str[i + 1]);
1576 i += 2;
1577 continue;
1578 }
1579 *ret = c;
1580 }
1581 *ret = '\0';
1582
1583 return ret - reallen;
1584 }
1585
1586 static char *
recode_credentials(const char * userinfo)1587 recode_credentials(const char *userinfo)
1588 {
1589 char *ui, *creds;
1590 size_t ulen, credsize;
1591
1592 /* url-decode the user and pass */
1593 ui = urldecode(userinfo);
1594
1595 ulen = strlen(ui);
1596 credsize = (ulen + 2) / 3 * 4 + 1;
1597 creds = malloc(credsize);
1598 if (creds == NULL)
1599 errx(1, "out of memory");
1600 if (b64_ntop(ui, ulen, creds, credsize) == -1)
1601 errx(1, "error in base64 encoding");
1602 free(ui);
1603 return (creds);
1604 }
1605
1606 static char
hextochar(const char * str)1607 hextochar(const char *str)
1608 {
1609 unsigned char c, ret;
1610
1611 c = str[0];
1612 ret = c;
1613 if (isalpha(c))
1614 ret -= isupper(c) ? 'A' - 10 : 'a' - 10;
1615 else
1616 ret -= '0';
1617 ret *= 16;
1618
1619 c = str[1];
1620 ret += c;
1621 if (isalpha(c))
1622 ret -= isupper(c) ? 'A' - 10 : 'a' - 10;
1623 else
1624 ret -= '0';
1625 return ret;
1626 }
1627
1628 int
isurl(const char * p)1629 isurl(const char *p)
1630 {
1631
1632 if (strncasecmp(p, FTP_URL, sizeof(FTP_URL) - 1) == 0 ||
1633 strncasecmp(p, HTTP_URL, sizeof(HTTP_URL) - 1) == 0 ||
1634 #ifndef NOSSL
1635 strncasecmp(p, HTTPS_URL, sizeof(HTTPS_URL) - 1) == 0 ||
1636 #endif /* !NOSSL */
1637 strncasecmp(p, FILE_URL, sizeof(FILE_URL) - 1) == 0 ||
1638 strstr(p, ":/"))
1639 return (1);
1640 return (0);
1641 }
1642
1643 #ifndef SMALL
1644 static int
ftp_printf(FILE * fp,const char * fmt,...)1645 ftp_printf(FILE *fp, const char *fmt, ...)
1646 {
1647 va_list ap;
1648 int ret;
1649
1650 va_start(ap, fmt);
1651 ret = vfprintf(fp, fmt, ap);
1652 va_end(ap);
1653
1654 if (debug) {
1655 va_start(ap, fmt);
1656 vfprintf(ttyout, fmt, ap);
1657 va_end(ap);
1658 }
1659
1660 return ret;
1661 }
1662 #endif /* !SMALL */
1663
1664 static void
ftp_close(FILE ** fin,struct tls ** tls,int * fd)1665 ftp_close(FILE **fin, struct tls **tls, int *fd)
1666 {
1667 #ifndef NOSSL
1668 int ret;
1669
1670 if (*tls != NULL) {
1671 if (tls_session_fd != -1)
1672 dprintf(STDERR_FILENO, "tls session resumed: %s\n",
1673 tls_conn_session_resumed(*tls) ? "yes" : "no");
1674 do {
1675 ret = tls_close(*tls);
1676 } while (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT);
1677 tls_free(*tls);
1678 *tls = NULL;
1679 }
1680 if (*fd != -1) {
1681 close(*fd);
1682 *fd = -1;
1683 }
1684 #endif
1685 if (*fin != NULL) {
1686 fclose(*fin);
1687 *fin = NULL;
1688 }
1689 }
1690
1691 static const char *
sockerror(struct tls * tls)1692 sockerror(struct tls *tls)
1693 {
1694 int save_errno = errno;
1695 #ifndef NOSSL
1696 if (tls != NULL) {
1697 const char *tlserr = tls_error(tls);
1698 if (tlserr != NULL)
1699 return tlserr;
1700 }
1701 #endif
1702 return strerror(save_errno);
1703 }
1704
1705 #ifndef NOSSL
1706 static int
proxy_connect(int socket,char * host,char * cookie)1707 proxy_connect(int socket, char *host, char *cookie)
1708 {
1709 int l;
1710 char buf[1024];
1711 char *connstr, *hosttail, *port;
1712
1713 if (*host == '[' && (hosttail = strrchr(host, ']')) != NULL &&
1714 (hosttail[1] == '\0' || hosttail[1] == ':')) {
1715 host++;
1716 *hosttail++ = '\0';
1717 } else
1718 hosttail = host;
1719
1720 port = strrchr(hosttail, ':'); /* find portnum */
1721 if (port != NULL)
1722 *port++ = '\0';
1723 if (!port)
1724 port = "443";
1725
1726 if (cookie) {
1727 l = asprintf(&connstr, "CONNECT %s:%s HTTP/1.1\r\n"
1728 "Host: %s:%s\r\n"
1729 "Proxy-Authorization: Basic %s\r\n%s\r\n\r\n",
1730 host, port, host, port, cookie, HTTP_USER_AGENT);
1731 } else {
1732 l = asprintf(&connstr, "CONNECT %s:%s HTTP/1.1\r\n"
1733 "Host: %s:%s\r\n%s\r\n\r\n",
1734 host, port, host, port, HTTP_USER_AGENT);
1735 }
1736
1737 if (l == -1)
1738 errx(1, "Could not allocate memory to assemble connect string!");
1739 #ifndef SMALL
1740 if (debug)
1741 printf("%s", connstr);
1742 #endif /* !SMALL */
1743 if (write(socket, connstr, l) != l)
1744 err(1, "Could not send connect string");
1745 read(socket, &buf, sizeof(buf)); /* only proxy header XXX: error handling? */
1746 free(connstr);
1747 return(200);
1748 }
1749
1750 static int
stdio_tls_write_wrapper(void * arg,const char * buf,int len)1751 stdio_tls_write_wrapper(void *arg, const char *buf, int len)
1752 {
1753 struct tls *tls = arg;
1754 ssize_t ret;
1755
1756 do {
1757 ret = tls_write(tls, buf, len);
1758 } while (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT);
1759
1760 return ret;
1761 }
1762
1763 static int
stdio_tls_read_wrapper(void * arg,char * buf,int len)1764 stdio_tls_read_wrapper(void *arg, char *buf, int len)
1765 {
1766 struct tls *tls = arg;
1767 ssize_t ret;
1768
1769 do {
1770 ret = tls_read(tls, buf, len);
1771 } while (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT);
1772
1773 return ret;
1774 }
1775 #endif /* !NOSSL */
1776