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