1 /* $NetBSD: fetch.c,v 1.21 2015/10/04 04:53:26 lukem Exp $ */ 2 /* from NetBSD: fetch.c,v 1.207 2015/09/12 19:38:42 wiz Exp */ 3 4 /*- 5 * Copyright (c) 1997-2015 The NetBSD Foundation, Inc. 6 * All rights reserved. 7 * 8 * This code is derived from software contributed to The NetBSD Foundation 9 * by Luke Mewburn. 10 * 11 * This code is derived from software contributed to The NetBSD Foundation 12 * by Scott Aaron Bamford. 13 * 14 * This code is derived from software contributed to The NetBSD Foundation 15 * by Thomas Klausner. 16 * 17 * Redistribution and use in source and binary forms, with or without 18 * modification, are permitted provided that the following conditions 19 * are met: 20 * 1. Redistributions of source code must retain the above copyright 21 * notice, this list of conditions and the following disclaimer. 22 * 2. Redistributions in binary form must reproduce the above copyright 23 * notice, this list of conditions and the following disclaimer in the 24 * documentation and/or other materials provided with the distribution. 25 * 26 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 27 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 28 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 29 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 30 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 31 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 32 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 33 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 34 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 35 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 36 * POSSIBILITY OF SUCH DAMAGE. 37 */ 38 39 #include "tnftp.h" 40 41 #if 0 /* tnftp */ 42 43 #include <sys/cdefs.h> 44 #ifndef lint 45 __RCSID(" NetBSD: fetch.c,v 1.207 2015/09/12 19:38:42 wiz Exp "); 46 #endif /* not lint */ 47 48 /* 49 * FTP User Program -- Command line file retrieval 50 */ 51 52 #include <sys/types.h> 53 #include <sys/param.h> 54 #include <sys/socket.h> 55 #include <sys/stat.h> 56 #include <sys/time.h> 57 58 #include <netinet/in.h> 59 60 #include <arpa/ftp.h> 61 #include <arpa/inet.h> 62 63 #include <assert.h> 64 #include <ctype.h> 65 #include <err.h> 66 #include <errno.h> 67 #include <netdb.h> 68 #include <fcntl.h> 69 #include <stdio.h> 70 #include <stdlib.h> 71 #include <string.h> 72 #include <unistd.h> 73 #include <time.h> 74 75 #endif /* tnftp */ 76 77 #include "ssl.h" 78 #include "ftp_var.h" 79 #include "version.h" 80 81 typedef enum { 82 UNKNOWN_URL_T=-1, 83 HTTP_URL_T, 84 #ifdef WITH_SSL 85 HTTPS_URL_T, 86 #endif 87 FTP_URL_T, 88 FILE_URL_T, 89 CLASSIC_URL_T 90 } url_t; 91 92 __dead static void aborthttp(int); 93 __dead static void timeouthttp(int); 94 #ifndef NO_AUTH 95 static int auth_url(const char *, char **, const char *, const char *); 96 static void base64_encode(const unsigned char *, size_t, unsigned char *); 97 #endif 98 static int go_fetch(const char *); 99 static int fetch_ftp(const char *); 100 static int fetch_url(const char *, const char *, char *, char *); 101 static const char *match_token(const char **, const char *); 102 static int parse_url(const char *, const char *, url_t *, char **, 103 char **, char **, char **, in_port_t *, char **); 104 static void url_decode(char *); 105 106 static int redirect_loop; 107 108 109 #define STRNEQUAL(a,b) (strncasecmp((a), (b), sizeof((b))-1) == 0) 110 #define ISLWS(x) ((x)=='\r' || (x)=='\n' || (x)==' ' || (x)=='\t') 111 #define SKIPLWS(x) do { while (ISLWS((*x))) x++; } while (0) 112 113 114 #define ABOUT_URL "about:" /* propaganda */ 115 #define FILE_URL "file://" /* file URL prefix */ 116 #define FTP_URL "ftp://" /* ftp URL prefix */ 117 #define HTTP_URL "http://" /* http URL prefix */ 118 #ifdef WITH_SSL 119 #define HTTPS_URL "https://" /* https URL prefix */ 120 121 #define IS_HTTP_TYPE(urltype) \ 122 (((urltype) == HTTP_URL_T) || ((urltype) == HTTPS_URL_T)) 123 #else 124 #define IS_HTTP_TYPE(urltype) \ 125 ((urltype) == HTTP_URL_T) 126 #endif 127 128 /* 129 * Determine if token is the next word in buf (case insensitive). 130 * If so, advance buf past the token and any trailing LWS, and 131 * return a pointer to the token (in buf). Otherwise, return NULL. 132 * token may be preceded by LWS. 133 * token must be followed by LWS or NUL. (I.e, don't partial match). 134 */ 135 static const char * 136 match_token(const char **buf, const char *token) 137 { 138 const char *p, *orig; 139 size_t tlen; 140 141 tlen = strlen(token); 142 p = *buf; 143 SKIPLWS(p); 144 orig = p; 145 if (strncasecmp(p, token, tlen) != 0) 146 return NULL; 147 p += tlen; 148 if (*p != '\0' && !ISLWS(*p)) 149 return NULL; 150 SKIPLWS(p); 151 orig = *buf; 152 *buf = p; 153 return orig; 154 } 155 156 #ifndef NO_AUTH 157 /* 158 * Generate authorization response based on given authentication challenge. 159 * Returns -1 if an error occurred, otherwise 0. 160 * Sets response to a malloc(3)ed string; caller should free. 161 */ 162 static int 163 auth_url(const char *challenge, char **response, const char *guser, 164 const char *gpass) 165 { 166 const char *cp, *scheme, *errormsg; 167 char *ep, *clear, *realm; 168 char uuser[BUFSIZ], *gotpass; 169 const char *upass; 170 int rval; 171 size_t len, clen, rlen; 172 173 *response = NULL; 174 clear = realm = NULL; 175 rval = -1; 176 cp = challenge; 177 scheme = "Basic"; /* only support Basic authentication */ 178 gotpass = NULL; 179 180 DPRINTF("auth_url: challenge `%s'\n", challenge); 181 182 if (! match_token(&cp, scheme)) { 183 warnx("Unsupported authentication challenge `%s'", 184 challenge); 185 goto cleanup_auth_url; 186 } 187 188 #define REALM "realm=\"" 189 if (STRNEQUAL(cp, REALM)) 190 cp += sizeof(REALM) - 1; 191 else { 192 warnx("Unsupported authentication challenge `%s'", 193 challenge); 194 goto cleanup_auth_url; 195 } 196 /* XXX: need to improve quoted-string parsing to support \ quoting, etc. */ 197 if ((ep = strchr(cp, '\"')) != NULL) { 198 len = ep - cp; 199 realm = (char *)ftp_malloc(len + 1); 200 (void)strlcpy(realm, cp, len + 1); 201 } else { 202 warnx("Unsupported authentication challenge `%s'", 203 challenge); 204 goto cleanup_auth_url; 205 } 206 207 fprintf(ttyout, "Username for `%s': ", realm); 208 if (guser != NULL) { 209 (void)strlcpy(uuser, guser, sizeof(uuser)); 210 fprintf(ttyout, "%s\n", uuser); 211 } else { 212 (void)fflush(ttyout); 213 if (get_line(stdin, uuser, sizeof(uuser), &errormsg) < 0) { 214 warnx("%s; can't authenticate", errormsg); 215 goto cleanup_auth_url; 216 } 217 } 218 if (gpass != NULL) 219 upass = gpass; 220 else { 221 gotpass = getpass("Password: "); 222 if (gotpass == NULL) { 223 warnx("Can't read password"); 224 goto cleanup_auth_url; 225 } 226 upass = gotpass; 227 } 228 229 clen = strlen(uuser) + strlen(upass) + 2; /* user + ":" + pass + "\0" */ 230 clear = (char *)ftp_malloc(clen); 231 (void)strlcpy(clear, uuser, clen); 232 (void)strlcat(clear, ":", clen); 233 (void)strlcat(clear, upass, clen); 234 if (gotpass) 235 memset(gotpass, 0, strlen(gotpass)); 236 237 /* scheme + " " + enc + "\0" */ 238 rlen = strlen(scheme) + 1 + (clen + 2) * 4 / 3 + 1; 239 *response = (char *)ftp_malloc(rlen); 240 (void)strlcpy(*response, scheme, rlen); 241 len = strlcat(*response, " ", rlen); 242 /* use `clen - 1' to not encode the trailing NUL */ 243 base64_encode((unsigned char *)clear, clen - 1, 244 (unsigned char *)*response + len); 245 memset(clear, 0, clen); 246 rval = 0; 247 248 cleanup_auth_url: 249 FREEPTR(clear); 250 FREEPTR(realm); 251 return (rval); 252 } 253 254 /* 255 * Encode len bytes starting at clear using base64 encoding into encoded, 256 * which should be at least ((len + 2) * 4 / 3 + 1) in size. 257 */ 258 static void 259 base64_encode(const unsigned char *clear, size_t len, unsigned char *encoded) 260 { 261 static const unsigned char enc[] = 262 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 263 unsigned char *cp; 264 size_t i; 265 266 cp = encoded; 267 for (i = 0; i < len; i += 3) { 268 *(cp++) = enc[((clear[i + 0] >> 2))]; 269 *(cp++) = enc[((clear[i + 0] << 4) & 0x30) 270 | ((clear[i + 1] >> 4) & 0x0f)]; 271 *(cp++) = enc[((clear[i + 1] << 2) & 0x3c) 272 | ((clear[i + 2] >> 6) & 0x03)]; 273 *(cp++) = enc[((clear[i + 2] ) & 0x3f)]; 274 } 275 *cp = '\0'; 276 while (i-- > len) 277 *(--cp) = '='; 278 } 279 #endif 280 281 /* 282 * Decode %xx escapes in given string, `in-place'. 283 */ 284 static void 285 url_decode(char *url) 286 { 287 unsigned char *p, *q; 288 289 if (EMPTYSTRING(url)) 290 return; 291 p = q = (unsigned char *)url; 292 293 #define HEXTOINT(x) (x - (isdigit(x) ? '0' : (islower(x) ? 'a' : 'A') - 10)) 294 while (*p) { 295 if (p[0] == '%' 296 && p[1] && isxdigit((unsigned char)p[1]) 297 && p[2] && isxdigit((unsigned char)p[2])) { 298 *q++ = HEXTOINT(p[1]) * 16 + HEXTOINT(p[2]); 299 p+=3; 300 } else 301 *q++ = *p++; 302 } 303 *q = '\0'; 304 } 305 306 307 /* 308 * Parse URL of form (per RFC 3986): 309 * <type>://[<user>[:<password>]@]<host>[:<port>][/<path>] 310 * Returns -1 if a parse error occurred, otherwise 0. 311 * It's the caller's responsibility to url_decode() the returned 312 * user, pass and path. 313 * 314 * Sets type to url_t, each of the given char ** pointers to a 315 * malloc(3)ed strings of the relevant section, and port to 316 * the number given, or ftpport if ftp://, or httpport if http://. 317 * 318 * XXX: this is not totally RFC 3986 compliant; <path> will have the 319 * leading `/' unless it's an ftp:// URL, as this makes things easier 320 * for file:// and http:// URLs. ftp:// URLs have the `/' between the 321 * host and the URL-path removed, but any additional leading slashes 322 * in the URL-path are retained (because they imply that we should 323 * later do "CWD" with a null argument). 324 * 325 * Examples: 326 * input URL output path 327 * --------- ----------- 328 * "http://host" "/" 329 * "http://host/" "/" 330 * "http://host/path" "/path" 331 * "file://host/dir/file" "dir/file" 332 * "ftp://host" "" 333 * "ftp://host/" "" 334 * "ftp://host//" "/" 335 * "ftp://host/dir/file" "dir/file" 336 * "ftp://host//dir/file" "/dir/file" 337 */ 338 static int 339 parse_url(const char *url, const char *desc, url_t *utype, 340 char **uuser, char **pass, char **host, char **port, 341 in_port_t *portnum, char **path) 342 { 343 const char *origurl, *tport; 344 char *cp, *ep, *thost; 345 size_t len; 346 347 if (url == NULL || desc == NULL || utype == NULL || uuser == NULL 348 || pass == NULL || host == NULL || port == NULL || portnum == NULL 349 || path == NULL) 350 errx(1, "parse_url: invoked with NULL argument!"); 351 DPRINTF("parse_url: %s `%s'\n", desc, url); 352 353 origurl = url; 354 *utype = UNKNOWN_URL_T; 355 *uuser = *pass = *host = *port = *path = NULL; 356 *portnum = 0; 357 tport = NULL; 358 359 if (STRNEQUAL(url, HTTP_URL)) { 360 url += sizeof(HTTP_URL) - 1; 361 *utype = HTTP_URL_T; 362 *portnum = HTTP_PORT; 363 tport = httpport; 364 } else if (STRNEQUAL(url, FTP_URL)) { 365 url += sizeof(FTP_URL) - 1; 366 *utype = FTP_URL_T; 367 *portnum = FTP_PORT; 368 tport = ftpport; 369 } else if (STRNEQUAL(url, FILE_URL)) { 370 url += sizeof(FILE_URL) - 1; 371 *utype = FILE_URL_T; 372 #ifdef WITH_SSL 373 } else if (STRNEQUAL(url, HTTPS_URL)) { 374 url += sizeof(HTTPS_URL) - 1; 375 *utype = HTTPS_URL_T; 376 *portnum = HTTPS_PORT; 377 tport = httpsport; 378 #endif 379 } else { 380 warnx("Invalid %s `%s'", desc, url); 381 cleanup_parse_url: 382 FREEPTR(*uuser); 383 if (*pass != NULL) 384 memset(*pass, 0, strlen(*pass)); 385 FREEPTR(*pass); 386 FREEPTR(*host); 387 FREEPTR(*port); 388 FREEPTR(*path); 389 return (-1); 390 } 391 392 if (*url == '\0') 393 return (0); 394 395 /* find [user[:pass]@]host[:port] */ 396 ep = strchr(url, '/'); 397 if (ep == NULL) 398 thost = ftp_strdup(url); 399 else { 400 len = ep - url; 401 thost = (char *)ftp_malloc(len + 1); 402 (void)strlcpy(thost, url, len + 1); 403 if (*utype == FTP_URL_T) /* skip first / for ftp URLs */ 404 ep++; 405 *path = ftp_strdup(ep); 406 } 407 408 cp = strchr(thost, '@'); /* look for user[:pass]@ in URLs */ 409 if (cp != NULL) { 410 if (*utype == FTP_URL_T) 411 anonftp = 0; /* disable anonftp */ 412 *uuser = thost; 413 *cp = '\0'; 414 thost = ftp_strdup(cp + 1); 415 cp = strchr(*uuser, ':'); 416 if (cp != NULL) { 417 *cp = '\0'; 418 *pass = ftp_strdup(cp + 1); 419 } 420 url_decode(*uuser); 421 if (*pass) 422 url_decode(*pass); 423 } 424 425 #ifdef INET6 426 /* 427 * Check if thost is an encoded IPv6 address, as per 428 * RFC 3986: 429 * `[' ipv6-address ']' 430 */ 431 if (*thost == '[') { 432 cp = thost + 1; 433 if ((ep = strchr(cp, ']')) == NULL || 434 (ep[1] != '\0' && ep[1] != ':')) { 435 warnx("Invalid address `%s' in %s `%s'", 436 thost, desc, origurl); 437 goto cleanup_parse_url; 438 } 439 len = ep - cp; /* change `[xyz]' -> `xyz' */ 440 memmove(thost, thost + 1, len); 441 thost[len] = '\0'; 442 if (! isipv6addr(thost)) { 443 warnx("Invalid IPv6 address `%s' in %s `%s'", 444 thost, desc, origurl); 445 goto cleanup_parse_url; 446 } 447 cp = ep + 1; 448 if (*cp == ':') 449 cp++; 450 else 451 cp = NULL; 452 } else 453 #endif /* INET6 */ 454 if ((cp = strchr(thost, ':')) != NULL) 455 *cp++ = '\0'; 456 *host = thost; 457 458 /* look for [:port] */ 459 if (cp != NULL) { 460 unsigned long nport; 461 462 nport = strtoul(cp, &ep, 10); 463 if (*cp == '\0' || *ep != '\0' || 464 nport < 1 || nport > MAX_IN_PORT_T) { 465 warnx("Unknown port `%s' in %s `%s'", 466 cp, desc, origurl); 467 goto cleanup_parse_url; 468 } 469 *portnum = nport; 470 tport = cp; 471 } 472 473 if (tport != NULL) 474 *port = ftp_strdup(tport); 475 if (*path == NULL) { 476 const char *emptypath = "/"; 477 if (*utype == FTP_URL_T) /* skip first / for ftp URLs */ 478 emptypath++; 479 *path = ftp_strdup(emptypath); 480 } 481 482 DPRINTF("parse_url: user `%s' pass `%s' host %s port %s(%d) " 483 "path `%s'\n", 484 STRorNULL(*uuser), STRorNULL(*pass), 485 STRorNULL(*host), STRorNULL(*port), 486 *portnum ? *portnum : -1, STRorNULL(*path)); 487 488 return (0); 489 } 490 491 sigjmp_buf httpabort; 492 493 /* 494 * Retrieve URL, via a proxy if necessary, using HTTP. 495 * If proxyenv is set, use that for the proxy, otherwise try ftp_proxy or 496 * http_proxy/https_proxy as appropriate. 497 * Supports HTTP redirects. 498 * Returns 1 on failure, 0 on completed xfer, -1 if ftp connection 499 * is still open (e.g, ftp xfer with trailing /) 500 */ 501 static int 502 fetch_url(const char *url, const char *proxyenv, char *proxyauth, char *wwwauth) 503 { 504 struct addrinfo hints, *res, *res0 = NULL; 505 int error; 506 sigfunc volatile oldint; 507 sigfunc volatile oldpipe; 508 sigfunc volatile oldalrm; 509 sigfunc volatile oldquit; 510 int volatile s; 511 struct stat sb; 512 int volatile ischunked; 513 int volatile isproxy; 514 int volatile rval; 515 int volatile hcode; 516 int len; 517 size_t flen; 518 static size_t bufsize; 519 static char *xferbuf; 520 const char *cp, *token; 521 char *ep; 522 char buf[FTPBUFLEN]; 523 const char *errormsg; 524 char *volatile savefile; 525 char *volatile auth; 526 char *volatile location; 527 char *volatile message; 528 char *uuser, *pass, *host, *port, *path; 529 char *volatile decodedpath; 530 char *puser, *ppass, *useragent; 531 off_t hashbytes, rangestart, rangeend, entitylen; 532 int (*volatile closefunc)(FILE *); 533 FETCH *volatile fin; 534 FILE *volatile fout; 535 const char *volatile penv = proxyenv; 536 time_t mtime; 537 url_t urltype; 538 in_port_t portnum; 539 #ifdef WITH_SSL 540 void *ssl; 541 #endif 542 543 DPRINTF("%s: `%s' proxyenv `%s'\n", __func__, url, STRorNULL(penv)); 544 545 oldquit = oldalrm = oldint = oldpipe = NULL; 546 closefunc = NULL; 547 fin = NULL; 548 fout = NULL; 549 s = -1; 550 savefile = NULL; 551 auth = location = message = NULL; 552 ischunked = isproxy = hcode = 0; 553 rval = 1; 554 uuser = pass = host = path = decodedpath = puser = ppass = NULL; 555 556 if (sigsetjmp(httpabort, 1)) 557 goto cleanup_fetch_url; 558 559 if (parse_url(url, "URL", &urltype, &uuser, &pass, &host, &port, 560 &portnum, &path) == -1) 561 goto cleanup_fetch_url; 562 563 if (urltype == FILE_URL_T && ! EMPTYSTRING(host) 564 && strcasecmp(host, "localhost") != 0) { 565 warnx("No support for non local file URL `%s'", url); 566 goto cleanup_fetch_url; 567 } 568 569 if (EMPTYSTRING(path)) { 570 if (urltype == FTP_URL_T) { 571 rval = fetch_ftp(url); 572 goto cleanup_fetch_url; 573 } 574 if (!IS_HTTP_TYPE(urltype) || outfile == NULL) { 575 warnx("Invalid URL (no file after host) `%s'", url); 576 goto cleanup_fetch_url; 577 } 578 } 579 580 decodedpath = ftp_strdup(path); 581 url_decode(decodedpath); 582 583 if (outfile) 584 savefile = outfile; 585 else { 586 cp = strrchr(decodedpath, '/'); /* find savefile */ 587 if (cp != NULL) 588 savefile = ftp_strdup(cp + 1); 589 else 590 savefile = ftp_strdup(decodedpath); 591 } 592 DPRINTF("%s: savefile `%s'\n", __func__, savefile); 593 if (EMPTYSTRING(savefile)) { 594 if (urltype == FTP_URL_T) { 595 rval = fetch_ftp(url); 596 goto cleanup_fetch_url; 597 } 598 warnx("No file after directory (you must specify an " 599 "output file) `%s'", url); 600 goto cleanup_fetch_url; 601 } 602 603 restart_point = 0; 604 filesize = -1; 605 rangestart = rangeend = entitylen = -1; 606 mtime = -1; 607 if (restartautofetch) { 608 if (stat(savefile, &sb) == 0) 609 restart_point = sb.st_size; 610 } 611 if (urltype == FILE_URL_T) { /* file:// URLs */ 612 direction = "copied"; 613 fin = fetch_open(decodedpath, "r"); 614 if (fin == NULL) { 615 warn("Can't open `%s'", decodedpath); 616 goto cleanup_fetch_url; 617 } 618 if (fstat(fetch_fileno(fin), &sb) == 0) { 619 mtime = sb.st_mtime; 620 filesize = sb.st_size; 621 } 622 if (restart_point) { 623 if (lseek(fetch_fileno(fin), restart_point, SEEK_SET) < 0) { 624 warn("Can't seek to restart `%s'", 625 decodedpath); 626 goto cleanup_fetch_url; 627 } 628 } 629 if (verbose) { 630 fprintf(ttyout, "Copying %s", decodedpath); 631 if (restart_point) 632 fprintf(ttyout, " (restarting at " LLF ")", 633 (LLT)restart_point); 634 fputs("\n", ttyout); 635 } 636 if (0 == rcvbuf_size) { 637 rcvbuf_size = 8 * 1024; /* XXX */ 638 } 639 } else { /* ftp:// or http:// URLs */ 640 const char *leading; 641 int hasleading; 642 643 if (penv == NULL) { 644 #ifdef WITH_SSL 645 if (urltype == HTTPS_URL_T) 646 penv = getoptionvalue("https_proxy"); 647 #endif 648 if (penv == NULL && IS_HTTP_TYPE(urltype)) 649 penv = getoptionvalue("http_proxy"); 650 else if (urltype == FTP_URL_T) 651 penv = getoptionvalue("ftp_proxy"); 652 } 653 direction = "retrieved"; 654 if (! EMPTYSTRING(penv)) { /* use proxy */ 655 url_t purltype; 656 char *phost, *ppath; 657 char *pport, *no_proxy; 658 in_port_t pportnum; 659 660 isproxy = 1; 661 662 /* check URL against list of no_proxied sites */ 663 no_proxy = getoptionvalue("no_proxy"); 664 if (! EMPTYSTRING(no_proxy)) { 665 char *np, *np_copy, *np_iter; 666 unsigned long np_port; 667 size_t hlen, plen; 668 669 np_iter = np_copy = ftp_strdup(no_proxy); 670 hlen = strlen(host); 671 while ((cp = strsep(&np_iter, " ,")) != NULL) { 672 if (*cp == '\0') 673 continue; 674 if ((np = strrchr(cp, ':')) != NULL) { 675 *np++ = '\0'; 676 np_port = strtoul(np, &ep, 10); 677 if (*np == '\0' || *ep != '\0') 678 continue; 679 if (np_port != portnum) 680 continue; 681 } 682 plen = strlen(cp); 683 if (hlen < plen) 684 continue; 685 if (strncasecmp(host + hlen - plen, 686 cp, plen) == 0) { 687 isproxy = 0; 688 break; 689 } 690 } 691 FREEPTR(np_copy); 692 if (isproxy == 0 && urltype == FTP_URL_T) { 693 rval = fetch_ftp(url); 694 goto cleanup_fetch_url; 695 } 696 } 697 698 if (isproxy) { 699 if (restart_point) { 700 warnx("Can't restart via proxy URL `%s'", 701 penv); 702 goto cleanup_fetch_url; 703 } 704 if (parse_url(penv, "proxy URL", &purltype, 705 &puser, &ppass, &phost, &pport, &pportnum, 706 &ppath) == -1) 707 goto cleanup_fetch_url; 708 709 if ((!IS_HTTP_TYPE(purltype) 710 && purltype != FTP_URL_T) || 711 EMPTYSTRING(phost) || 712 (! EMPTYSTRING(ppath) 713 && strcmp(ppath, "/") != 0)) { 714 warnx("Malformed proxy URL `%s'", penv); 715 FREEPTR(phost); 716 FREEPTR(pport); 717 FREEPTR(ppath); 718 goto cleanup_fetch_url; 719 } 720 if (isipv6addr(host) && 721 strchr(host, '%') != NULL) { 722 warnx( 723 "Scoped address notation `%s' disallowed via web proxy", 724 host); 725 FREEPTR(phost); 726 FREEPTR(pport); 727 FREEPTR(ppath); 728 goto cleanup_fetch_url; 729 } 730 731 FREEPTR(host); 732 host = phost; 733 FREEPTR(port); 734 port = pport; 735 FREEPTR(path); 736 path = ftp_strdup(url); 737 FREEPTR(ppath); 738 urltype = purltype; 739 } 740 } /* ! EMPTYSTRING(penv) */ 741 742 memset(&hints, 0, sizeof(hints)); 743 hints.ai_flags = 0; 744 hints.ai_family = family; 745 hints.ai_socktype = SOCK_STREAM; 746 hints.ai_protocol = 0; 747 error = getaddrinfo(host, port, &hints, &res0); 748 if (error) { 749 warnx("Can't LOOKUP `%s:%s': %s", host, port, 750 (error == EAI_SYSTEM) ? strerror(errno) 751 : gai_strerror(error)); 752 goto cleanup_fetch_url; 753 } 754 if (res0->ai_canonname) 755 host = res0->ai_canonname; 756 757 s = -1; 758 #ifdef WITH_SSL 759 ssl = NULL; 760 #endif 761 for (res = res0; res; res = res->ai_next) { 762 char hname[NI_MAXHOST], sname[NI_MAXSERV]; 763 764 ai_unmapped(res); 765 if (getnameinfo(res->ai_addr, res->ai_addrlen, 766 hname, sizeof(hname), sname, sizeof(sname), 767 NI_NUMERICHOST | NI_NUMERICSERV) != 0) { 768 strlcpy(hname, "?", sizeof(hname)); 769 strlcpy(sname, "?", sizeof(sname)); 770 } 771 772 if (verbose && res0->ai_next) { 773 fprintf(ttyout, "Trying %s:%s ...\n", 774 hname, sname); 775 } 776 777 s = socket(res->ai_family, SOCK_STREAM, 778 res->ai_protocol); 779 if (s < 0) { 780 warn( 781 "Can't create socket for connection to " 782 "`%s:%s'", hname, sname); 783 continue; 784 } 785 786 if (ftp_connect(s, res->ai_addr, res->ai_addrlen, 787 verbose || !res->ai_next) < 0) { 788 close(s); 789 s = -1; 790 continue; 791 } 792 793 #ifdef WITH_SSL 794 if (urltype == HTTPS_URL_T) { 795 if ((ssl = fetch_start_ssl(s, host)) == NULL) { 796 close(s); 797 s = -1; 798 continue; 799 } 800 } 801 #endif 802 803 /* success */ 804 break; 805 } 806 807 if (s < 0) { 808 warnx("Can't connect to `%s:%s'", host, port); 809 goto cleanup_fetch_url; 810 } 811 812 oldalrm = xsignal(SIGALRM, timeouthttp); 813 alarmtimer(quit_time ? quit_time : 60); 814 fin = fetch_fdopen(s, "r+"); 815 fetch_set_ssl(fin, ssl); 816 alarmtimer(0); 817 818 alarmtimer(quit_time ? quit_time : 60); 819 /* 820 * Construct and send the request. 821 */ 822 if (verbose) 823 fprintf(ttyout, "Requesting %s\n", url); 824 leading = " ("; 825 hasleading = 0; 826 if (isproxy) { 827 if (verbose) { 828 fprintf(ttyout, "%svia %s:%s", leading, 829 host, port); 830 leading = ", "; 831 hasleading++; 832 } 833 fetch_printf(fin, "GET %s HTTP/1.0\r\n", path); 834 if (flushcache) 835 fetch_printf(fin, "Pragma: no-cache\r\n"); 836 } else { 837 fetch_printf(fin, "GET %s HTTP/1.1\r\n", path); 838 if (strchr(host, ':')) { 839 char *h, *p; 840 841 /* 842 * strip off IPv6 scope identifier, since it is 843 * local to the node 844 */ 845 h = ftp_strdup(host); 846 if (isipv6addr(h) && 847 (p = strchr(h, '%')) != NULL) { 848 *p = '\0'; 849 } 850 fetch_printf(fin, "Host: [%s]", h); 851 free(h); 852 } else 853 fetch_printf(fin, "Host: %s", host); 854 #ifdef WITH_SSL 855 if ((urltype == HTTP_URL_T && portnum != HTTP_PORT) || 856 (urltype == HTTPS_URL_T && portnum != HTTPS_PORT)) 857 #else 858 if (portnum != HTTP_PORT) 859 #endif 860 fetch_printf(fin, ":%u", portnum); 861 fetch_printf(fin, "\r\n"); 862 fetch_printf(fin, "Accept: */*\r\n"); 863 fetch_printf(fin, "Connection: close\r\n"); 864 if (restart_point) { 865 fputs(leading, ttyout); 866 fetch_printf(fin, "Range: bytes=" LLF "-\r\n", 867 (LLT)restart_point); 868 fprintf(ttyout, "restarting at " LLF, 869 (LLT)restart_point); 870 leading = ", "; 871 hasleading++; 872 } 873 if (flushcache) 874 fetch_printf(fin, "Cache-Control: no-cache\r\n"); 875 } 876 if ((useragent=getenv("FTPUSERAGENT")) != NULL) { 877 fetch_printf(fin, "User-Agent: %s\r\n", useragent); 878 } else { 879 fetch_printf(fin, "User-Agent: %s/%s\r\n", 880 FTP_PRODUCT, FTP_VERSION); 881 } 882 if (wwwauth) { 883 if (verbose) { 884 fprintf(ttyout, "%swith authorization", 885 leading); 886 leading = ", "; 887 hasleading++; 888 } 889 fetch_printf(fin, "Authorization: %s\r\n", wwwauth); 890 } 891 if (proxyauth) { 892 if (verbose) { 893 fprintf(ttyout, 894 "%swith proxy authorization", leading); 895 leading = ", "; 896 hasleading++; 897 } 898 fetch_printf(fin, "Proxy-Authorization: %s\r\n", proxyauth); 899 } 900 if (verbose && hasleading) 901 fputs(")\n", ttyout); 902 fetch_printf(fin, "\r\n"); 903 if (fetch_flush(fin) == EOF) { 904 warn("Writing HTTP request"); 905 alarmtimer(0); 906 goto cleanup_fetch_url; 907 } 908 alarmtimer(0); 909 910 /* Read the response */ 911 alarmtimer(quit_time ? quit_time : 60); 912 len = fetch_getline(fin, buf, sizeof(buf), &errormsg); 913 alarmtimer(0); 914 if (len < 0) { 915 if (*errormsg == '\n') 916 errormsg++; 917 warnx("Receiving HTTP reply: %s", errormsg); 918 goto cleanup_fetch_url; 919 } 920 while (len > 0 && (ISLWS(buf[len-1]))) 921 buf[--len] = '\0'; 922 DPRINTF("%s: received `%s'\n", __func__, buf); 923 924 /* Determine HTTP response code */ 925 cp = strchr(buf, ' '); 926 if (cp == NULL) 927 goto improper; 928 else 929 cp++; 930 hcode = strtol(cp, &ep, 10); 931 if (*ep != '\0' && !isspace((unsigned char)*ep)) 932 goto improper; 933 message = ftp_strdup(cp); 934 935 /* Read the rest of the header. */ 936 while (1) { 937 alarmtimer(quit_time ? quit_time : 60); 938 len = fetch_getline(fin, buf, sizeof(buf), &errormsg); 939 alarmtimer(0); 940 if (len < 0) { 941 if (*errormsg == '\n') 942 errormsg++; 943 warnx("Receiving HTTP reply: %s", errormsg); 944 goto cleanup_fetch_url; 945 } 946 while (len > 0 && (ISLWS(buf[len-1]))) 947 buf[--len] = '\0'; 948 if (len == 0) 949 break; 950 DPRINTF("%s: received `%s'\n", __func__, buf); 951 952 /* 953 * Look for some headers 954 */ 955 956 cp = buf; 957 958 if (match_token(&cp, "Content-Length:")) { 959 filesize = STRTOLL(cp, &ep, 10); 960 if (filesize < 0 || *ep != '\0') 961 goto improper; 962 DPRINTF("%s: parsed len as: " LLF "\n", 963 __func__, (LLT)filesize); 964 965 } else if (match_token(&cp, "Content-Range:")) { 966 if (! match_token(&cp, "bytes")) 967 goto improper; 968 969 if (*cp == '*') 970 cp++; 971 else { 972 rangestart = STRTOLL(cp, &ep, 10); 973 if (rangestart < 0 || *ep != '-') 974 goto improper; 975 cp = ep + 1; 976 rangeend = STRTOLL(cp, &ep, 10); 977 if (rangeend < 0 || rangeend < rangestart) 978 goto improper; 979 cp = ep; 980 } 981 if (*cp != '/') 982 goto improper; 983 cp++; 984 if (*cp == '*') 985 cp++; 986 else { 987 entitylen = STRTOLL(cp, &ep, 10); 988 if (entitylen < 0) 989 goto improper; 990 cp = ep; 991 } 992 if (*cp != '\0') 993 goto improper; 994 995 #ifndef NO_DEBUG 996 if (ftp_debug) { 997 fprintf(ttyout, "parsed range as: "); 998 if (rangestart == -1) 999 fprintf(ttyout, "*"); 1000 else 1001 fprintf(ttyout, LLF "-" LLF, 1002 (LLT)rangestart, 1003 (LLT)rangeend); 1004 fprintf(ttyout, "/" LLF "\n", (LLT)entitylen); 1005 } 1006 #endif 1007 if (! restart_point) { 1008 warnx( 1009 "Received unexpected Content-Range header"); 1010 goto cleanup_fetch_url; 1011 } 1012 1013 } else if (match_token(&cp, "Last-Modified:")) { 1014 struct tm parsed; 1015 const char *t; 1016 1017 memset(&parsed, 0, sizeof(parsed)); 1018 t = parse_rfc2616time(&parsed, cp); 1019 if (t != NULL) { 1020 parsed.tm_isdst = -1; 1021 if (*t == '\0') 1022 mtime = timegm(&parsed); 1023 #ifndef NO_DEBUG 1024 if (ftp_debug && mtime != -1) { 1025 fprintf(ttyout, 1026 "parsed time as: %s", 1027 rfc2822time(localtime(&mtime))); 1028 } 1029 #endif 1030 } 1031 1032 } else if (match_token(&cp, "Location:")) { 1033 location = ftp_strdup(cp); 1034 DPRINTF("%s: parsed location as `%s'\n", 1035 __func__, cp); 1036 1037 } else if (match_token(&cp, "Transfer-Encoding:")) { 1038 if (match_token(&cp, "binary")) { 1039 warnx( 1040 "Bogus transfer encoding `binary' (fetching anyway)"); 1041 continue; 1042 } 1043 if (! (token = match_token(&cp, "chunked"))) { 1044 warnx( 1045 "Unsupported transfer encoding `%s'", 1046 token); 1047 goto cleanup_fetch_url; 1048 } 1049 ischunked++; 1050 DPRINTF("%s: using chunked encoding\n", 1051 __func__); 1052 1053 } else if (match_token(&cp, "Proxy-Authenticate:") 1054 || match_token(&cp, "WWW-Authenticate:")) { 1055 if (! (token = match_token(&cp, "Basic"))) { 1056 DPRINTF("%s: skipping unknown auth " 1057 "scheme `%s'\n", __func__, token); 1058 continue; 1059 } 1060 FREEPTR(auth); 1061 auth = ftp_strdup(token); 1062 DPRINTF("%s: parsed auth as `%s'\n", 1063 __func__, cp); 1064 } 1065 1066 } 1067 /* finished parsing header */ 1068 1069 switch (hcode) { 1070 case 200: 1071 break; 1072 case 206: 1073 if (! restart_point) { 1074 warnx("Not expecting partial content header"); 1075 goto cleanup_fetch_url; 1076 } 1077 break; 1078 case 300: 1079 case 301: 1080 case 302: 1081 case 303: 1082 case 305: 1083 case 307: 1084 if (EMPTYSTRING(location)) { 1085 warnx( 1086 "No redirection Location provided by server"); 1087 goto cleanup_fetch_url; 1088 } 1089 if (redirect_loop++ > 5) { 1090 warnx("Too many redirections requested"); 1091 goto cleanup_fetch_url; 1092 } 1093 if (hcode == 305) { 1094 if (verbose) 1095 fprintf(ttyout, "Redirected via %s\n", 1096 location); 1097 rval = fetch_url(url, location, 1098 proxyauth, wwwauth); 1099 } else { 1100 if (verbose) 1101 fprintf(ttyout, "Redirected to %s\n", 1102 location); 1103 rval = go_fetch(location); 1104 } 1105 goto cleanup_fetch_url; 1106 #ifndef NO_AUTH 1107 case 401: 1108 case 407: 1109 { 1110 char **authp; 1111 char *auser, *apass; 1112 1113 if (hcode == 401) { 1114 authp = &wwwauth; 1115 auser = uuser; 1116 apass = pass; 1117 } else { 1118 authp = &proxyauth; 1119 auser = puser; 1120 apass = ppass; 1121 } 1122 if (verbose || *authp == NULL || 1123 auser == NULL || apass == NULL) 1124 fprintf(ttyout, "%s\n", message); 1125 if (EMPTYSTRING(auth)) { 1126 warnx( 1127 "No authentication challenge provided by server"); 1128 goto cleanup_fetch_url; 1129 } 1130 if (*authp != NULL) { 1131 char reply[10]; 1132 1133 fprintf(ttyout, 1134 "Authorization failed. Retry (y/n)? "); 1135 if (get_line(stdin, reply, sizeof(reply), NULL) 1136 < 0) { 1137 goto cleanup_fetch_url; 1138 } 1139 if (tolower((unsigned char)reply[0]) != 'y') 1140 goto cleanup_fetch_url; 1141 auser = NULL; 1142 apass = NULL; 1143 } 1144 if (auth_url(auth, authp, auser, apass) == 0) { 1145 rval = fetch_url(url, penv, 1146 proxyauth, wwwauth); 1147 memset(*authp, 0, strlen(*authp)); 1148 FREEPTR(*authp); 1149 } 1150 goto cleanup_fetch_url; 1151 } 1152 #endif 1153 default: 1154 if (message) 1155 warnx("Error retrieving file `%s'", message); 1156 else 1157 warnx("Unknown error retrieving file"); 1158 goto cleanup_fetch_url; 1159 } 1160 } /* end of ftp:// or http:// specific setup */ 1161 1162 /* Open the output file. */ 1163 1164 /* 1165 * Only trust filenames with special meaning if they came from 1166 * the command line 1167 */ 1168 if (outfile == savefile) { 1169 if (strcmp(savefile, "-") == 0) { 1170 fout = stdout; 1171 } else if (*savefile == '|') { 1172 #if 0 1173 oldpipe = xsignal(SIGPIPE, SIG_IGN); 1174 fout = popen(savefile + 1, "w"); 1175 if (fout == NULL) { 1176 warn("Can't execute `%s'", savefile + 1); 1177 goto cleanup_fetch_url; 1178 } 1179 closefunc = pclose; 1180 #endif 1181 } 1182 } 1183 if (fout == NULL) { 1184 if ((rangeend != -1 && rangeend <= restart_point) || 1185 (rangestart == -1 && filesize != -1 && filesize <= restart_point)) { 1186 /* already done */ 1187 if (verbose) 1188 fprintf(ttyout, "already done\n"); 1189 rval = 0; 1190 goto cleanup_fetch_url; 1191 } 1192 if (restart_point && rangestart != -1) { 1193 if (entitylen != -1) 1194 filesize = entitylen; 1195 if (rangestart != restart_point) { 1196 warnx( 1197 "Size of `%s' differs from save file `%s'", 1198 url, savefile); 1199 goto cleanup_fetch_url; 1200 } 1201 fout = fopen(savefile, "a"); 1202 } else 1203 fout = fopen(savefile, "w"); 1204 if (fout == NULL) { 1205 warn("Can't open `%s'", savefile); 1206 goto cleanup_fetch_url; 1207 } 1208 closefunc = fclose; 1209 } 1210 1211 /* Trap signals */ 1212 oldquit = xsignal(SIGQUIT, psummary); 1213 oldint = xsignal(SIGINT, aborthttp); 1214 1215 assert(rcvbuf_size > 0); 1216 if ((size_t)rcvbuf_size > bufsize) { 1217 if (xferbuf) 1218 (void)free(xferbuf); 1219 bufsize = rcvbuf_size; 1220 xferbuf = ftp_malloc(bufsize); 1221 } 1222 1223 bytes = 0; 1224 hashbytes = mark; 1225 if (oldalrm) { 1226 (void)xsignal(SIGALRM, oldalrm); 1227 oldalrm = NULL; 1228 } 1229 progressmeter(-1); 1230 1231 /* Finally, suck down the file. */ 1232 do { 1233 long chunksize; 1234 short lastchunk; 1235 1236 chunksize = 0; 1237 lastchunk = 0; 1238 /* read chunk-size */ 1239 if (ischunked) { 1240 if (fetch_getln(xferbuf, bufsize, fin) == NULL) { 1241 warnx("Unexpected EOF reading chunk-size"); 1242 goto cleanup_fetch_url; 1243 } 1244 errno = 0; 1245 chunksize = strtol(xferbuf, &ep, 16); 1246 if (ep == xferbuf) { 1247 warnx("Invalid chunk-size"); 1248 goto cleanup_fetch_url; 1249 } 1250 if (errno == ERANGE || chunksize < 0) { 1251 errno = ERANGE; 1252 warn("Chunk-size `%.*s'", 1253 (int)(ep-xferbuf), xferbuf); 1254 goto cleanup_fetch_url; 1255 } 1256 1257 /* 1258 * XXX: Work around bug in Apache 1.3.9 and 1259 * 1.3.11, which incorrectly put trailing 1260 * space after the chunk-size. 1261 */ 1262 while (*ep == ' ') 1263 ep++; 1264 1265 /* skip [ chunk-ext ] */ 1266 if (*ep == ';') { 1267 while (*ep && *ep != '\r') 1268 ep++; 1269 } 1270 1271 if (strcmp(ep, "\r\n") != 0) { 1272 warnx("Unexpected data following chunk-size"); 1273 goto cleanup_fetch_url; 1274 } 1275 DPRINTF("%s: got chunk-size of " LLF "\n", __func__, 1276 (LLT)chunksize); 1277 if (chunksize == 0) { 1278 lastchunk = 1; 1279 goto chunkdone; 1280 } 1281 } 1282 /* transfer file or chunk */ 1283 while (1) { 1284 struct timeval then, now, td; 1285 volatile off_t bufrem; 1286 1287 if (rate_get) 1288 (void)gettimeofday(&then, NULL); 1289 bufrem = rate_get ? rate_get : (off_t)bufsize; 1290 if (ischunked) 1291 bufrem = MIN(chunksize, bufrem); 1292 while (bufrem > 0) { 1293 flen = fetch_read(xferbuf, sizeof(char), 1294 MIN((off_t)bufsize, bufrem), fin); 1295 if (flen <= 0) 1296 goto chunkdone; 1297 bytes += flen; 1298 bufrem -= flen; 1299 if (fwrite(xferbuf, sizeof(char), flen, fout) 1300 != flen) { 1301 warn("Writing `%s'", savefile); 1302 goto cleanup_fetch_url; 1303 } 1304 if (hash && !progress) { 1305 while (bytes >= hashbytes) { 1306 (void)putc('#', ttyout); 1307 hashbytes += mark; 1308 } 1309 (void)fflush(ttyout); 1310 } 1311 if (ischunked) { 1312 chunksize -= flen; 1313 if (chunksize <= 0) 1314 break; 1315 } 1316 } 1317 if (rate_get) { 1318 while (1) { 1319 (void)gettimeofday(&now, NULL); 1320 timersub(&now, &then, &td); 1321 if (td.tv_sec > 0) 1322 break; 1323 usleep(1000000 - td.tv_usec); 1324 } 1325 } 1326 if (ischunked && chunksize <= 0) 1327 break; 1328 } 1329 /* read CRLF after chunk*/ 1330 chunkdone: 1331 if (ischunked) { 1332 if (fetch_getln(xferbuf, bufsize, fin) == NULL) { 1333 alarmtimer(0); 1334 warnx("Unexpected EOF reading chunk CRLF"); 1335 goto cleanup_fetch_url; 1336 } 1337 if (strcmp(xferbuf, "\r\n") != 0) { 1338 warnx("Unexpected data following chunk"); 1339 goto cleanup_fetch_url; 1340 } 1341 if (lastchunk) 1342 break; 1343 } 1344 } while (ischunked); 1345 1346 /* XXX: deal with optional trailer & CRLF here? */ 1347 1348 if (hash && !progress && bytes > 0) { 1349 if (bytes < mark) 1350 (void)putc('#', ttyout); 1351 (void)putc('\n', ttyout); 1352 } 1353 if (fetch_error(fin)) { 1354 warn("Reading file"); 1355 goto cleanup_fetch_url; 1356 } 1357 progressmeter(1); 1358 (void)fflush(fout); 1359 if (closefunc == fclose && mtime != -1) { 1360 struct timeval tval[2]; 1361 1362 (void)gettimeofday(&tval[0], NULL); 1363 tval[1].tv_sec = mtime; 1364 tval[1].tv_usec = 0; 1365 (*closefunc)(fout); 1366 fout = NULL; 1367 1368 if (utimes(savefile, tval) == -1) { 1369 fprintf(ttyout, 1370 "Can't change modification time to %s", 1371 rfc2822time(localtime(&mtime))); 1372 } 1373 } 1374 if (bytes > 0) 1375 ptransfer(0); 1376 bytes = 0; 1377 1378 rval = 0; 1379 goto cleanup_fetch_url; 1380 1381 improper: 1382 warnx("Improper response from `%s:%s'", host, port); 1383 1384 cleanup_fetch_url: 1385 if (oldint) 1386 (void)xsignal(SIGINT, oldint); 1387 if (oldpipe) 1388 (void)xsignal(SIGPIPE, oldpipe); 1389 if (oldalrm) 1390 (void)xsignal(SIGALRM, oldalrm); 1391 if (oldquit) 1392 (void)xsignal(SIGQUIT, oldpipe); 1393 if (fin != NULL) 1394 fetch_close(fin); 1395 else if (s != -1) 1396 close(s); 1397 if (closefunc != NULL && fout != NULL) 1398 (*closefunc)(fout); 1399 if (res0) 1400 freeaddrinfo(res0); 1401 if (savefile != outfile) 1402 FREEPTR(savefile); 1403 FREEPTR(uuser); 1404 if (pass != NULL) 1405 memset(pass, 0, strlen(pass)); 1406 FREEPTR(pass); 1407 FREEPTR(host); 1408 FREEPTR(port); 1409 FREEPTR(path); 1410 FREEPTR(decodedpath); 1411 FREEPTR(puser); 1412 if (ppass != NULL) 1413 memset(ppass, 0, strlen(ppass)); 1414 FREEPTR(ppass); 1415 FREEPTR(auth); 1416 FREEPTR(location); 1417 FREEPTR(message); 1418 return (rval); 1419 } 1420 1421 /* 1422 * Abort a HTTP retrieval 1423 */ 1424 static void 1425 aborthttp(int notused) 1426 { 1427 char msgbuf[100]; 1428 int len; 1429 1430 sigint_raised = 1; 1431 alarmtimer(0); 1432 if (fromatty) { 1433 len = snprintf(msgbuf, sizeof(msgbuf), 1434 "\n%s: HTTP fetch aborted.\n", getprogname()); 1435 if (len > 0) 1436 write(fileno(ttyout), msgbuf, len); 1437 } 1438 siglongjmp(httpabort, 1); 1439 } 1440 1441 static void 1442 timeouthttp(int notused) 1443 { 1444 char msgbuf[100]; 1445 int len; 1446 1447 alarmtimer(0); 1448 if (fromatty) { 1449 len = snprintf(msgbuf, sizeof(msgbuf), 1450 "\n%s: HTTP fetch timeout.\n", getprogname()); 1451 if (len > 0) 1452 write(fileno(ttyout), msgbuf, len); 1453 } 1454 siglongjmp(httpabort, 1); 1455 } 1456 1457 /* 1458 * Retrieve ftp URL or classic ftp argument using FTP. 1459 * Returns 1 on failure, 0 on completed xfer, -1 if ftp connection 1460 * is still open (e.g, ftp xfer with trailing /) 1461 */ 1462 static int 1463 fetch_ftp(const char *url) 1464 { 1465 char *cp, *xargv[5], rempath[MAXPATHLEN]; 1466 char *host, *path, *dir, *file, *uuser, *pass; 1467 char *port; 1468 char cmdbuf[MAXPATHLEN]; 1469 char dirbuf[4]; 1470 int dirhasglob, filehasglob, rval, transtype, xargc; 1471 int oanonftp, oautologin; 1472 in_port_t portnum; 1473 url_t urltype; 1474 1475 DPRINTF("fetch_ftp: `%s'\n", url); 1476 host = path = dir = file = uuser = pass = NULL; 1477 port = NULL; 1478 rval = 1; 1479 transtype = TYPE_I; 1480 1481 if (STRNEQUAL(url, FTP_URL)) { 1482 if ((parse_url(url, "URL", &urltype, &uuser, &pass, 1483 &host, &port, &portnum, &path) == -1) || 1484 (uuser != NULL && *uuser == '\0') || 1485 EMPTYSTRING(host)) { 1486 warnx("Invalid URL `%s'", url); 1487 goto cleanup_fetch_ftp; 1488 } 1489 /* 1490 * Note: Don't url_decode(path) here. We need to keep the 1491 * distinction between "/" and "%2F" until later. 1492 */ 1493 1494 /* check for trailing ';type=[aid]' */ 1495 if (! EMPTYSTRING(path) && (cp = strrchr(path, ';')) != NULL) { 1496 if (strcasecmp(cp, ";type=a") == 0) 1497 transtype = TYPE_A; 1498 else if (strcasecmp(cp, ";type=i") == 0) 1499 transtype = TYPE_I; 1500 else if (strcasecmp(cp, ";type=d") == 0) { 1501 warnx( 1502 "Directory listing via a URL is not supported"); 1503 goto cleanup_fetch_ftp; 1504 } else { 1505 warnx("Invalid suffix `%s' in URL `%s'", cp, 1506 url); 1507 goto cleanup_fetch_ftp; 1508 } 1509 *cp = 0; 1510 } 1511 } else { /* classic style `[user@]host:[file]' */ 1512 urltype = CLASSIC_URL_T; 1513 host = ftp_strdup(url); 1514 cp = strchr(host, '@'); 1515 if (cp != NULL) { 1516 *cp = '\0'; 1517 uuser = host; 1518 anonftp = 0; /* disable anonftp */ 1519 host = ftp_strdup(cp + 1); 1520 } 1521 cp = strchr(host, ':'); 1522 if (cp != NULL) { 1523 *cp = '\0'; 1524 path = ftp_strdup(cp + 1); 1525 } 1526 } 1527 if (EMPTYSTRING(host)) 1528 goto cleanup_fetch_ftp; 1529 1530 /* Extract the file and (if present) directory name. */ 1531 dir = path; 1532 if (! EMPTYSTRING(dir)) { 1533 /* 1534 * If we are dealing with classic `[user@]host:[path]' syntax, 1535 * then a path of the form `/file' (resulting from input of the 1536 * form `host:/file') means that we should do "CWD /" before 1537 * retrieving the file. So we set dir="/" and file="file". 1538 * 1539 * But if we are dealing with URLs like `ftp://host/path' then 1540 * a path of the form `/file' (resulting from a URL of the form 1541 * `ftp://host//file') means that we should do `CWD ' (with an 1542 * empty argument) before retrieving the file. So we set 1543 * dir="" and file="file". 1544 * 1545 * If the path does not contain / at all, we set dir=NULL. 1546 * (We get a path without any slashes if we are dealing with 1547 * classic `[user@]host:[file]' or URL `ftp://host/file'.) 1548 * 1549 * In all other cases, we set dir to a string that does not 1550 * include the final '/' that separates the dir part from the 1551 * file part of the path. (This will be the empty string if 1552 * and only if we are dealing with a path of the form `/file' 1553 * resulting from an URL of the form `ftp://host//file'.) 1554 */ 1555 cp = strrchr(dir, '/'); 1556 if (cp == dir && urltype == CLASSIC_URL_T) { 1557 file = cp + 1; 1558 (void)strlcpy(dirbuf, "/", sizeof(dirbuf)); 1559 dir = dirbuf; 1560 } else if (cp != NULL) { 1561 *cp++ = '\0'; 1562 file = cp; 1563 } else { 1564 file = dir; 1565 dir = NULL; 1566 } 1567 } else 1568 dir = NULL; 1569 if (urltype == FTP_URL_T && file != NULL) { 1570 url_decode(file); 1571 /* but still don't url_decode(dir) */ 1572 } 1573 DPRINTF("fetch_ftp: user `%s' pass `%s' host %s port %s " 1574 "path `%s' dir `%s' file `%s'\n", 1575 STRorNULL(uuser), STRorNULL(pass), 1576 STRorNULL(host), STRorNULL(port), 1577 STRorNULL(path), STRorNULL(dir), STRorNULL(file)); 1578 1579 dirhasglob = filehasglob = 0; 1580 if (doglob && urltype == CLASSIC_URL_T) { 1581 if (! EMPTYSTRING(dir) && strpbrk(dir, "*?[]{}") != NULL) 1582 dirhasglob = 1; 1583 if (! EMPTYSTRING(file) && strpbrk(file, "*?[]{}") != NULL) 1584 filehasglob = 1; 1585 } 1586 1587 /* Set up the connection */ 1588 oanonftp = anonftp; 1589 if (connected) 1590 disconnect(0, NULL); 1591 anonftp = oanonftp; 1592 (void)strlcpy(cmdbuf, getprogname(), sizeof(cmdbuf)); 1593 xargv[0] = cmdbuf; 1594 xargv[1] = host; 1595 xargv[2] = NULL; 1596 xargc = 2; 1597 if (port) { 1598 xargv[2] = port; 1599 xargv[3] = NULL; 1600 xargc = 3; 1601 } 1602 oautologin = autologin; 1603 /* don't autologin in setpeer(), use ftp_login() below */ 1604 autologin = 0; 1605 setpeer(xargc, xargv); 1606 autologin = oautologin; 1607 if ((connected == 0) || 1608 (connected == 1 && !ftp_login(host, uuser, pass))) { 1609 warnx("Can't connect or login to host `%s:%s'", 1610 host, port ? port : "?"); 1611 goto cleanup_fetch_ftp; 1612 } 1613 1614 switch (transtype) { 1615 case TYPE_A: 1616 setascii(1, xargv); 1617 break; 1618 case TYPE_I: 1619 setbinary(1, xargv); 1620 break; 1621 default: 1622 errx(1, "fetch_ftp: unknown transfer type %d", transtype); 1623 } 1624 1625 /* 1626 * Change directories, if necessary. 1627 * 1628 * Note: don't use EMPTYSTRING(dir) below, because 1629 * dir=="" means something different from dir==NULL. 1630 */ 1631 if (dir != NULL && !dirhasglob) { 1632 char *nextpart; 1633 1634 /* 1635 * If we are dealing with a classic `[user@]host:[path]' 1636 * (urltype is CLASSIC_URL_T) then we have a raw directory 1637 * name (not encoded in any way) and we can change 1638 * directories in one step. 1639 * 1640 * If we are dealing with an `ftp://host/path' URL 1641 * (urltype is FTP_URL_T), then RFC 3986 says we need to 1642 * send a separate CWD command for each unescaped "/" 1643 * in the path, and we have to interpret %hex escaping 1644 * *after* we find the slashes. It's possible to get 1645 * empty components here, (from multiple adjacent 1646 * slashes in the path) and RFC 3986 says that we should 1647 * still do `CWD ' (with a null argument) in such cases. 1648 * 1649 * Many ftp servers don't support `CWD ', so if there's an 1650 * error performing that command, bail out with a descriptive 1651 * message. 1652 * 1653 * Examples: 1654 * 1655 * host: dir="", urltype=CLASSIC_URL_T 1656 * logged in (to default directory) 1657 * host:file dir=NULL, urltype=CLASSIC_URL_T 1658 * "RETR file" 1659 * host:dir/ dir="dir", urltype=CLASSIC_URL_T 1660 * "CWD dir", logged in 1661 * ftp://host/ dir="", urltype=FTP_URL_T 1662 * logged in (to default directory) 1663 * ftp://host/dir/ dir="dir", urltype=FTP_URL_T 1664 * "CWD dir", logged in 1665 * ftp://host/file dir=NULL, urltype=FTP_URL_T 1666 * "RETR file" 1667 * ftp://host//file dir="", urltype=FTP_URL_T 1668 * "CWD ", "RETR file" 1669 * host:/file dir="/", urltype=CLASSIC_URL_T 1670 * "CWD /", "RETR file" 1671 * ftp://host///file dir="/", urltype=FTP_URL_T 1672 * "CWD ", "CWD ", "RETR file" 1673 * ftp://host/%2F/file dir="%2F", urltype=FTP_URL_T 1674 * "CWD /", "RETR file" 1675 * ftp://host/foo/file dir="foo", urltype=FTP_URL_T 1676 * "CWD foo", "RETR file" 1677 * ftp://host/foo/bar/file dir="foo/bar" 1678 * "CWD foo", "CWD bar", "RETR file" 1679 * ftp://host//foo/bar/file dir="/foo/bar" 1680 * "CWD ", "CWD foo", "CWD bar", "RETR file" 1681 * ftp://host/foo//bar/file dir="foo//bar" 1682 * "CWD foo", "CWD ", "CWD bar", "RETR file" 1683 * ftp://host/%2F/foo/bar/file dir="%2F/foo/bar" 1684 * "CWD /", "CWD foo", "CWD bar", "RETR file" 1685 * ftp://host/%2Ffoo/bar/file dir="%2Ffoo/bar" 1686 * "CWD /foo", "CWD bar", "RETR file" 1687 * ftp://host/%2Ffoo%2Fbar/file dir="%2Ffoo%2Fbar" 1688 * "CWD /foo/bar", "RETR file" 1689 * ftp://host/%2Ffoo%2Fbar%2Ffile dir=NULL 1690 * "RETR /foo/bar/file" 1691 * 1692 * Note that we don't need `dir' after this point. 1693 */ 1694 do { 1695 if (urltype == FTP_URL_T) { 1696 nextpart = strchr(dir, '/'); 1697 if (nextpart) { 1698 *nextpart = '\0'; 1699 nextpart++; 1700 } 1701 url_decode(dir); 1702 } else 1703 nextpart = NULL; 1704 DPRINTF("fetch_ftp: dir `%s', nextpart `%s'\n", 1705 STRorNULL(dir), STRorNULL(nextpart)); 1706 if (urltype == FTP_URL_T || *dir != '\0') { 1707 (void)strlcpy(cmdbuf, "cd", sizeof(cmdbuf)); 1708 xargv[0] = cmdbuf; 1709 xargv[1] = dir; 1710 xargv[2] = NULL; 1711 dirchange = 0; 1712 cd(2, xargv); 1713 if (! dirchange) { 1714 if (*dir == '\0' && code == 500) 1715 fprintf(stderr, 1716 "\n" 1717 "ftp: The `CWD ' command (without a directory), which is required by\n" 1718 " RFC 3986 to support the empty directory in the URL pathname (`//'),\n" 1719 " conflicts with the server's conformance to RFC 959.\n" 1720 " Try the same URL without the `//' in the URL pathname.\n" 1721 "\n"); 1722 goto cleanup_fetch_ftp; 1723 } 1724 } 1725 dir = nextpart; 1726 } while (dir != NULL); 1727 } 1728 1729 if (EMPTYSTRING(file)) { 1730 rval = -1; 1731 goto cleanup_fetch_ftp; 1732 } 1733 1734 if (dirhasglob) { 1735 (void)strlcpy(rempath, dir, sizeof(rempath)); 1736 (void)strlcat(rempath, "/", sizeof(rempath)); 1737 (void)strlcat(rempath, file, sizeof(rempath)); 1738 file = rempath; 1739 } 1740 1741 /* Fetch the file(s). */ 1742 xargc = 2; 1743 (void)strlcpy(cmdbuf, "get", sizeof(cmdbuf)); 1744 xargv[0] = cmdbuf; 1745 xargv[1] = file; 1746 xargv[2] = NULL; 1747 if (dirhasglob || filehasglob) { 1748 int ointeractive; 1749 1750 ointeractive = interactive; 1751 interactive = 0; 1752 if (restartautofetch) 1753 (void)strlcpy(cmdbuf, "mreget", sizeof(cmdbuf)); 1754 else 1755 (void)strlcpy(cmdbuf, "mget", sizeof(cmdbuf)); 1756 xargv[0] = cmdbuf; 1757 mget(xargc, xargv); 1758 interactive = ointeractive; 1759 } else { 1760 if (outfile == NULL) { 1761 cp = strrchr(file, '/'); /* find savefile */ 1762 if (cp != NULL) 1763 outfile = cp + 1; 1764 else 1765 outfile = file; 1766 } 1767 xargv[2] = (char *)outfile; 1768 xargv[3] = NULL; 1769 xargc++; 1770 if (restartautofetch) 1771 reget(xargc, xargv); 1772 else 1773 get(xargc, xargv); 1774 } 1775 1776 if ((code / 100) == COMPLETE) 1777 rval = 0; 1778 1779 cleanup_fetch_ftp: 1780 FREEPTR(port); 1781 FREEPTR(host); 1782 FREEPTR(path); 1783 FREEPTR(uuser); 1784 if (pass) 1785 memset(pass, 0, strlen(pass)); 1786 FREEPTR(pass); 1787 return (rval); 1788 } 1789 1790 /* 1791 * Retrieve the given file to outfile. 1792 * Supports arguments of the form: 1793 * "host:path", "ftp://host/path" if $ftpproxy, call fetch_url() else 1794 * call fetch_ftp() 1795 * "http://host/path" call fetch_url() to use HTTP 1796 * "file:///path" call fetch_url() to copy 1797 * "about:..." print a message 1798 * 1799 * Returns 1 on failure, 0 on completed xfer, -1 if ftp connection 1800 * is still open (e.g, ftp xfer with trailing /) 1801 */ 1802 static int 1803 go_fetch(const char *url) 1804 { 1805 char *proxyenv; 1806 char *p; 1807 1808 #ifndef NO_ABOUT 1809 /* 1810 * Check for about:* 1811 */ 1812 if (STRNEQUAL(url, ABOUT_URL)) { 1813 url += sizeof(ABOUT_URL) -1; 1814 if (strcasecmp(url, "ftp") == 0 || 1815 strcasecmp(url, "tnftp") == 0) { 1816 fputs( 1817 "This version of ftp has been enhanced by Luke Mewburn <lukem@NetBSD.org>\n" 1818 "for the NetBSD project. Execute `man ftp' for more details.\n", ttyout); 1819 } else if (strcasecmp(url, "lukem") == 0) { 1820 fputs( 1821 "Luke Mewburn is the author of most of the enhancements in this ftp client.\n" 1822 "Please email feedback to <lukem@NetBSD.org>.\n", ttyout); 1823 } else if (strcasecmp(url, "netbsd") == 0) { 1824 fputs( 1825 "NetBSD is a freely available and redistributable UNIX-like operating system.\n" 1826 "For more information, see http://www.NetBSD.org/\n", ttyout); 1827 } else if (strcasecmp(url, "version") == 0) { 1828 fprintf(ttyout, "Version: %s %s%s\n", 1829 FTP_PRODUCT, FTP_VERSION, 1830 #ifdef INET6 1831 "" 1832 #else 1833 " (-IPv6)" 1834 #endif 1835 ); 1836 } else { 1837 fprintf(ttyout, "`%s' is an interesting topic.\n", url); 1838 } 1839 fputs("\n", ttyout); 1840 return (0); 1841 } 1842 #endif 1843 1844 /* 1845 * Check for file:// and http:// URLs. 1846 */ 1847 if (STRNEQUAL(url, HTTP_URL) 1848 #ifdef WITH_SSL 1849 || STRNEQUAL(url, HTTPS_URL) 1850 #endif 1851 || STRNEQUAL(url, FILE_URL)) 1852 return (fetch_url(url, NULL, NULL, NULL)); 1853 1854 /* 1855 * If it contains "://" but does not begin with ftp:// 1856 * or something that was already handled, then it's 1857 * unsupported. 1858 * 1859 * If it contains ":" but not "://" then we assume the 1860 * part before the colon is a host name, not an URL scheme, 1861 * so we don't try to match that here. 1862 */ 1863 if ((p = strstr(url, "://")) != NULL && ! STRNEQUAL(url, FTP_URL)) 1864 errx(1, "Unsupported URL scheme `%.*s'", (int)(p - url), url); 1865 1866 /* 1867 * Try FTP URL-style and host:file arguments next. 1868 * If ftpproxy is set with an FTP URL, use fetch_url() 1869 * Othewise, use fetch_ftp(). 1870 */ 1871 proxyenv = getoptionvalue("ftp_proxy"); 1872 if (!EMPTYSTRING(proxyenv) && STRNEQUAL(url, FTP_URL)) 1873 return (fetch_url(url, NULL, NULL, NULL)); 1874 1875 return (fetch_ftp(url)); 1876 } 1877 1878 /* 1879 * Retrieve multiple files from the command line, 1880 * calling go_fetch() for each file. 1881 * 1882 * If an ftp path has a trailing "/", the path will be cd-ed into and 1883 * the connection remains open, and the function will return -1 1884 * (to indicate the connection is alive). 1885 * If an error occurs the return value will be the offset+1 in 1886 * argv[] of the file that caused a problem (i.e, argv[x] 1887 * returns x+1) 1888 * Otherwise, 0 is returned if all files retrieved successfully. 1889 */ 1890 int 1891 auto_fetch(int argc, char *argv[]) 1892 { 1893 volatile int argpos, rval; 1894 1895 argpos = rval = 0; 1896 1897 if (sigsetjmp(toplevel, 1)) { 1898 if (connected) 1899 disconnect(0, NULL); 1900 if (rval > 0) 1901 rval = argpos + 1; 1902 return (rval); 1903 } 1904 (void)xsignal(SIGINT, intr); 1905 (void)xsignal(SIGPIPE, lostpeer); 1906 1907 /* 1908 * Loop through as long as there's files to fetch. 1909 */ 1910 for (; (rval == 0) && (argpos < argc); argpos++) { 1911 if (strchr(argv[argpos], ':') == NULL) 1912 break; 1913 redirect_loop = 0; 1914 if (!anonftp) 1915 anonftp = 2; /* Handle "automatic" transfers. */ 1916 rval = go_fetch(argv[argpos]); 1917 if (outfile != NULL && strcmp(outfile, "-") != 0 1918 && outfile[0] != '|') 1919 outfile = NULL; 1920 if (rval > 0) 1921 rval = argpos + 1; 1922 } 1923 1924 if (connected && rval != -1) 1925 disconnect(0, NULL); 1926 return (rval); 1927 } 1928 1929 1930 /* 1931 * Upload multiple files from the command line. 1932 * 1933 * If an error occurs the return value will be the offset+1 in 1934 * argv[] of the file that caused a problem (i.e, argv[x] 1935 * returns x+1) 1936 * Otherwise, 0 is returned if all files uploaded successfully. 1937 */ 1938 int 1939 auto_put(int argc, char **argv, const char *uploadserver) 1940 { 1941 char *uargv[4], *path, *pathsep; 1942 int uargc, rval, argpos; 1943 size_t len; 1944 char cmdbuf[MAX_C_NAME]; 1945 1946 (void)strlcpy(cmdbuf, "mput", sizeof(cmdbuf)); 1947 uargv[0] = cmdbuf; 1948 uargv[1] = argv[0]; 1949 uargc = 2; 1950 uargv[2] = uargv[3] = NULL; 1951 pathsep = NULL; 1952 rval = 1; 1953 1954 DPRINTF("auto_put: target `%s'\n", uploadserver); 1955 1956 path = ftp_strdup(uploadserver); 1957 len = strlen(path); 1958 if (path[len - 1] != '/' && path[len - 1] != ':') { 1959 /* 1960 * make sure we always pass a directory to auto_fetch 1961 */ 1962 if (argc > 1) { /* more than one file to upload */ 1963 len = strlen(uploadserver) + 2; /* path + "/" + "\0" */ 1964 free(path); 1965 path = (char *)ftp_malloc(len); 1966 (void)strlcpy(path, uploadserver, len); 1967 (void)strlcat(path, "/", len); 1968 } else { /* single file to upload */ 1969 (void)strlcpy(cmdbuf, "put", sizeof(cmdbuf)); 1970 uargv[0] = cmdbuf; 1971 pathsep = strrchr(path, '/'); 1972 if (pathsep == NULL) { 1973 pathsep = strrchr(path, ':'); 1974 if (pathsep == NULL) { 1975 warnx("Invalid URL `%s'", path); 1976 goto cleanup_auto_put; 1977 } 1978 pathsep++; 1979 uargv[2] = ftp_strdup(pathsep); 1980 pathsep[0] = '/'; 1981 } else 1982 uargv[2] = ftp_strdup(pathsep + 1); 1983 pathsep[1] = '\0'; 1984 uargc++; 1985 } 1986 } 1987 DPRINTF("auto_put: URL `%s' argv[2] `%s'\n", 1988 path, STRorNULL(uargv[2])); 1989 1990 /* connect and cwd */ 1991 rval = auto_fetch(1, &path); 1992 if(rval >= 0) 1993 goto cleanup_auto_put; 1994 1995 rval = 0; 1996 1997 /* target filename provided; upload 1 file */ 1998 /* XXX : is this the best way? */ 1999 if (uargc == 3) { 2000 uargv[1] = argv[0]; 2001 put(uargc, uargv); 2002 if ((code / 100) != COMPLETE) 2003 rval = 1; 2004 } else { /* otherwise a target dir: upload all files to it */ 2005 for(argpos = 0; argv[argpos] != NULL; argpos++) { 2006 uargv[1] = argv[argpos]; 2007 mput(uargc, uargv); 2008 if ((code / 100) != COMPLETE) { 2009 rval = argpos + 1; 2010 break; 2011 } 2012 } 2013 } 2014 2015 cleanup_auto_put: 2016 free(path); 2017 FREEPTR(uargv[2]); 2018 return (rval); 2019 } 2020