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