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