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