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