1 /* $NetBSD: fetch.c,v 1.105 2000/02/14 21:46:26 lukem 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.105 2000/02/14 21:46:26 lukem 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] != '\0')) { 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:%s(%d) path `%s'\n", 430 *user ? *user : "<null>", *pass ? *pass : "<null>", 431 *host ? *host : "<null>", *port ? *port : "<null>", 432 *portnum ? *portnum : -1, *path ? *path : "<null>"); 433 434 return (0); 435 } 436 437 sigjmp_buf httpabort; 438 439 /* 440 * Retrieve URL, via a proxy if necessary, using HTTP. 441 * If proxyenv is set, use that for the proxy, otherwise try ftp_proxy or 442 * http_proxy as appropriate. 443 * Supports HTTP redirects. 444 * Returns -1 on failure, 0 on completed xfer, 1 if ftp connection 445 * is still open (e.g, ftp xfer with trailing /) 446 */ 447 static int 448 fetch_url(url, proxyenv, proxyauth, wwwauth) 449 const char *url; 450 const char *proxyenv; 451 char *proxyauth; 452 char *wwwauth; 453 { 454 #if defined(NI_NUMERICHOST) && defined(INET6) 455 struct addrinfo hints, *res, *res0 = NULL; 456 int error; 457 char hbuf[NI_MAXHOST]; 458 #else 459 struct sockaddr_in sin; 460 struct hostent *hp = NULL; 461 #endif 462 volatile sigfunc oldintr, oldintp; 463 volatile int s; 464 struct stat sb; 465 int ischunked, isproxy, rval, hcode; 466 size_t len; 467 static size_t bufsize; 468 static char *xferbuf; 469 char *cp, *ep, *buf, *savefile; 470 char *auth, *location, *message; 471 char *user, *pass, *host, *port, *path, *decodedpath; 472 char *puser, *ppass; 473 off_t hashbytes, rangestart, rangeend, entitylen; 474 int (*closefunc) __P((FILE *)); 475 FILE *fin, *fout; 476 time_t mtime; 477 url_t urltype; 478 in_port_t portnum; 479 480 oldintr = oldintp = NULL; 481 closefunc = NULL; 482 fin = fout = NULL; 483 s = -1; 484 buf = savefile = NULL; 485 auth = location = message = NULL; 486 ischunked = isproxy = hcode = 0; 487 rval = 1; 488 user = pass = host = path = decodedpath = puser = ppass = NULL; 489 490 #ifdef __GNUC__ /* shut up gcc warnings */ 491 (void)&closefunc; 492 (void)&fin; 493 (void)&fout; 494 (void)&buf; 495 (void)&savefile; 496 (void)&rval; 497 (void)&isproxy; 498 (void)&hcode; 499 (void)&ischunked; 500 (void)&message; 501 (void)&location; 502 (void)&auth; 503 (void)&decodedpath; 504 #endif 505 506 if (parse_url(url, "URL", &urltype, &user, &pass, &host, &port, 507 &portnum, &path) == -1) 508 goto cleanup_fetch_url; 509 510 if (urltype == FILE_URL_T && ! EMPTYSTRING(host) 511 && strcasecmp(host, "localhost") != 0) { 512 warnx("No support for non local file URL `%s'", url); 513 goto cleanup_fetch_url; 514 } 515 516 if (EMPTYSTRING(path)) { 517 if (urltype == FTP_URL_T) { 518 rval = fetch_ftp(url); 519 goto cleanup_fetch_url; 520 } 521 if (urltype != HTTP_URL_T || outfile == NULL) { 522 warnx("Invalid URL (no file after host) `%s'", url); 523 goto cleanup_fetch_url; 524 } 525 } 526 527 decodedpath = xstrdup(path); 528 url_decode(decodedpath); 529 530 if (outfile) 531 savefile = xstrdup(outfile); 532 else { 533 cp = strrchr(decodedpath, '/'); /* find savefile */ 534 if (cp != NULL) 535 savefile = xstrdup(cp + 1); 536 else 537 savefile = xstrdup(decodedpath); 538 } 539 if (EMPTYSTRING(savefile)) { 540 if (urltype == FTP_URL_T) { 541 rval = fetch_ftp(url); 542 goto cleanup_fetch_url; 543 } 544 warnx("Invalid URL (no file after directory) `%s'", url); 545 goto cleanup_fetch_url; 546 } else { 547 if (debug) 548 fprintf(ttyout, "got savefile as `%s'\n", savefile); 549 } 550 551 restart_point = 0; 552 filesize = -1; 553 rangestart = rangeend = entitylen = -1; 554 mtime = -1; 555 if (restartautofetch) { 556 if (strcmp(savefile, "-") != 0 && *savefile != '|' && 557 stat(savefile, &sb) == 0) 558 restart_point = sb.st_size; 559 } 560 if (urltype == FILE_URL_T) { /* file:// URLs */ 561 direction = "copied"; 562 fin = fopen(decodedpath, "r"); 563 if (fin == NULL) { 564 warn("Cannot open file `%s'", decodedpath); 565 goto cleanup_fetch_url; 566 } 567 if (fstat(fileno(fin), &sb) == 0) { 568 mtime = sb.st_mtime; 569 filesize = sb.st_size; 570 } 571 if (restart_point) { 572 if (lseek(fileno(fin), restart_point, SEEK_SET) < 0) { 573 warn("Can't lseek to restart `%s'", 574 decodedpath); 575 goto cleanup_fetch_url; 576 } 577 } 578 if (verbose) { 579 fprintf(ttyout, "Copying %s", decodedpath); 580 if (restart_point) 581 #ifndef NO_QUAD 582 fprintf(ttyout, " (restarting at %lld)", 583 (long long)restart_point); 584 #else 585 fprintf(ttyout, " (restarting at %ld)", 586 (long)restart_point); 587 #endif 588 fputs("\n", ttyout); 589 } 590 } else { /* ftp:// or http:// URLs */ 591 char *leading; 592 int hasleading; 593 594 if (proxyenv == NULL) { 595 if (urltype == HTTP_URL_T) 596 proxyenv = getoptionvalue("http_proxy"); 597 else if (urltype == FTP_URL_T) 598 proxyenv = getoptionvalue("ftp_proxy"); 599 } 600 direction = "retrieved"; 601 if (! EMPTYSTRING(proxyenv)) { /* use proxy */ 602 url_t purltype; 603 char *phost, *ppath; 604 char *pport, *no_proxy; 605 606 isproxy = 1; 607 608 /* check URL against list of no_proxied sites */ 609 no_proxy = getoptionvalue("no_proxy"); 610 if (! EMPTYSTRING(no_proxy)) { 611 char *np, *np_copy; 612 long np_port; 613 size_t hlen, plen; 614 615 np_copy = xstrdup(no_proxy); 616 hlen = strlen(host); 617 while ((cp = strsep(&np_copy, " ,")) != NULL) { 618 if (*cp == '\0') 619 continue; 620 if ((np = strrchr(cp, ':')) != NULL) { 621 *np = '\0'; 622 np_port = 623 strtol(np + 1, &ep, 10); 624 if (*ep != '\0') 625 continue; 626 if (np_port != portnum) 627 continue; 628 } 629 plen = strlen(cp); 630 if (strncasecmp(host + hlen - plen, 631 cp, plen) == 0) { 632 isproxy = 0; 633 break; 634 } 635 } 636 FREEPTR(np_copy); 637 } 638 639 if (isproxy) { 640 if (parse_url(proxyenv, "proxy URL", &purltype, 641 &puser, &ppass, &phost, &pport, &portnum, 642 &ppath) == -1) 643 goto cleanup_fetch_url; 644 645 if ((purltype != HTTP_URL_T 646 && purltype != FTP_URL_T) || 647 EMPTYSTRING(phost) || 648 (! EMPTYSTRING(ppath) 649 && strcmp(ppath, "/") != 0)) { 650 warnx("Malformed proxy URL `%s'", 651 proxyenv); 652 FREEPTR(phost); 653 FREEPTR(pport); 654 FREEPTR(ppath); 655 goto cleanup_fetch_url; 656 } 657 658 FREEPTR(host); 659 host = phost; 660 FREEPTR(port); 661 port = pport; 662 FREEPTR(path); 663 path = xstrdup(url); 664 FREEPTR(ppath); 665 } 666 } /* ! EMPTYSTRING(proxyenv) */ 667 668 #if !defined(NI_NUMERICHOST) || !defined(INET6) 669 memset(&sin, 0, sizeof(sin)); 670 sin.sin_family = AF_INET; 671 672 if (isdigit((unsigned char)host[0])) { 673 if (inet_aton(host, &sin.sin_addr) == 0) { 674 warnx("Invalid IP address `%s'", host); 675 goto cleanup_fetch_url; 676 } 677 } else { 678 hp = gethostbyname(host); 679 if (hp == NULL) { 680 warnx("%s: %s", host, hstrerror(h_errno)); 681 goto cleanup_fetch_url; 682 } 683 if (hp->h_addrtype != AF_INET) { 684 warnx("`%s': not an Internet address?", host); 685 goto cleanup_fetch_url; 686 } 687 if (hp->h_length > sizeof(sin.sin_addr)) 688 hp->h_length = sizeof(sin.sin_addr); 689 memcpy(&sin.sin_addr, hp->h_addr, hp->h_length); 690 } 691 sin.sin_port = htons(portnum); 692 693 s = socket(AF_INET, SOCK_STREAM, 0); 694 if (s == -1) { 695 warn("Can't create socket"); 696 goto cleanup_fetch_url; 697 } 698 699 while (xconnect(s, (struct sockaddr *)&sin, 700 sizeof(sin)) == -1) { 701 if (errno == EINTR) 702 continue; 703 if (hp && hp->h_addr_list[1]) { 704 int oerrno = errno; 705 char *ia; 706 707 ia = inet_ntoa(sin.sin_addr); 708 errno = oerrno; 709 warn("Connect to address `%s'", ia); 710 hp->h_addr_list++; 711 memcpy(&sin.sin_addr, hp->h_addr_list[0], 712 (size_t)hp->h_length); 713 if (verbose) 714 fprintf(ttyout, "Trying %s...\n", 715 inet_ntoa(sin.sin_addr)); 716 (void)close(s); 717 s = socket(AF_INET, SOCK_STREAM, 0); 718 if (s < 0) { 719 warn("Can't create socket"); 720 goto cleanup_fetch_url; 721 } 722 continue; 723 } 724 warn("Can't connect to `%s'", host); 725 goto cleanup_fetch_url; 726 } 727 #else 728 memset(&hints, 0, sizeof(hints)); 729 hints.ai_flags = 0; 730 hints.ai_family = AF_UNSPEC; 731 hints.ai_socktype = SOCK_STREAM; 732 hints.ai_protocol = 0; 733 error = getaddrinfo(host, port, &hints, &res0); 734 if (error) { 735 warnx(gai_strerror(error)); 736 goto cleanup_fetch_url; 737 } 738 if (res0->ai_canonname) 739 host = res0->ai_canonname; 740 741 s = -1; 742 for (res = res0; res; res = res->ai_next) { 743 if (getnameinfo(res->ai_addr, res->ai_addrlen, 744 hbuf, sizeof(hbuf), NULL, 0, 745 NI_NUMERICHOST) != 0) 746 strncpy(hbuf, "invalid", sizeof(hbuf)); 747 748 if (verbose && res != res0) 749 fprintf(ttyout, "Trying %s...\n", hbuf); 750 751 s = socket(res->ai_family, res->ai_socktype, 752 res->ai_protocol); 753 if (s < 0) { 754 warn("Can't create socket"); 755 continue; 756 } 757 758 if (xconnect(s, res->ai_addr, res->ai_addrlen) < 0) { 759 warn("Connect to address `%s'", hbuf); 760 close(s); 761 s = -1; 762 continue; 763 } 764 765 /* success */ 766 break; 767 } 768 freeaddrinfo(res0); 769 770 if (s < 0) { 771 warn("Can't connect to %s", host); 772 goto cleanup_fetch_url; 773 } 774 #endif 775 776 fin = fdopen(s, "r+"); 777 /* 778 * Construct and send the request. 779 */ 780 if (verbose) 781 fprintf(ttyout, "Requesting %s\n", url); 782 leading = " ("; 783 hasleading = 0; 784 if (isproxy) { 785 if (verbose) { 786 fprintf(ttyout, "%svia %s:%s", leading, 787 host, port); 788 leading = ", "; 789 hasleading++; 790 } 791 fprintf(fin, "GET %s HTTP/1.0\r\n", path); 792 if (flushcache) 793 fprintf(fin, "Pragma: no-cache\r\n"); 794 } else { 795 fprintf(fin, "GET %s HTTP/1.1\r\n", path); 796 fprintf(fin, "Host: %s:%d\r\n", host, portnum); 797 fprintf(fin, "Accept: */*\r\n"); 798 fprintf(fin, "Connection: close\r\n"); 799 if (restart_point) { 800 fputs(leading, ttyout); 801 #ifndef NO_QUAD 802 fprintf(fin, "Range: bytes=%lld-\r\n", 803 (long long)restart_point); 804 fprintf(ttyout, "restarting at %lld", 805 (long long)restart_point); 806 #else 807 fprintf(fin, "Range: bytes=%ld-\r\n", 808 (long)restart_point); 809 fprintf(ttyout, "restarting at %ld", 810 (long)restart_point); 811 #endif 812 leading = ", "; 813 hasleading++; 814 } 815 if (flushcache) 816 fprintf(fin, "Cache-Control: no-cache\r\n"); 817 } 818 fprintf(fin, "User-Agent: %s/%s\r\n", FTP_PRODUCT, FTP_VERSION); 819 if (wwwauth) { 820 if (verbose) { 821 fprintf(ttyout, "%swith authorization", 822 leading); 823 leading = ", "; 824 hasleading++; 825 } 826 fprintf(fin, "Authorization: %s\r\n", wwwauth); 827 } 828 if (proxyauth) { 829 if (verbose) { 830 fprintf(ttyout, 831 "%swith proxy authorization", leading); 832 leading = ", "; 833 hasleading++; 834 } 835 fprintf(fin, "Proxy-Authorization: %s\r\n", proxyauth); 836 } 837 if (verbose && hasleading) 838 fputs(")\n", ttyout); 839 fprintf(fin, "\r\n"); 840 if (fflush(fin) == EOF) { 841 warn("Writing HTTP request"); 842 goto cleanup_fetch_url; 843 } 844 845 /* Read the response */ 846 if ((buf = fparseln(fin, &len, NULL, "\0\0\0", 0)) == NULL) { 847 warn("Receiving HTTP reply"); 848 goto cleanup_fetch_url; 849 } 850 while (len > 0 && (buf[len-1] == '\r' || buf[len-1] == '\n')) 851 buf[--len] = '\0'; 852 if (debug) 853 fprintf(ttyout, "received `%s'\n", buf); 854 855 /* Determine HTTP response code */ 856 cp = strchr(buf, ' '); 857 if (cp == NULL) 858 goto improper; 859 else 860 cp++; 861 hcode = strtol(cp, &ep, 10); 862 if (*ep != '\0' && !isspace((unsigned char)*ep)) 863 goto improper; 864 message = xstrdup(cp); 865 866 /* Read the rest of the header. */ 867 FREEPTR(buf); 868 while (1) { 869 if ((buf = fparseln(fin, &len, NULL, "\0\0\0", 0)) 870 == NULL) { 871 warn("Receiving HTTP reply"); 872 goto cleanup_fetch_url; 873 } 874 while (len > 0 && 875 (buf[len-1] == '\r' || buf[len-1] == '\n')) 876 buf[--len] = '\0'; 877 if (len == 0) 878 break; 879 if (debug) 880 fprintf(ttyout, "received `%s'\n", buf); 881 882 /* Look for some headers */ 883 cp = buf; 884 885 #define CONTENTLEN "Content-Length: " 886 if (strncasecmp(cp, CONTENTLEN, 887 sizeof(CONTENTLEN) - 1) == 0) { 888 cp += sizeof(CONTENTLEN) - 1; 889 #ifndef NO_QUAD 890 filesize = strtoq(cp, &ep, 10); 891 #else 892 filesize = strtol(cp, &ep, 10); 893 #endif 894 if (filesize < 0 || *ep != '\0') 895 goto improper; 896 if (debug) 897 #ifndef NO_QUAD 898 fprintf(ttyout, "parsed len as: %lld\n", 899 (long long)filesize); 900 #else 901 fprintf(ttyout, "parsed len as: %ld\n", 902 (long)filesize); 903 #endif 904 905 #define CONTENTRANGE "Content-Range: bytes " 906 } else if (strncasecmp(cp, CONTENTRANGE, 907 sizeof(CONTENTRANGE) - 1) == 0) { 908 cp += sizeof(CONTENTRANGE) - 1; 909 #ifndef NO_QUAD 910 rangestart = strtoq(cp, &ep, 10); 911 #else 912 rangestart = strtol(cp, &ep, 10); 913 #endif 914 if (rangestart < 0 || *ep != '-') 915 goto improper; 916 cp = ep + 1; 917 918 #ifndef NO_QUAD 919 rangeend = strtoq(cp, &ep, 10); 920 #else 921 rangeend = strtol(cp, &ep, 10); 922 #endif 923 if (rangeend < 0 || *ep != '/' || 924 rangeend < rangestart) 925 goto improper; 926 cp = ep + 1; 927 928 #ifndef NO_QUAD 929 entitylen = strtoq(cp, &ep, 10); 930 #else 931 entitylen = strtol(cp, &ep, 10); 932 #endif 933 if (entitylen < 0 || *ep != '\0') 934 goto improper; 935 936 if (debug) 937 #ifndef NO_QUAD 938 fprintf(ttyout, 939 "parsed range as: %lld-%lld/%lld\n", 940 (long long)rangestart, 941 (long long)rangeend, 942 (long long)entitylen); 943 #else 944 fprintf(ttyout, 945 "parsed range as: %ld-%ld/%ld\n", 946 (long)rangestart, 947 (long)rangeend, 948 (long)entitylen); 949 #endif 950 if (! restart_point) { 951 warnx( 952 "Received unexpected Content-Range header"); 953 goto cleanup_fetch_url; 954 } 955 956 #define LASTMOD "Last-Modified: " 957 } else if (strncasecmp(cp, LASTMOD, 958 sizeof(LASTMOD) - 1) == 0) { 959 struct tm parsed; 960 char *t; 961 962 cp += sizeof(LASTMOD) - 1; 963 /* RFC 1123 */ 964 if ((t = strptime(cp, 965 "%a, %d %b %Y %H:%M:%S GMT", 966 &parsed)) 967 /* RFC 850 */ 968 || (t = strptime(cp, 969 "%a, %d-%b-%y %H:%M:%S GMT", 970 &parsed)) 971 /* asctime */ 972 || (t = strptime(cp, 973 "%a, %b %d %H:%M:%S %Y", 974 &parsed))) { 975 parsed.tm_isdst = -1; 976 if (*t == '\0') 977 mtime = timegm(&parsed); 978 if (debug && mtime != -1) { 979 fprintf(ttyout, 980 "parsed date as: %s", 981 ctime(&mtime)); 982 } 983 } 984 985 #define LOCATION "Location: " 986 } else if (strncasecmp(cp, LOCATION, 987 sizeof(LOCATION) - 1) == 0) { 988 cp += sizeof(LOCATION) - 1; 989 location = xstrdup(cp); 990 if (debug) 991 fprintf(ttyout, 992 "parsed location as: %s\n", cp); 993 994 #define TRANSENC "Transfer-Encoding: " 995 } else if (strncasecmp(cp, TRANSENC, 996 sizeof(TRANSENC) - 1) == 0) { 997 cp += sizeof(TRANSENC) - 1; 998 if (strcasecmp(cp, "chunked") != 0) { 999 warnx( 1000 "Unsupported transfer encoding - `%s'", 1001 cp); 1002 goto cleanup_fetch_url; 1003 } 1004 ischunked++; 1005 if (debug) 1006 fprintf(ttyout, 1007 "using chunked encoding\n"); 1008 1009 #define PROXYAUTH "Proxy-Authenticate: " 1010 } else if (strncasecmp(cp, PROXYAUTH, 1011 sizeof(PROXYAUTH) - 1) == 0) { 1012 cp += sizeof(PROXYAUTH) - 1; 1013 FREEPTR(auth); 1014 auth = xstrdup(cp); 1015 if (debug) 1016 fprintf(ttyout, 1017 "parsed proxy-auth as: %s\n", cp); 1018 1019 #define WWWAUTH "WWW-Authenticate: " 1020 } else if (strncasecmp(cp, WWWAUTH, 1021 sizeof(WWWAUTH) - 1) == 0) { 1022 cp += sizeof(WWWAUTH) - 1; 1023 FREEPTR(auth); 1024 auth = xstrdup(cp); 1025 if (debug) 1026 fprintf(ttyout, 1027 "parsed www-auth as: %s\n", cp); 1028 1029 } 1030 1031 } 1032 /* finished parsing header */ 1033 FREEPTR(buf); 1034 1035 switch (hcode) { 1036 case 200: 1037 break; 1038 case 206: 1039 if (! restart_point) { 1040 warnx("Not expecting partial content header"); 1041 goto cleanup_fetch_url; 1042 } 1043 break; 1044 case 300: 1045 case 301: 1046 case 302: 1047 case 303: 1048 case 305: 1049 if (EMPTYSTRING(location)) { 1050 warnx( 1051 "No redirection Location provided by server"); 1052 goto cleanup_fetch_url; 1053 } 1054 if (redirect_loop++ > 5) { 1055 warnx("Too many redirections requested"); 1056 goto cleanup_fetch_url; 1057 } 1058 if (hcode == 305) { 1059 if (verbose) 1060 fprintf(ttyout, "Redirected via %s\n", 1061 location); 1062 rval = fetch_url(url, location, 1063 proxyauth, wwwauth); 1064 } else { 1065 if (verbose) 1066 fprintf(ttyout, "Redirected to %s\n", 1067 location); 1068 rval = go_fetch(location); 1069 } 1070 goto cleanup_fetch_url; 1071 case 401: 1072 case 407: 1073 { 1074 char **authp; 1075 char *auser, *apass; 1076 1077 fprintf(ttyout, "%s\n", message); 1078 if (EMPTYSTRING(auth)) { 1079 warnx( 1080 "No authentication challenge provided by server"); 1081 goto cleanup_fetch_url; 1082 } 1083 if (hcode == 401) { 1084 authp = &wwwauth; 1085 auser = user; 1086 apass = pass; 1087 } else { 1088 authp = &proxyauth; 1089 auser = puser; 1090 apass = ppass; 1091 } 1092 if (*authp != NULL) { 1093 char reply[10]; 1094 1095 fprintf(ttyout, 1096 "Authorization failed. Retry (y/n)? "); 1097 if (fgets(reply, sizeof(reply), stdin) 1098 == NULL) { 1099 clearerr(stdin); 1100 goto cleanup_fetch_url; 1101 } else { 1102 if (tolower(reply[0]) != 'y') 1103 goto cleanup_fetch_url; 1104 } 1105 auser = NULL; 1106 apass = NULL; 1107 } 1108 if (auth_url(auth, authp, auser, apass) == 0) { 1109 rval = fetch_url(url, proxyenv, 1110 proxyauth, wwwauth); 1111 memset(*authp, 0, strlen(*authp)); 1112 FREEPTR(*authp); 1113 } 1114 goto cleanup_fetch_url; 1115 } 1116 default: 1117 if (message) 1118 warnx("Error retrieving file - `%s'", message); 1119 else 1120 warnx("Unknown error retrieving file"); 1121 goto cleanup_fetch_url; 1122 } 1123 } /* end of ftp:// or http:// specific setup */ 1124 1125 /* Open the output file. */ 1126 if (strcmp(savefile, "-") == 0) { 1127 fout = stdout; 1128 } else if (*savefile == '|') { 1129 oldintp = xsignal(SIGPIPE, SIG_IGN); 1130 fout = popen(savefile + 1, "w"); 1131 if (fout == NULL) { 1132 warn("Can't run `%s'", savefile + 1); 1133 goto cleanup_fetch_url; 1134 } 1135 closefunc = pclose; 1136 } else { 1137 if (restart_point){ 1138 if (entitylen != -1) 1139 filesize = entitylen; 1140 if (rangestart != -1 && rangestart != restart_point) { 1141 warnx( 1142 "Size of `%s' differs from save file `%s'", 1143 url, savefile); 1144 goto cleanup_fetch_url; 1145 } 1146 fout = fopen(savefile, "a"); 1147 } else 1148 fout = fopen(savefile, "w"); 1149 if (fout == NULL) { 1150 warn("Can't open `%s'", savefile); 1151 goto cleanup_fetch_url; 1152 } 1153 closefunc = fclose; 1154 } 1155 1156 /* Trap signals */ 1157 if (sigsetjmp(httpabort, 1)) 1158 goto cleanup_fetch_url; 1159 (void)xsignal(SIGQUIT, psummary); 1160 oldintr = xsignal(SIGINT, aborthttp); 1161 1162 if (rcvbuf_size > bufsize) { 1163 if (xferbuf) 1164 (void)free(xferbuf); 1165 bufsize = rcvbuf_size; 1166 xferbuf = xmalloc(bufsize); 1167 } 1168 1169 bytes = 0; 1170 hashbytes = mark; 1171 progressmeter(-1); 1172 1173 /* Finally, suck down the file. */ 1174 do { 1175 long chunksize; 1176 1177 chunksize = 0; 1178 /* read chunksize */ 1179 if (ischunked) { 1180 if (fgets(xferbuf, bufsize, fin) == NULL) { 1181 warnx("Unexpected EOF reading chunksize"); 1182 goto cleanup_fetch_url; 1183 } 1184 chunksize = strtol(xferbuf, &ep, 16); 1185 1186 /* 1187 * XXX: Work around bug in Apache 1.3.9, which 1188 * incorrectly puts a trailing space after 1189 * the chunksize. 1190 */ 1191 if (*ep == ' ') 1192 ep++; 1193 1194 if (strcmp(ep, "\r\n") != 0) { 1195 warnx("Unexpected data following chunksize"); 1196 goto cleanup_fetch_url; 1197 } 1198 if (debug) 1199 fprintf(ttyout, 1200 #ifndef NO_QUAD 1201 "got chunksize of %lld\n", 1202 (long long)chunksize); 1203 #else 1204 "got chunksize of %ld\n", 1205 (long)chunksize); 1206 #endif 1207 if (chunksize == 0) 1208 break; 1209 } 1210 /* transfer file or chunk */ 1211 while (1) { 1212 struct timeval then, now, td; 1213 off_t bufrem; 1214 1215 if (rate_get) 1216 (void)gettimeofday(&then, NULL); 1217 bufrem = rate_get ? rate_get : bufsize; 1218 if (ischunked) 1219 bufrem = MIN(chunksize, bufrem); 1220 while (bufrem > 0) { 1221 len = fread(xferbuf, sizeof(char), 1222 MIN(bufsize, bufrem), fin); 1223 if (len <= 0) 1224 goto chunkdone; 1225 bytes += len; 1226 bufrem -= len; 1227 if (fwrite(xferbuf, sizeof(char), len, fout) 1228 != len) { 1229 warn("Writing `%s'", savefile); 1230 goto cleanup_fetch_url; 1231 } 1232 if (hash && !progress) { 1233 while (bytes >= hashbytes) { 1234 (void)putc('#', ttyout); 1235 hashbytes += mark; 1236 } 1237 (void)fflush(ttyout); 1238 } 1239 if (ischunked) { 1240 chunksize -= len; 1241 if (chunksize <= 0) 1242 break; 1243 } 1244 } 1245 if (rate_get) { 1246 while (1) { 1247 (void)gettimeofday(&now, NULL); 1248 timersub(&now, &then, &td); 1249 if (td.tv_sec > 0) 1250 break; 1251 usleep(1000000 - td.tv_usec); 1252 } 1253 } 1254 if (ischunked && chunksize <= 0) 1255 break; 1256 } 1257 /* read CRLF after chunk*/ 1258 chunkdone: 1259 if (ischunked) { 1260 if (fgets(xferbuf, bufsize, fin) == NULL) 1261 break; 1262 if (strcmp(xferbuf, "\r\n") != 0) { 1263 warnx("Unexpected data following chunk"); 1264 goto cleanup_fetch_url; 1265 } 1266 } 1267 } while (ischunked); 1268 if (hash && !progress && bytes > 0) { 1269 if (bytes < mark) 1270 (void)putc('#', ttyout); 1271 (void)putc('\n', ttyout); 1272 } 1273 if (ferror(fin)) { 1274 warn("Reading file"); 1275 goto cleanup_fetch_url; 1276 } 1277 progressmeter(1); 1278 bytes = 0; 1279 (void)fflush(fout); 1280 if (closefunc == fclose && mtime != -1) { 1281 struct timeval tval[2]; 1282 1283 (void)gettimeofday(&tval[0], NULL); 1284 tval[1].tv_sec = mtime; 1285 tval[1].tv_usec = 0; 1286 (*closefunc)(fout); 1287 fout = NULL; 1288 1289 if (utimes(savefile, tval) == -1) { 1290 fprintf(ttyout, 1291 "Can't change modification time to %s", 1292 asctime(localtime(&mtime))); 1293 } 1294 } 1295 if (bytes > 0) 1296 ptransfer(0); 1297 1298 rval = 0; 1299 goto cleanup_fetch_url; 1300 1301 improper: 1302 warnx("Improper response from `%s'", host); 1303 1304 cleanup_fetch_url: 1305 if (oldintr) 1306 (void)xsignal(SIGINT, oldintr); 1307 if (oldintp) 1308 (void)xsignal(SIGPIPE, oldintp); 1309 if (fin != NULL) 1310 fclose(fin); 1311 else if (s != -1) 1312 close(s); 1313 if (closefunc != NULL && fout != NULL) 1314 (*closefunc)(fout); 1315 FREEPTR(savefile); 1316 FREEPTR(user); 1317 FREEPTR(pass); 1318 FREEPTR(host); 1319 FREEPTR(port); 1320 FREEPTR(path); 1321 FREEPTR(decodedpath); 1322 FREEPTR(puser); 1323 FREEPTR(ppass); 1324 FREEPTR(buf); 1325 FREEPTR(auth); 1326 FREEPTR(location); 1327 FREEPTR(message); 1328 return (rval); 1329 } 1330 1331 /* 1332 * Abort a HTTP retrieval 1333 */ 1334 void 1335 aborthttp(notused) 1336 int notused; 1337 { 1338 char msgbuf[100]; 1339 int len; 1340 1341 alarmtimer(0); 1342 len = strlcpy(msgbuf, "\nHTTP fetch aborted.\n", sizeof(msgbuf)); 1343 write(fileno(ttyout), msgbuf, len); 1344 siglongjmp(httpabort, 1); 1345 } 1346 1347 /* 1348 * Retrieve ftp URL or classic ftp argument using FTP. 1349 * Returns 1 on failure, 0 on completed xfer, -1 if ftp connection 1350 * is still open (e.g, ftp xfer with trailing /) 1351 */ 1352 static int 1353 fetch_ftp(url) 1354 const char *url; 1355 { 1356 char *cp, *xargv[5], rempath[MAXPATHLEN]; 1357 char *host, *path, *dir, *file, *user, *pass; 1358 char *port; 1359 int dirhasglob, filehasglob, oautologin, rval, type, xargc; 1360 in_port_t portnum; 1361 url_t urltype; 1362 1363 host = path = dir = file = user = pass = NULL; 1364 port = NULL; 1365 rval = 1; 1366 type = TYPE_I; 1367 1368 if (strncasecmp(url, FTP_URL, sizeof(FTP_URL) - 1) == 0) { 1369 if ((parse_url(url, "URL", &urltype, &user, &pass, 1370 &host, &port, &portnum, &path) == -1) || 1371 (user != NULL && *user == '\0') || 1372 (pass != NULL && *pass == '\0') || 1373 EMPTYSTRING(host)) { 1374 warnx("Invalid URL `%s'", url); 1375 goto cleanup_fetch_ftp; 1376 } 1377 url_decode(user); 1378 url_decode(pass); 1379 /* 1380 * Note: Don't url_decode(path) here. We need to keep the 1381 * distinction between "/" and "%2F" until later. 1382 */ 1383 1384 /* check for trailing ';type=[aid]' */ 1385 if (! EMPTYSTRING(path) && (cp = strrchr(path, ';')) != NULL) { 1386 if (strcasecmp(cp, ";type=a") == 0) 1387 type = TYPE_A; 1388 else if (strcasecmp(cp, ";type=i") == 0) 1389 type = TYPE_I; 1390 else if (strcasecmp(cp, ";type=d") == 0) { 1391 warnx( 1392 "Directory listing via a URL is not supported"); 1393 goto cleanup_fetch_ftp; 1394 } else { 1395 warnx("Invalid suffix `%s' in URL `%s'", cp, 1396 url); 1397 goto cleanup_fetch_ftp; 1398 } 1399 *cp = 0; 1400 } 1401 } else { /* classic style `[user@]host:[file]' */ 1402 urltype = CLASSIC_URL_T; 1403 host = xstrdup(url); 1404 cp = strchr(host, '@'); 1405 if (cp != NULL) { 1406 *cp = '\0'; 1407 user = host; 1408 anonftp = 0; /* disable anonftp */ 1409 host = xstrdup(cp + 1); 1410 } 1411 cp = strchr(host, ':'); 1412 if (cp != NULL) { 1413 *cp = '\0'; 1414 path = xstrdup(cp + 1); 1415 } 1416 } 1417 if (EMPTYSTRING(host)) 1418 goto cleanup_fetch_ftp; 1419 1420 /* Extract the file and (if present) directory name. */ 1421 dir = path; 1422 if (! EMPTYSTRING(dir)) { 1423 /* 1424 * If we are dealing with classic `[user@]host:[path]' syntax, 1425 * then a path of the form `/file' (resulting from input of the 1426 * form `host:/file') means that we should do "CWD /" before 1427 * retrieving the file. So we set dir="/" and file="file". 1428 * 1429 * But if we are dealing with URLs like `ftp://host/path' then 1430 * a path of the form `/file' (resulting from a URL of the form 1431 * `ftp://host//file') means that we should do `CWD ' (with an 1432 * empty argument) before retrieving the file. So we set 1433 * dir="" and file="file". 1434 * 1435 * If the path does not contain / at all, we set dir=NULL. 1436 * (We get a path without any slashes if we are dealing with 1437 * classic `[user@]host:[file]' or URL `ftp://host/file'.) 1438 * 1439 * In all other cases, we set dir to a string that does not 1440 * include the final '/' that separates the dir part from the 1441 * file part of the path. (This will be the empty string if 1442 * and only if we are dealing with a path of the form `/file' 1443 * resulting from an URL of the form `ftp://host//file'.) 1444 */ 1445 cp = strrchr(dir, '/'); 1446 if (cp == dir && urltype == CLASSIC_URL_T) { 1447 file = cp + 1; 1448 dir = "/"; 1449 } else if (cp != NULL) { 1450 *cp++ = '\0'; 1451 file = cp; 1452 } else { 1453 file = dir; 1454 dir = NULL; 1455 } 1456 } else 1457 dir = NULL; 1458 if (urltype == FTP_URL_T && file != NULL) { 1459 url_decode(file); 1460 /* but still don't url_decode(dir) */ 1461 } 1462 if (debug) 1463 fprintf(ttyout, 1464 "fetch_ftp: user `%s' pass `%s' host %s:%s path `%s' dir `%s' file `%s'\n", 1465 user ? user : "<null>", pass ? pass : "<null>", 1466 host ? host : "<null>", port ? port : "<null>", 1467 path ? path : "<null>", 1468 dir ? dir : "<null>", file ? file : "<null>"); 1469 1470 dirhasglob = filehasglob = 0; 1471 if (doglob && urltype == CLASSIC_URL_T) { 1472 if (! EMPTYSTRING(dir) && strpbrk(dir, "*?[]{}") != NULL) 1473 dirhasglob = 1; 1474 if (! EMPTYSTRING(file) && strpbrk(file, "*?[]{}") != NULL) 1475 filehasglob = 1; 1476 } 1477 1478 /* Set up the connection */ 1479 if (connected) 1480 disconnect(0, NULL); 1481 xargv[0] = __progname; 1482 xargv[1] = host; 1483 xargv[2] = NULL; 1484 xargc = 2; 1485 if (port) { 1486 xargv[2] = port; 1487 xargv[3] = NULL; 1488 xargc = 3; 1489 } 1490 oautologin = autologin; 1491 if (user != NULL) 1492 autologin = 0; 1493 setpeer(xargc, xargv); 1494 autologin = oautologin; 1495 if ((connected == 0) || ((connected == 1) 1496 && !ftp_login(host, user, pass))) { 1497 warnx("Can't connect or login to host `%s'", host); 1498 goto cleanup_fetch_ftp; 1499 } 1500 1501 switch (type) { 1502 case TYPE_A: 1503 setascii(0, NULL); 1504 break; 1505 case TYPE_I: 1506 setbinary(0, NULL); 1507 break; 1508 default: 1509 errx(1, "fetch_ftp: unknown transfer type %d", type); 1510 } 1511 1512 /* 1513 * Change directories, if necessary. 1514 * 1515 * Note: don't use EMPTYSTRING(dir) below, because 1516 * dir=="" means something different from dir==NULL. 1517 */ 1518 if (dir != NULL && !dirhasglob) { 1519 char *nextpart; 1520 1521 /* 1522 * If we are dealing with a classic `[user@]host:[path]' 1523 * (urltype is CLASSIC_URL_T) then we have a raw directory 1524 * name (not encoded in any way) and we can change 1525 * directories in one step. 1526 * 1527 * If we are dealing with an `ftp://host/path' URL 1528 * (urltype is FTP_URL_T), then RFC 1738 says we need to 1529 * send a separate CWD command for each unescaped "/" 1530 * in the path, and we have to interpret %hex escaping 1531 * *after* we find the slashes. It's possible to get 1532 * empty components here, (from multiple adjacent 1533 * slashes in the path) and RFC 1738 says that we should 1534 * still do `CWD ' (with a null argument) in such cases. 1535 * 1536 * Many ftp servers don't support `CWD ', so if there's an 1537 * error performing that command, bail out with a descriptive 1538 * message. 1539 * 1540 * Examples: 1541 * 1542 * host: dir="", urltype=CLASSIC_URL_T 1543 * logged in (to default directory) 1544 * host:file dir=NULL, urltype=CLASSIC_URL_T 1545 * "RETR file" 1546 * host:dir/ dir="dir", urltype=CLASSIC_URL_T 1547 * "CWD dir", logged in 1548 * ftp://host/ dir="", urltype=FTP_URL_T 1549 * logged in (to default directory) 1550 * ftp://host/dir/ dir="dir", urltype=FTP_URL_T 1551 * "CWD dir", logged in 1552 * ftp://host/file dir=NULL, urltype=FTP_URL_T 1553 * "RETR file" 1554 * ftp://host//file dir="", urltype=FTP_URL_T 1555 * "CWD ", "RETR file" 1556 * host:/file dir="/", urltype=CLASSIC_URL_T 1557 * "CWD /", "RETR file" 1558 * ftp://host///file dir="/", urltype=FTP_URL_T 1559 * "CWD ", "CWD ", "RETR file" 1560 * ftp://host/%2F/file dir="%2F", urltype=FTP_URL_T 1561 * "CWD /", "RETR file" 1562 * ftp://host/foo/file dir="foo", urltype=FTP_URL_T 1563 * "CWD foo", "RETR file" 1564 * ftp://host/foo/bar/file dir="foo/bar" 1565 * "CWD foo", "CWD bar", "RETR file" 1566 * ftp://host//foo/bar/file dir="/foo/bar" 1567 * "CWD ", "CWD foo", "CWD bar", "RETR file" 1568 * ftp://host/foo//bar/file dir="foo//bar" 1569 * "CWD foo", "CWD ", "CWD bar", "RETR file" 1570 * ftp://host/%2F/foo/bar/file dir="%2F/foo/bar" 1571 * "CWD /", "CWD foo", "CWD bar", "RETR file" 1572 * ftp://host/%2Ffoo/bar/file dir="%2Ffoo/bar" 1573 * "CWD /foo", "CWD bar", "RETR file" 1574 * ftp://host/%2Ffoo%2Fbar/file dir="%2Ffoo%2Fbar" 1575 * "CWD /foo/bar", "RETR file" 1576 * ftp://host/%2Ffoo%2Fbar%2Ffile dir=NULL 1577 * "RETR /foo/bar/file" 1578 * 1579 * Note that we don't need `dir' after this point. 1580 */ 1581 do { 1582 if (urltype == FTP_URL_T) { 1583 nextpart = strchr(dir, '/'); 1584 if (nextpart) { 1585 *nextpart = '\0'; 1586 nextpart++; 1587 } 1588 url_decode(dir); 1589 } else 1590 nextpart = NULL; 1591 if (debug) 1592 fprintf(ttyout, "dir `%s', nextpart `%s'\n", 1593 dir ? dir : "<null>", 1594 nextpart ? nextpart : "<null>"); 1595 if (urltype == FTP_URL_T || *dir != '\0') { 1596 xargv[0] = "cd"; 1597 xargv[1] = dir; 1598 xargv[2] = NULL; 1599 dirchange = 0; 1600 cd(2, xargv); 1601 if (! dirchange) { 1602 if (*dir == '\0' && code == 500) 1603 fprintf(stderr, 1604 "\n" 1605 "ftp: The `CWD ' command (without a directory), which is required by\n" 1606 " RFC 1738 to support the empty directory in the URL pathname (`//'),\n" 1607 " conflicts with the server's conformance to RFC 959.\n" 1608 " Try the same URL without the `//' in the URL pathname.\n" 1609 "\n"); 1610 goto cleanup_fetch_ftp; 1611 } 1612 } 1613 dir = nextpart; 1614 } while (dir != NULL); 1615 } 1616 1617 if (EMPTYSTRING(file)) { 1618 rval = -1; 1619 goto cleanup_fetch_ftp; 1620 } 1621 1622 if (dirhasglob) { 1623 (void)strlcpy(rempath, dir, sizeof(rempath)); 1624 (void)strlcat(rempath, "/", sizeof(rempath)); 1625 (void)strlcat(rempath, file, sizeof(rempath)); 1626 file = rempath; 1627 } 1628 1629 /* Fetch the file(s). */ 1630 xargc = 2; 1631 xargv[0] = "get"; 1632 xargv[1] = file; 1633 xargv[2] = NULL; 1634 if (dirhasglob || filehasglob) { 1635 int ointeractive; 1636 1637 ointeractive = interactive; 1638 interactive = 0; 1639 xargv[0] = "mget"; 1640 mget(xargc, xargv); 1641 interactive = ointeractive; 1642 } else { 1643 if (outfile == NULL) { 1644 cp = strrchr(file, '/'); /* find savefile */ 1645 if (cp != NULL) 1646 outfile = cp + 1; 1647 else 1648 outfile = file; 1649 } 1650 xargv[2] = (char *)outfile; 1651 xargv[3] = NULL; 1652 xargc++; 1653 if (restartautofetch) 1654 reget(xargc, xargv); 1655 else 1656 get(xargc, xargv); 1657 } 1658 1659 if ((code / 100) == COMPLETE) 1660 rval = 0; 1661 1662 cleanup_fetch_ftp: 1663 FREEPTR(host); 1664 FREEPTR(path); 1665 FREEPTR(user); 1666 FREEPTR(pass); 1667 return (rval); 1668 } 1669 1670 /* 1671 * Retrieve the given file to outfile. 1672 * Supports arguments of the form: 1673 * "host:path", "ftp://host/path" if $ftpproxy, call fetch_url() else 1674 * call fetch_ftp() 1675 * "http://host/path" call fetch_url() to use HTTP 1676 * "file:///path" call fetch_url() to copy 1677 * "about:..." print a message 1678 * 1679 * Returns 1 on failure, 0 on completed xfer, -1 if ftp connection 1680 * is still open (e.g, ftp xfer with trailing /) 1681 */ 1682 static int 1683 go_fetch(url) 1684 const char *url; 1685 { 1686 char *proxy; 1687 1688 /* 1689 * Check for about:* 1690 */ 1691 if (strncasecmp(url, ABOUT_URL, sizeof(ABOUT_URL) - 1) == 0) { 1692 url += sizeof(ABOUT_URL) -1; 1693 if (strcasecmp(url, "ftp") == 0) { 1694 fputs( 1695 "This version of ftp has been enhanced by Luke Mewburn <lukem@netbsd.org>\n" 1696 "for the NetBSD project. Execute `man ftp' for more details.\n", ttyout); 1697 } else if (strcasecmp(url, "lukem") == 0) { 1698 fputs( 1699 "Luke Mewburn is the author of most of the enhancements in this ftp client.\n" 1700 "Please email feedback to <lukem@netbsd.org>.\n", ttyout); 1701 } else if (strcasecmp(url, "netbsd") == 0) { 1702 fputs( 1703 "NetBSD is a freely available and redistributable UNIX-like operating system.\n" 1704 "For more information, see http://www.netbsd.org/index.html\n", ttyout); 1705 } else if (strcasecmp(url, "version") == 0) { 1706 fprintf(ttyout, "Version: %s %s\n", 1707 FTP_PRODUCT, FTP_VERSION); 1708 } else { 1709 fprintf(ttyout, "`%s' is an interesting topic.\n", url); 1710 } 1711 fputs("\n", ttyout); 1712 return (0); 1713 } 1714 1715 /* 1716 * Check for file:// and http:// URLs. 1717 */ 1718 if (strncasecmp(url, HTTP_URL, sizeof(HTTP_URL) - 1) == 0 || 1719 strncasecmp(url, FILE_URL, sizeof(FILE_URL) - 1) == 0) 1720 return (fetch_url(url, NULL, NULL, NULL)); 1721 1722 /* 1723 * Try FTP URL-style and host:file arguments next. 1724 * If ftpproxy is set with an FTP URL, use fetch_url() 1725 * Othewise, use fetch_ftp(). 1726 */ 1727 proxy = getoptionvalue("ftp_proxy"); 1728 if (!EMPTYSTRING(proxy) && 1729 strncasecmp(url, FTP_URL, sizeof(FTP_URL) - 1) == 0) 1730 return (fetch_url(url, NULL, NULL, NULL)); 1731 1732 return (fetch_ftp(url)); 1733 } 1734 1735 /* 1736 * Retrieve multiple files from the command line, 1737 * calling go_fetch() for each file. 1738 * 1739 * If an ftp path has a trailing "/", the path will be cd-ed into and 1740 * the connection remains open, and the function will return -1 1741 * (to indicate the connection is alive). 1742 * If an error occurs the return value will be the offset+1 in 1743 * argv[] of the file that caused a problem (i.e, argv[x] 1744 * returns x+1) 1745 * Otherwise, 0 is returned if all files retrieved successfully. 1746 */ 1747 int 1748 auto_fetch(argc, argv) 1749 int argc; 1750 char *argv[]; 1751 { 1752 volatile int argpos; 1753 int rval; 1754 1755 argpos = 0; 1756 1757 if (sigsetjmp(toplevel, 1)) { 1758 if (connected) 1759 disconnect(0, NULL); 1760 return (argpos + 1); 1761 } 1762 (void)xsignal(SIGINT, intr); 1763 (void)xsignal(SIGPIPE, lostpeer); 1764 1765 /* 1766 * Loop through as long as there's files to fetch. 1767 */ 1768 for (rval = 0; (rval == 0) && (argpos < argc); argpos++) { 1769 if (strchr(argv[argpos], ':') == NULL) 1770 break; 1771 redirect_loop = 0; 1772 if (!anonftp) 1773 anonftp = 2; /* Handle "automatic" transfers. */ 1774 rval = go_fetch(argv[argpos]); 1775 if (outfile != NULL && strcmp(outfile, "-") != 0 1776 && outfile[0] != '|') 1777 outfile = NULL; 1778 if (rval > 0) 1779 rval = argpos + 1; 1780 } 1781 1782 if (connected && rval != -1) 1783 disconnect(0, NULL); 1784 return (rval); 1785 } 1786