1 /* $OpenBSD: fetch.c,v 1.70 2006/09/25 18:59:59 deraadt Exp $ */ 2 /* $NetBSD: fetch.c,v 1.14 1997/08/18 10:20:20 lukem Exp $ */ 3 4 /*- 5 * Copyright (c) 1997 The NetBSD Foundation, Inc. 6 * All rights reserved. 7 * 8 * This code is derived from software contributed to The NetBSD Foundation 9 * by Jason Thorpe and Luke Mewburn. 10 * 11 * Redistribution and use in source and binary forms, with or without 12 * modification, are permitted provided that the following conditions 13 * are met: 14 * 1. Redistributions of source code must retain the above copyright 15 * notice, this list of conditions and the following disclaimer. 16 * 2. Redistributions in binary form must reproduce the above copyright 17 * notice, this list of conditions and the following disclaimer in the 18 * documentation and/or other materials provided with the distribution. 19 * 3. All advertising materials mentioning features or use of this software 20 * must display the following acknowledgement: 21 * This product includes software developed by the NetBSD 22 * Foundation, Inc. and its contributors. 23 * 4. Neither the name of The NetBSD Foundation nor the names of its 24 * contributors may be used to endorse or promote products derived 25 * from this software without specific prior written permission. 26 * 27 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 28 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 29 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 30 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 31 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 32 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 33 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 34 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 35 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 36 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 37 * POSSIBILITY OF SUCH DAMAGE. 38 */ 39 40 #if !defined(lint) && !defined(SMALL) 41 static const char rcsid[] = "$OpenBSD: fetch.c,v 1.70 2006/09/25 18:59:59 deraadt Exp $"; 42 #endif /* not lint and not SMALL */ 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 53 #include <netinet/in.h> 54 55 #include <arpa/ftp.h> 56 #include <arpa/inet.h> 57 58 #include <ctype.h> 59 #include <err.h> 60 #include <libgen.h> 61 #include <limits.h> 62 #include <netdb.h> 63 #include <fcntl.h> 64 #include <signal.h> 65 #include <stdio.h> 66 #include <stdarg.h> 67 #include <errno.h> 68 #include <stdlib.h> 69 #include <string.h> 70 #include <unistd.h> 71 #include <util.h> 72 73 #ifndef SMALL 74 #include <openssl/ssl.h> 75 #include <openssl/err.h> 76 #else 77 #define SSL void 78 #endif 79 80 #include "ftp_var.h" 81 82 static int url_get(const char *, const char *, const char *); 83 void aborthttp(int); 84 void abortfile(int); 85 char hextochar(const char *); 86 char *urldecode(const char *); 87 int ftp_printf(FILE *, SSL *, const char *, ...) __attribute__((format(printf, 3, 4))); 88 char *ftp_readline(FILE *, SSL *, size_t *); 89 size_t ftp_read(FILE *, SSL *, char *, size_t); 90 #ifndef SMALL 91 int proxy_connect(int, char *); 92 int SSL_vprintf(SSL *, const char *, va_list); 93 char *SSL_readline(SSL *, size_t *); 94 #endif 95 96 #define FTP_URL "ftp://" /* ftp URL prefix */ 97 #define HTTP_URL "http://" /* http URL prefix */ 98 #define HTTPS_URL "https://" /* https URL prefix */ 99 #define FILE_URL "file:" /* file URL prefix */ 100 #define FTP_PROXY "ftp_proxy" /* env var with ftp proxy location */ 101 #define HTTP_PROXY "http_proxy" /* env var with http proxy location */ 102 103 104 #define EMPTYSTRING(x) ((x) == NULL || (*(x) == '\0')) 105 106 static const char *at_encoding_warning = 107 "Extra `@' characters in usernames and passwords should be encoded as %%40"; 108 109 jmp_buf httpabort; 110 111 static int redirect_loop; 112 113 /* 114 * Retrieve URL, via the proxy in $proxyvar if necessary. 115 * Modifies the string argument given. 116 * Returns -1 on failure, 0 on success 117 */ 118 static int 119 url_get(const char *origline, const char *proxyenv, const char *outfile) 120 { 121 char pbuf[NI_MAXSERV], hbuf[NI_MAXHOST], *cp, *portnum, *path, ststr[4]; 122 char *hosttail, *cause = "unknown", *newline, *host, *port, *buf = NULL; 123 int error, i, isftpurl = 0, isfileurl = 0, isredirect = 0, rval = -1; 124 struct addrinfo hints, *res0, *res; 125 const char * volatile savefile; 126 char * volatile proxyurl = NULL; 127 volatile int s = -1, out; 128 volatile sig_t oldintr; 129 FILE *fin = NULL; 130 off_t hashbytes; 131 const char *errstr; 132 size_t len, wlen; 133 #ifndef SMALL 134 char *sslpath = NULL, *sslhost = NULL; 135 int ishttpsurl = 0; 136 SSL_CTX *ssl_ctx = NULL; 137 #endif 138 SSL *ssl = NULL; 139 int status; 140 141 newline = strdup(origline); 142 if (newline == NULL) 143 errx(1, "Can't allocate memory to parse URL"); 144 if (strncasecmp(newline, HTTP_URL, sizeof(HTTP_URL) - 1) == 0) 145 host = newline + sizeof(HTTP_URL) - 1; 146 else if (strncasecmp(newline, FTP_URL, sizeof(FTP_URL) - 1) == 0) { 147 host = newline + sizeof(FTP_URL) - 1; 148 isftpurl = 1; 149 } else if (strncasecmp(newline, FILE_URL, sizeof(FILE_URL) - 1) == 0) { 150 host = newline + sizeof(FILE_URL) - 1; 151 isfileurl = 1; 152 #ifndef SMALL 153 } else if (strncasecmp(newline, HTTPS_URL, sizeof(HTTPS_URL) - 1) == 0) { 154 host = newline + sizeof(HTTPS_URL) - 1; 155 ishttpsurl = 1; 156 #endif 157 } else 158 errx(1, "url_get: Invalid URL '%s'", newline); 159 160 if (isfileurl) { 161 path = host; 162 } else { 163 path = strchr(host, '/'); /* find path */ 164 if (EMPTYSTRING(path)) { 165 if (isftpurl) 166 goto noftpautologin; 167 warnx("Invalid URL (no `/' after host): %s", origline); 168 goto cleanup_url_get; 169 } 170 *path++ = '\0'; 171 if (EMPTYSTRING(path)) { 172 if (isftpurl) 173 goto noftpautologin; 174 warnx("Invalid URL (no file after host): %s", origline); 175 goto cleanup_url_get; 176 } 177 } 178 179 if (outfile) 180 savefile = outfile; 181 else 182 savefile = basename(path); 183 184 if (EMPTYSTRING(savefile)) { 185 if (isftpurl) 186 goto noftpautologin; 187 warnx("Invalid URL (no file after directory): %s", origline); 188 goto cleanup_url_get; 189 } 190 191 if (!isfileurl && proxyenv != NULL) { /* use proxy */ 192 #ifndef SMALL 193 if (ishttpsurl) { 194 sslpath = strdup(path); 195 sslhost = strdup(host); 196 if (! sslpath || ! sslhost) 197 errx(1, "Can't allocate memory for https path/host."); 198 } 199 #endif 200 proxyurl = strdup(proxyenv); 201 if (proxyurl == NULL) 202 errx(1, "Can't allocate memory for proxy URL."); 203 if (strncasecmp(proxyurl, HTTP_URL, sizeof(HTTP_URL) - 1) == 0) 204 host = proxyurl + sizeof(HTTP_URL) - 1; 205 else if (strncasecmp(proxyurl, FTP_URL, sizeof(FTP_URL) - 1) == 0) 206 host = proxyurl + sizeof(FTP_URL) - 1; 207 else { 208 warnx("Malformed proxy URL: %s", proxyenv); 209 goto cleanup_url_get; 210 } 211 if (EMPTYSTRING(host)) { 212 warnx("Malformed proxy URL: %s", proxyenv); 213 goto cleanup_url_get; 214 } 215 *--path = '/'; /* add / back to real path */ 216 path = strchr(host, '/'); /* remove trailing / on host */ 217 if (!EMPTYSTRING(path)) 218 *path++ = '\0'; 219 path = newline; 220 } 221 222 if (isfileurl) { 223 struct stat st; 224 225 s = open(path, O_RDONLY); 226 if (s == -1) { 227 warn("Can't open file %s", path); 228 goto cleanup_url_get; 229 } 230 231 if (fstat(s, &st) == -1) 232 filesize = -1; 233 else 234 filesize = st.st_size; 235 236 /* Open the output file. */ 237 if (strcmp(savefile, "-") != 0) { 238 out = open(savefile, O_CREAT | O_WRONLY | O_TRUNC, 239 0666); 240 if (out < 0) { 241 warn("Can't open %s", savefile); 242 goto cleanup_url_get; 243 } 244 } else 245 out = fileno(stdout); 246 247 /* Trap signals */ 248 oldintr = NULL; 249 if (setjmp(httpabort)) { 250 if (oldintr) 251 (void)signal(SIGINT, oldintr); 252 goto cleanup_url_get; 253 } 254 oldintr = signal(SIGINT, abortfile); 255 256 bytes = 0; 257 hashbytes = mark; 258 progressmeter(-1); 259 260 if ((buf = malloc(4096)) == NULL) 261 errx(1, "Can't allocate memory for transfer buffer"); 262 263 /* Finally, suck down the file. */ 264 i = 0; 265 while ((len = read(s, buf, 4096)) > 0) { 266 bytes += len; 267 for (cp = buf; len > 0; len -= i, cp += i) { 268 if ((i = write(out, cp, len)) == -1) { 269 warn("Writing %s", savefile); 270 goto cleanup_url_get; 271 } 272 else if (i == 0) 273 break; 274 } 275 if (hash && !progress) { 276 while (bytes >= hashbytes) { 277 (void)putc('#', ttyout); 278 hashbytes += mark; 279 } 280 (void)fflush(ttyout); 281 } 282 } 283 if (hash && !progress && bytes > 0) { 284 if (bytes < mark) 285 (void)putc('#', ttyout); 286 (void)putc('\n', ttyout); 287 (void)fflush(ttyout); 288 } 289 if (len != 0) { 290 warn("Reading from file"); 291 goto cleanup_url_get; 292 } 293 progressmeter(1); 294 if (verbose) 295 fputs("Successfully retrieved file.\n", ttyout); 296 (void)signal(SIGINT, oldintr); 297 298 rval = 0; 299 goto cleanup_url_get; 300 } 301 302 if (*host == '[' && (hosttail = strrchr(host, ']')) != NULL && 303 (hosttail[1] == '\0' || hosttail[1] == ':')) { 304 host++; 305 *hosttail++ = '\0'; 306 } else 307 hosttail = host; 308 309 portnum = strrchr(hosttail, ':'); /* find portnum */ 310 if (portnum != NULL) 311 *portnum++ = '\0'; 312 313 if (debug) 314 fprintf(ttyout, "host %s, port %s, path %s, save as %s.\n", 315 host, portnum, path, savefile); 316 317 memset(&hints, 0, sizeof(hints)); 318 hints.ai_family = family; 319 hints.ai_socktype = SOCK_STREAM; 320 #ifndef SMALL 321 port = portnum ? portnum : (ishttpsurl ? httpsport : httpport); 322 #else 323 port = portnum ? portnum : httpport; 324 #endif 325 error = getaddrinfo(host, port, &hints, &res0); 326 /* 327 * If the services file is corrupt/missing, fall back 328 * on our hard-coded defines. 329 */ 330 if (error == EAI_SERVICE && port == httpport) { 331 snprintf(pbuf, sizeof(pbuf), "%d", HTTP_PORT); 332 error = getaddrinfo(host, pbuf, &hints, &res0); 333 #ifndef SMALL 334 } else if (error == EAI_SERVICE && port == httpsport) { 335 snprintf(pbuf, sizeof(pbuf), "%d", HTTPS_PORT); 336 error = getaddrinfo(host, pbuf, &hints, &res0); 337 #endif 338 } 339 if (error) { 340 warnx("%s: %s", gai_strerror(error), host); 341 goto cleanup_url_get; 342 } 343 344 s = -1; 345 for (res = res0; res; res = res->ai_next) { 346 if (getnameinfo(res->ai_addr, res->ai_addrlen, hbuf, 347 sizeof(hbuf), NULL, 0, NI_NUMERICHOST) != 0) 348 strlcpy(hbuf, "(unknown)", sizeof(hbuf)); 349 if (verbose) 350 fprintf(ttyout, "Trying %s...\n", hbuf); 351 352 s = socket(res->ai_family, res->ai_socktype, res->ai_protocol); 353 if (s == -1) { 354 cause = "socket"; 355 continue; 356 } 357 358 again: 359 if (connect(s, res->ai_addr, res->ai_addrlen) < 0) { 360 int save_errno; 361 362 if (errno == EINTR) 363 goto again; 364 save_errno = errno; 365 close(s); 366 errno = save_errno; 367 s = -1; 368 cause = "connect"; 369 continue; 370 } 371 372 /* get port in numeric */ 373 if (getnameinfo(res->ai_addr, res->ai_addrlen, NULL, 0, 374 pbuf, sizeof(pbuf), NI_NUMERICSERV) == 0) 375 port = pbuf; 376 else 377 port = NULL; 378 379 #ifndef SMALL 380 if (proxyenv && sslhost) 381 proxy_connect(s, sslhost); 382 #endif 383 break; 384 } 385 freeaddrinfo(res0); 386 if (s < 0) { 387 warn("%s", cause); 388 goto cleanup_url_get; 389 } 390 391 #ifndef SMALL 392 if (ishttpsurl) { 393 if (proxyenv && sslpath) { 394 ishttpsurl = 0; 395 proxyurl = NULL; 396 path = sslpath; 397 } 398 SSL_library_init(); 399 SSL_load_error_strings(); 400 SSLeay_add_ssl_algorithms(); 401 ssl_ctx = SSL_CTX_new(SSLv23_client_method()); 402 ssl = SSL_new(ssl_ctx); 403 if (ssl == NULL || ssl_ctx == NULL) { 404 ERR_print_errors_fp(ttyout); 405 goto cleanup_url_get; 406 } 407 if (SSL_set_fd(ssl, s) == 0) { 408 ERR_print_errors_fp(ttyout); 409 goto cleanup_url_get; 410 } 411 if (SSL_connect(ssl) <= 0) { 412 ERR_print_errors_fp(ttyout); 413 goto cleanup_url_get;; 414 } 415 } else { 416 fin = fdopen(s, "r+"); 417 } 418 #else 419 fin = fdopen(s, "r+"); 420 #endif 421 422 if (verbose) 423 fprintf(ttyout, "Requesting %s", origline); 424 /* 425 * Construct and send the request. Proxy requests don't want leading /. 426 */ 427 if (proxyurl) { 428 if (verbose) 429 fprintf(ttyout, " (via %s)\n", proxyenv); 430 /* 431 * Host: directive must use the destination host address for 432 * the original URI (path). We do not attach it at this moment. 433 */ 434 ftp_printf(fin, ssl, "GET %s HTTP/1.0\r\n%s\r\n\r\n", path, 435 HTTP_USER_AGENT); 436 } else { 437 ftp_printf(fin, ssl, "GET /%s HTTP/1.0\r\nHost: ", path); 438 if (strchr(host, ':')) { 439 char *h, *p; 440 441 /* 442 * strip off scoped address portion, since it's 443 * local to node 444 */ 445 h = strdup(host); 446 if (h == NULL) 447 errx(1, "Can't allocate memory."); 448 if ((p = strchr(h, '%')) != NULL) 449 *p = '\0'; 450 ftp_printf(fin, ssl, "[%s]", h); 451 free(h); 452 } else 453 ftp_printf(fin, ssl, "%s", host); 454 455 /* 456 * Send port number only if it's specified and does not equal 457 * 80. Some broken HTTP servers get confused if you explicitly 458 * send them the port number. 459 */ 460 #ifndef SMALL 461 if (port && strcmp(port, (ishttpsurl ? "443" : "80")) != 0) 462 ftp_printf(fin, ssl, ":%s", port); 463 #else 464 if (port && strcmp(port, "80") != 0) 465 ftp_printf(fin, ssl, ":%s", port); 466 #endif 467 ftp_printf(fin, ssl, "\r\n%s\r\n\r\n", HTTP_USER_AGENT); 468 if (verbose) 469 fprintf(ttyout, "\n"); 470 } 471 if (fin != NULL && fflush(fin) == EOF) { 472 warn("Writing HTTP request"); 473 goto cleanup_url_get; 474 } 475 if ((buf = ftp_readline(fin, ssl, &len)) == NULL) { 476 warn("Receiving HTTP reply"); 477 goto cleanup_url_get; 478 } 479 480 while (len > 0 && (buf[len-1] == '\r' || buf[len-1] == '\n')) 481 buf[--len] = '\0'; 482 if (debug) 483 fprintf(ttyout, "received '%s'\n", buf); 484 485 cp = strchr(buf, ' '); 486 if (cp == NULL) 487 goto improper; 488 else 489 cp++; 490 491 strlcpy(ststr, cp, sizeof(ststr)); 492 status = strtonum(ststr, 200, 307, &errstr); 493 if (errstr) { 494 warnx("Error retrieving file: %s", cp); 495 goto cleanup_url_get; 496 } 497 498 switch (status) { 499 case 200: /* OK */ 500 break; 501 case 301: /* Moved Permanently */ 502 case 302: /* Found */ 503 case 303: /* See Other */ 504 case 307: /* Temporary Redirect */ 505 isredirect++; 506 if (redirect_loop++ > 10) { 507 warnx("Too many redirections requested"); 508 goto cleanup_url_get; 509 } 510 break; 511 default: 512 warnx("Error retrieving file: %s", cp); 513 goto cleanup_url_get; 514 } 515 516 /* 517 * Read the rest of the header. 518 */ 519 free(buf); 520 filesize = -1; 521 522 for (;;) { 523 if ((buf = ftp_readline(fin, ssl, &len)) == NULL) { 524 warn("Receiving HTTP reply"); 525 goto cleanup_url_get; 526 } 527 528 while (len > 0 && (buf[len-1] == '\r' || buf[len-1] == '\n')) 529 buf[--len] = '\0'; 530 if (len == 0) 531 break; 532 if (debug) 533 fprintf(ttyout, "received '%s'\n", buf); 534 535 /* Look for some headers */ 536 cp = buf; 537 #define CONTENTLEN "Content-Length: " 538 if (strncasecmp(cp, CONTENTLEN, sizeof(CONTENTLEN) - 1) == 0) { 539 cp += sizeof(CONTENTLEN) - 1; 540 filesize = strtonum(cp, 0, LLONG_MAX, &errstr); 541 if (errstr != NULL) 542 goto improper; 543 #define LOCATION "Location: " 544 } else if (isredirect && 545 strncasecmp(cp, LOCATION, sizeof(LOCATION) - 1) == 0) { 546 cp += sizeof(LOCATION) - 1; 547 if (verbose) 548 fprintf(ttyout, "Redirected to %s\n", cp); 549 if (fin != NULL) 550 fclose(fin); 551 else if (s != -1) 552 close(s); 553 free(proxyurl); 554 free(newline); 555 rval = url_get(cp, proxyenv, outfile); 556 free(buf); 557 return (rval); 558 } 559 } 560 561 /* Open the output file. */ 562 if (strcmp(savefile, "-") != 0) { 563 out = open(savefile, O_CREAT | O_WRONLY | O_TRUNC, 0666); 564 if (out < 0) { 565 warn("Can't open %s", savefile); 566 goto cleanup_url_get; 567 } 568 } else 569 out = fileno(stdout); 570 571 /* Trap signals */ 572 oldintr = NULL; 573 if (setjmp(httpabort)) { 574 if (oldintr) 575 (void)signal(SIGINT, oldintr); 576 goto cleanup_url_get; 577 } 578 oldintr = signal(SIGINT, aborthttp); 579 580 bytes = 0; 581 hashbytes = mark; 582 progressmeter(-1); 583 584 free(buf); 585 586 /* Finally, suck down the file. */ 587 if ((buf = malloc(4096)) == NULL) 588 errx(1, "Can't allocate memory for transfer buffer"); 589 i = 0; 590 len = 1; 591 while (len > 0) { 592 len = ftp_read(fin, ssl, buf, 4096); 593 bytes += len; 594 for (cp = buf, wlen = len; wlen > 0; wlen -= i, cp += i) { 595 if ((i = write(out, cp, wlen)) == -1) { 596 warn("Writing %s", savefile); 597 goto cleanup_url_get; 598 } 599 else if (i == 0) 600 break; 601 } 602 if (hash && !progress) { 603 while (bytes >= hashbytes) { 604 (void)putc('#', ttyout); 605 hashbytes += mark; 606 } 607 (void)fflush(ttyout); 608 } 609 } 610 if (hash && !progress && bytes > 0) { 611 if (bytes < mark) 612 (void)putc('#', ttyout); 613 (void)putc('\n', ttyout); 614 (void)fflush(ttyout); 615 } 616 if (len != 0) { 617 warn("Reading from socket"); 618 goto cleanup_url_get; 619 } 620 progressmeter(1); 621 if (filesize != -1 && len == 0 && bytes != filesize) { 622 if (verbose) 623 fputs("Read short file.\n", ttyout); 624 goto cleanup_url_get; 625 } 626 627 if (verbose) 628 fputs("Successfully retrieved file.\n", ttyout); 629 (void)signal(SIGINT, oldintr); 630 631 rval = 0; 632 goto cleanup_url_get; 633 634 noftpautologin: 635 warnx( 636 "Auto-login using ftp URLs isn't supported when using $ftp_proxy"); 637 goto cleanup_url_get; 638 639 improper: 640 warnx("Improper response from %s", host); 641 642 cleanup_url_get: 643 #ifndef SMALL 644 if (ssl) { 645 SSL_shutdown(ssl); 646 SSL_free(ssl); 647 } 648 #endif 649 if (fin != NULL) 650 fclose(fin); 651 else if (s != -1) 652 close(s); 653 free(buf); 654 free(proxyurl); 655 free(newline); 656 return (rval); 657 } 658 659 /* 660 * Abort a http retrieval 661 */ 662 /* ARGSUSED */ 663 void 664 aborthttp(int signo) 665 { 666 667 alarmtimer(0); 668 fputs("\nhttp fetch aborted.\n", ttyout); 669 (void)fflush(ttyout); 670 longjmp(httpabort, 1); 671 } 672 673 /* 674 * Abort a http retrieval 675 */ 676 /* ARGSUSED */ 677 void 678 abortfile(int signo) 679 { 680 681 alarmtimer(0); 682 fputs("\nfile fetch aborted.\n", ttyout); 683 (void)fflush(ttyout); 684 longjmp(httpabort, 1); 685 } 686 687 /* 688 * Retrieve multiple files from the command line, transferring 689 * files of the form "host:path", "ftp://host/path" using the 690 * ftp protocol, and files of the form "http://host/path" using 691 * the http protocol. 692 * If path has a trailing "/", then return (-1); 693 * the path will be cd-ed into and the connection remains open, 694 * and the function will return -1 (to indicate the connection 695 * is alive). 696 * If an error occurs the return value will be the offset+1 in 697 * argv[] of the file that caused a problem (i.e, argv[x] 698 * returns x+1) 699 * Otherwise, 0 is returned if all files retrieved successfully. 700 */ 701 int 702 auto_fetch(int argc, char *argv[], char *outfile) 703 { 704 char *xargv[5]; 705 char *cp, *url, *host, *dir, *file, *portnum; 706 char *username, *pass, *pathstart; 707 char *ftpproxy, *httpproxy; 708 int rval, xargc; 709 volatile int argpos; 710 int dirhasglob, filehasglob, oautologin; 711 char rempath[MAXPATHLEN]; 712 713 argpos = 0; 714 715 if (setjmp(toplevel)) { 716 if (connected) 717 disconnect(0, NULL); 718 return (argpos + 1); 719 } 720 (void)signal(SIGINT, (sig_t)intr); 721 (void)signal(SIGPIPE, (sig_t)lostpeer); 722 723 if ((ftpproxy = getenv(FTP_PROXY)) != NULL && *ftpproxy == '\0') 724 ftpproxy = NULL; 725 if ((httpproxy = getenv(HTTP_PROXY)) != NULL && *httpproxy == '\0') 726 httpproxy = NULL; 727 728 /* 729 * Loop through as long as there's files to fetch. 730 */ 731 for (rval = 0; (rval == 0) && (argpos < argc); free(url), argpos++) { 732 if (strchr(argv[argpos], ':') == NULL) 733 break; 734 host = dir = file = portnum = username = pass = NULL; 735 736 /* 737 * We muck with the string, so we make a copy. 738 */ 739 url = strdup(argv[argpos]); 740 if (url == NULL) 741 errx(1, "Can't allocate memory for auto-fetch."); 742 743 /* 744 * Try HTTP URL-style arguments first. 745 */ 746 if (strncasecmp(url, HTTP_URL, sizeof(HTTP_URL) - 1) == 0 || 747 #ifndef SMALL 748 /* even if we compiled without SSL, url_get will check */ 749 strncasecmp(url, HTTPS_URL, sizeof(HTTPS_URL) -1) == 0 || 750 #endif 751 strncasecmp(url, FILE_URL, sizeof(FILE_URL) - 1) == 0) { 752 redirect_loop = 0; 753 if (url_get(url, httpproxy, outfile) == -1) 754 rval = argpos + 1; 755 continue; 756 } 757 758 /* 759 * Try FTP URL-style arguments next. If ftpproxy is 760 * set, use url_get() instead of standard ftp. 761 * Finally, try host:file. 762 */ 763 host = url; 764 if (strncasecmp(url, FTP_URL, sizeof(FTP_URL) - 1) == 0) { 765 char *passend, *passagain, *userend; 766 767 if (ftpproxy) { 768 if (url_get(url, ftpproxy, outfile) == -1) 769 rval = argpos + 1; 770 continue; 771 } 772 host += sizeof(FTP_URL) - 1; 773 dir = strchr(host, '/'); 774 775 /* Look for [user:pass@]host[:port] */ 776 777 /* check if we have "user:pass@" */ 778 userend = strchr(host, ':'); 779 passend = strchr(host, '@'); 780 if (passend && userend && userend < passend && 781 (!dir || passend < dir)) { 782 username = host; 783 pass = userend + 1; 784 host = passend + 1; 785 *userend = *passend = '\0'; 786 passagain = strchr(host, '@'); 787 if (strchr(pass, '@') != NULL || 788 (passagain != NULL && passagain < dir)) { 789 warnx(at_encoding_warning); 790 goto bad_ftp_url; 791 } 792 793 if (EMPTYSTRING(username) || EMPTYSTRING(pass)) { 794 bad_ftp_url: 795 warnx("Invalid URL: %s", argv[argpos]); 796 rval = argpos + 1; 797 continue; 798 } 799 username = urldecode(username); 800 pass = urldecode(pass); 801 } 802 803 #ifdef INET6 804 /* check [host]:port, or [host] */ 805 if (host[0] == '[') { 806 cp = strchr(host, ']'); 807 if (cp && (!dir || cp < dir)) { 808 if (cp + 1 == dir || cp[1] == ':') { 809 host++; 810 *cp++ = '\0'; 811 } else 812 cp = NULL; 813 } else 814 cp = host; 815 } else 816 cp = host; 817 #else 818 cp = host; 819 #endif 820 821 /* split off host[:port] if there is */ 822 if (cp) { 823 portnum = strchr(cp, ':'); 824 pathstart = strchr(cp, '/'); 825 /* : in path is not a port # indicator */ 826 if (portnum && pathstart && 827 pathstart < portnum) 828 portnum = NULL; 829 830 if (!portnum) 831 ; 832 else { 833 if (!dir) 834 ; 835 else if (portnum + 1 < dir) { 836 *portnum++ = '\0'; 837 /* 838 * XXX should check if portnum 839 * is decimal number 840 */ 841 } else { 842 /* empty portnum */ 843 goto bad_ftp_url; 844 } 845 } 846 } else 847 portnum = NULL; 848 } else { /* classic style `host:file' */ 849 dir = strchr(host, ':'); 850 } 851 if (EMPTYSTRING(host)) { 852 rval = argpos + 1; 853 continue; 854 } 855 856 /* 857 * If dir is NULL, the file wasn't specified 858 * (URL looked something like ftp://host) 859 */ 860 if (dir != NULL) 861 *dir++ = '\0'; 862 863 /* 864 * Extract the file and (if present) directory name. 865 */ 866 if (!EMPTYSTRING(dir)) { 867 cp = strrchr(dir, '/'); 868 if (cp != NULL) { 869 *cp++ = '\0'; 870 file = cp; 871 } else { 872 file = dir; 873 dir = NULL; 874 } 875 } 876 if (debug) 877 fprintf(ttyout, 878 "user %s:%s host %s port %s dir %s file %s\n", 879 username, pass, host, portnum, dir, file); 880 881 /* 882 * Set up the connection. 883 */ 884 if (connected) 885 disconnect(0, NULL); 886 xargv[0] = __progname; 887 xargv[1] = host; 888 xargv[2] = NULL; 889 xargc = 2; 890 if (!EMPTYSTRING(portnum)) { 891 xargv[2] = portnum; 892 xargv[3] = NULL; 893 xargc = 3; 894 } 895 oautologin = autologin; 896 if (username != NULL) 897 autologin = 0; 898 setpeer(xargc, xargv); 899 autologin = oautologin; 900 if ((connected == 0) || 901 ((connected == 1) && !ftp_login(host, username, pass))) { 902 warnx("Can't connect or login to host `%s'", host); 903 rval = argpos + 1; 904 continue; 905 } 906 907 /* Always use binary transfers. */ 908 setbinary(0, NULL); 909 910 dirhasglob = filehasglob = 0; 911 if (doglob) { 912 if (!EMPTYSTRING(dir) && 913 strpbrk(dir, "*?[]{}") != NULL) 914 dirhasglob = 1; 915 if (!EMPTYSTRING(file) && 916 strpbrk(file, "*?[]{}") != NULL) 917 filehasglob = 1; 918 } 919 920 /* Change directories, if necessary. */ 921 if (!EMPTYSTRING(dir) && !dirhasglob) { 922 xargv[0] = "cd"; 923 xargv[1] = dir; 924 xargv[2] = NULL; 925 cd(2, xargv); 926 if (!dirchange) { 927 rval = argpos + 1; 928 continue; 929 } 930 } 931 932 if (EMPTYSTRING(file)) { 933 rval = -1; 934 continue; 935 } 936 937 if (verbose) 938 fprintf(ttyout, "Retrieving %s/%s\n", dir ? dir : "", file); 939 940 if (dirhasglob) { 941 snprintf(rempath, sizeof(rempath), "%s/%s", dir, file); 942 file = rempath; 943 } 944 945 /* Fetch the file(s). */ 946 xargc = 2; 947 xargv[0] = "get"; 948 xargv[1] = file; 949 xargv[2] = NULL; 950 if (dirhasglob || filehasglob) { 951 int ointeractive; 952 953 ointeractive = interactive; 954 interactive = 0; 955 xargv[0] = "mget"; 956 mget(xargc, xargv); 957 interactive = ointeractive; 958 } else { 959 if (outfile != NULL) { 960 xargv[2] = outfile; 961 xargv[3] = NULL; 962 xargc++; 963 } 964 get(xargc, xargv); 965 } 966 967 if ((code / 100) != COMPLETE) 968 rval = argpos + 1; 969 } 970 if (connected && rval != -1) 971 disconnect(0, NULL); 972 return (rval); 973 } 974 975 char * 976 urldecode(const char *str) 977 { 978 char *ret, c; 979 int i, reallen; 980 981 if (str == NULL) 982 return NULL; 983 if ((ret = malloc(strlen(str)+1)) == NULL) 984 err(1, "Can't allocate memory for URL decoding"); 985 for (i = 0, reallen = 0; str[i] != '\0'; i++, reallen++, ret++) { 986 c = str[i]; 987 if (c == '+') { 988 *ret = ' '; 989 continue; 990 } 991 992 /* Cannot use strtol here because next char 993 * after %xx may be a digit. 994 */ 995 if (c == '%' && isxdigit(str[i+1]) && isxdigit(str[i+2])) { 996 *ret = hextochar(&str[i+1]); 997 i+=2; 998 continue; 999 } 1000 *ret = c; 1001 } 1002 *ret = '\0'; 1003 1004 return ret-reallen; 1005 } 1006 1007 char 1008 hextochar(const char *str) 1009 { 1010 char c, ret; 1011 1012 c = str[0]; 1013 ret = c; 1014 if (isalpha(c)) 1015 ret -= isupper(c) ? 'A' - 10 : 'a' - 10; 1016 else 1017 ret -= '0'; 1018 ret *= 16; 1019 1020 c = str[1]; 1021 ret += c; 1022 if (isalpha(c)) 1023 ret -= isupper(c) ? 'A' - 10 : 'a' - 10; 1024 else 1025 ret -= '0'; 1026 return ret; 1027 } 1028 1029 int 1030 isurl(const char *p) 1031 { 1032 1033 if (strncasecmp(p, FTP_URL, sizeof(FTP_URL) - 1) == 0 || 1034 strncasecmp(p, HTTP_URL, sizeof(HTTP_URL) - 1) == 0 || 1035 #ifndef SMALL 1036 strncasecmp(p, HTTPS_URL, sizeof(HTTPS_URL) - 1) == 0 || 1037 #endif 1038 strncasecmp(p, FILE_URL, sizeof(FILE_URL) - 1) == 0 || 1039 strstr(p, ":/")) 1040 return (1); 1041 return (0); 1042 } 1043 1044 char * 1045 ftp_readline(FILE *fp, SSL *ssl, size_t *lenp) 1046 { 1047 if (fp != NULL) 1048 return fparseln(fp, lenp, NULL, "\0\0\0", 0); 1049 #ifndef SMALL 1050 else if (ssl != NULL) 1051 return SSL_readline(ssl, lenp); 1052 #endif 1053 else 1054 return NULL; 1055 } 1056 1057 size_t 1058 ftp_read(FILE *fp, SSL *ssl, char *buf, size_t len) 1059 { 1060 size_t ret; 1061 if (fp != NULL) 1062 ret = fread(buf, sizeof(char), len, fp); 1063 #ifndef SMALL 1064 else if (ssl != NULL) { 1065 int nr; 1066 1067 if (len > INT_MAX) 1068 len = INT_MAX; 1069 if ((nr = SSL_read(ssl, buf, (int)len)) <= 0) 1070 ret = 0; 1071 else 1072 ret = nr; 1073 } 1074 #endif 1075 else 1076 ret = 0; 1077 return (ret); 1078 } 1079 1080 int 1081 ftp_printf(FILE *fp, SSL *ssl, const char *fmt, ...) 1082 { 1083 int ret; 1084 va_list ap; 1085 1086 va_start(ap, fmt); 1087 1088 if (fp != NULL) 1089 ret = vfprintf(fp, fmt, ap); 1090 #ifndef SMALL 1091 else if (ssl != NULL) 1092 ret = SSL_vprintf((SSL*)ssl, fmt, ap); 1093 #endif 1094 else 1095 ret = NULL; 1096 1097 va_end(ap); 1098 return (ret); 1099 } 1100 1101 #ifndef SMALL 1102 int 1103 SSL_vprintf(SSL *ssl, const char *fmt, va_list ap) 1104 { 1105 int ret; 1106 char *string; 1107 1108 if ((ret = vasprintf(&string, fmt, ap)) == -1) 1109 return ret; 1110 ret = SSL_write(ssl, string, ret); 1111 free(string); 1112 return ret; 1113 } 1114 1115 char * 1116 SSL_readline(SSL *ssl, size_t *lenp) 1117 { 1118 size_t i, len; 1119 char *buf, *q, c; 1120 1121 len = 128; 1122 if ((buf = malloc(len)) == NULL) 1123 errx(1, "Can't allocate memory for transfer buffer"); 1124 for (i = 0; ; i++) { 1125 if (i >= len - 1) { 1126 if ((q = realloc(buf, 2 * len)) == NULL) 1127 errx(1, "Can't expand transfer buffer"); 1128 buf = q; 1129 len *= 2; 1130 } 1131 if (SSL_read(ssl, &c, 1) <= 0) 1132 break; 1133 buf[i] = c; 1134 if (c == '\n') 1135 break; 1136 } 1137 *lenp = i; 1138 return (buf); 1139 } 1140 1141 int 1142 proxy_connect(int socket, char *host) 1143 { 1144 int l; 1145 char buf[1024]; 1146 char *connstr, *hosttail, *port; 1147 1148 if (*host == '[' && (hosttail = strrchr(host, ']')) != NULL && 1149 (hosttail[1] == '\0' || hosttail[1] == ':')) { 1150 host++; 1151 *hosttail++ = '\0'; 1152 } else 1153 hosttail = host; 1154 1155 port = strrchr(hosttail, ':'); /* find portnum */ 1156 if (port != NULL) 1157 *port++ = '\0'; 1158 if (!port) 1159 port = "443"; 1160 1161 l = asprintf(&connstr, "CONNECT %s:%s HTTP/1.1\n\n", host, port); 1162 if (l == -1) 1163 errx(1, "Could not allocate memory to assemble connect string!"); 1164 if (debug) 1165 printf("%s", connstr); 1166 if (write(socket, connstr, l) != l) 1167 err(1, "Could not send connect string"); 1168 read(socket, &buf, sizeof(buf)); /* only proxy header XXX: error handling? */ 1169 return(200); 1170 } 1171 #endif 1172