1 /* $NetBSD: fetch.c,v 1.16 1997/09/21 01:06:31 lukem Exp $ */ 2 3 /*- 4 * Copyright (c) 1997 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Jason Thorpe and 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.16 1997/09/21 01:06:31 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 52 #include <netinet/in.h> 53 54 #include <arpa/ftp.h> 55 #include <arpa/inet.h> 56 57 #include <ctype.h> 58 #include <err.h> 59 #include <netdb.h> 60 #include <fcntl.h> 61 #include <signal.h> 62 #include <stdio.h> 63 #include <stdlib.h> 64 #include <string.h> 65 #include <unistd.h> 66 67 #include "ftp_var.h" 68 69 static int url_get __P((const char *, const char *)); 70 void aborthttp __P((int)); 71 72 73 #define FTP_URL "ftp://" /* ftp URL prefix */ 74 #define HTTP_URL "http://" /* http URL prefix */ 75 #define FTP_PROXY "ftp_proxy" /* env var with ftp proxy location */ 76 #define HTTP_PROXY "http_proxy" /* env var with http proxy location */ 77 78 79 #define EMPTYSTRING(x) ((x) == NULL || (*(x) == '\0')) 80 81 jmp_buf httpabort; 82 83 /* 84 * Retrieve URL, via the proxy in $proxyvar if necessary. 85 * Modifies the string argument given. 86 * Returns -1 on failure, 0 on success 87 */ 88 static int 89 url_get(origline, proxyenv) 90 const char *origline; 91 const char *proxyenv; 92 { 93 struct sockaddr_in sin; 94 int i, out, isftpurl; 95 u_int16_t port; 96 volatile int s; 97 size_t len; 98 char c, *cp, *ep, *portnum, *path, buf[4096]; 99 const char *savefile; 100 char *line, *proxy, *host; 101 volatile sig_t oldintr; 102 off_t hashbytes; 103 104 s = -1; 105 proxy = NULL; 106 isftpurl = 0; 107 108 #ifdef __GNUC__ /* XXX: to shut up gcc warnings */ 109 (void)&savefile; 110 (void)&proxy; 111 #endif 112 113 line = strdup(origline); 114 if (line == NULL) 115 errx(1, "Can't allocate memory to parse URL"); 116 if (strncasecmp(line, HTTP_URL, sizeof(HTTP_URL) - 1) == 0) 117 host = line + sizeof(HTTP_URL) - 1; 118 else if (strncasecmp(line, FTP_URL, sizeof(FTP_URL) - 1) == 0) { 119 host = line + sizeof(FTP_URL) - 1; 120 isftpurl = 1; 121 } else 122 errx(1, "url_get: Invalid URL '%s'", line); 123 124 path = strchr(host, '/'); /* find path */ 125 if (EMPTYSTRING(path)) { 126 if (isftpurl) 127 goto noftpautologin; 128 warnx("Invalid URL (no `/' after host): %s", origline); 129 goto cleanup_url_get; 130 } 131 *path++ = '\0'; 132 if (EMPTYSTRING(path)) { 133 if (isftpurl) 134 goto noftpautologin; 135 warnx("Invalid URL (no file after host): %s", origline); 136 goto cleanup_url_get; 137 } 138 139 savefile = strrchr(path, '/'); /* find savefile */ 140 if (savefile != NULL) 141 savefile++; 142 else 143 savefile = path; 144 if (EMPTYSTRING(savefile)) { 145 if (isftpurl) 146 goto noftpautologin; 147 warnx("Invalid URL (no file after directory): %s", origline); 148 goto cleanup_url_get; 149 } 150 151 if (proxyenv != NULL) { /* use proxy */ 152 proxy = strdup(proxyenv); 153 if (proxy == NULL) 154 errx(1, "Can't allocate memory for proxy URL."); 155 if (strncasecmp(proxy, HTTP_URL, sizeof(HTTP_URL) - 1) == 0) 156 host = proxy + sizeof(HTTP_URL) - 1; 157 else if (strncasecmp(proxy, FTP_URL, sizeof(FTP_URL) - 1) == 0) 158 host = proxy + sizeof(FTP_URL) - 1; 159 else { 160 warnx("Malformed proxy URL: %s", proxyenv); 161 goto cleanup_url_get; 162 } 163 if (EMPTYSTRING(host)) { 164 warnx("Malformed proxy URL: %s", proxyenv); 165 goto cleanup_url_get; 166 } 167 *--path = '/'; /* add / back to real path */ 168 path = strchr(host, '/'); /* remove trailing / on host */ 169 if (! EMPTYSTRING(path)) 170 *path++ = '\0'; 171 path = line; 172 } 173 174 portnum = strchr(host, ':'); /* find portnum */ 175 if (portnum != NULL) 176 *portnum++ = '\0'; 177 178 if (debug) 179 printf("host %s, port %s, path %s, save as %s.\n", 180 host, portnum, path, savefile); 181 182 memset(&sin, 0, sizeof(sin)); 183 sin.sin_family = AF_INET; 184 185 if (isdigit(host[0])) { 186 if (inet_aton(host, &sin.sin_addr) == 0) { 187 warnx("Invalid IP address: %s", host); 188 goto cleanup_url_get; 189 } 190 } else { 191 struct hostent *hp; 192 193 hp = gethostbyname(host); 194 if (hp == NULL) { 195 warnx("%s: %s", host, hstrerror(h_errno)); 196 goto cleanup_url_get; 197 } 198 if (hp->h_addrtype != AF_INET) { 199 warnx("%s: not an Internet address?", host); 200 goto cleanup_url_get; 201 } 202 memcpy(&sin.sin_addr, hp->h_addr, hp->h_length); 203 } 204 205 if (! EMPTYSTRING(portnum)) { 206 char *ep; 207 long nport; 208 209 nport = strtol(portnum, &ep, 10); 210 if (nport < 1 || nport > 0xffff || *ep != '\0') { 211 warnx("Invalid port: %s", portnum); 212 goto cleanup_url_get; 213 } 214 port = htons(nport); 215 } else 216 port = httpport; 217 sin.sin_port = port; 218 219 s = socket(AF_INET, SOCK_STREAM, 0); 220 if (s == -1) { 221 warn("Can't create socket"); 222 goto cleanup_url_get; 223 } 224 225 if (connect(s, (struct sockaddr *)&sin, sizeof(sin)) == -1) { 226 warn("Can't connect to %s", host); 227 goto cleanup_url_get; 228 } 229 230 /* 231 * Construct and send the request. We're expecting a return 232 * status of "200". Proxy requests don't want leading /. 233 */ 234 if (!proxy) 235 printf("Requesting %s\n", origline); 236 else 237 printf("Requesting %s (via %s)\n", origline, proxyenv); 238 snprintf(buf, sizeof(buf), "GET %s%s HTTP/1.0\r\n\r\n", 239 proxy ? "" : "/", path); 240 len = strlen(buf); 241 if (write(s, buf, len) < len) { 242 warn("Writing HTTP request"); 243 goto cleanup_url_get; 244 } 245 memset(buf, 0, sizeof(buf)); 246 for (cp = buf; cp < buf + sizeof(buf); ) { 247 if (read(s, cp, 1) != 1) 248 goto improper; 249 if (*cp == '\r') 250 continue; 251 if (*cp == '\n') 252 break; 253 cp++; 254 } 255 buf[sizeof(buf) - 1] = '\0'; /* sanity */ 256 cp = strchr(buf, ' '); 257 if (cp == NULL) 258 goto improper; 259 else 260 cp++; 261 if (strncmp(cp, "200", 3)) { 262 warnx("Error retrieving file: %s", cp); 263 goto cleanup_url_get; 264 } 265 266 /* 267 * Read the rest of the header. 268 */ 269 memset(buf, 0, sizeof(buf)); 270 c = '\0'; 271 for (cp = buf; cp < buf + sizeof(buf); ) { 272 if (read(s, cp, 1) != 1) 273 goto improper; 274 if (*cp == '\r') 275 continue; 276 if (*cp == '\n' && c == '\n') 277 break; 278 c = *cp; 279 cp++; 280 } 281 buf[sizeof(buf) - 1] = '\0'; /* sanity */ 282 283 /* 284 * Look for the "Content-length: " header. 285 */ 286 #define CONTENTLEN "Content-Length: " 287 for (cp = buf; *cp != '\0'; cp++) { 288 if (tolower(*cp) == 'c' && 289 strncasecmp(cp, CONTENTLEN, sizeof(CONTENTLEN) - 1) == 0) 290 break; 291 } 292 if (*cp != '\0') { 293 cp += sizeof(CONTENTLEN) - 1; 294 ep = strchr(cp, '\n'); 295 if (ep == NULL) 296 goto improper; 297 else 298 *ep = '\0'; 299 filesize = strtol(cp, &ep, 10); 300 if (filesize < 1 || *ep != '\0') 301 goto improper; 302 } else 303 filesize = -1; 304 305 /* Open the output file. */ 306 out = open(savefile, O_CREAT | O_WRONLY | O_TRUNC, 0666); 307 if (out < 0) { 308 warn("Can't open %s", savefile); 309 goto cleanup_url_get; 310 } 311 312 /* Trap signals */ 313 oldintr = NULL; 314 if (setjmp(httpabort)) { 315 if (oldintr) 316 (void)signal(SIGINT, oldintr); 317 goto cleanup_url_get; 318 } 319 oldintr = signal(SIGINT, aborthttp); 320 321 bytes = 0; 322 hashbytes = mark; 323 progressmeter(-1); 324 325 /* Finally, suck down the file. */ 326 i = 0; 327 while ((len = read(s, buf, sizeof(buf))) > 0) { 328 bytes += len; 329 for (cp = buf; len > 0; len -= i, cp += i) { 330 if ((i = write(out, cp, len)) == -1) { 331 warn("Writing %s", savefile); 332 goto cleanup_url_get; 333 } 334 else if (i == 0) 335 break; 336 } 337 if (hash && !progress) { 338 while (bytes >= hashbytes) { 339 (void)putchar('#'); 340 hashbytes += mark; 341 } 342 (void)fflush(stdout); 343 } 344 } 345 if (hash && !progress && bytes > 0) { 346 if (bytes < mark) 347 (void)putchar('#'); 348 (void)putchar('\n'); 349 (void)fflush(stdout); 350 } 351 if (len != 0) { 352 warn("Reading from socket"); 353 goto cleanup_url_get; 354 } 355 progressmeter(1); 356 if (verbose) 357 puts("Successfully retrieved file."); 358 (void)signal(SIGINT, oldintr); 359 360 close(s); 361 close(out); 362 if (proxy) 363 free(proxy); 364 free(line); 365 return (0); 366 367 noftpautologin: 368 warnx( 369 "Auto-login using ftp URLs isn't supported when using $ftp_proxy"); 370 goto cleanup_url_get; 371 372 improper: 373 warnx("Improper response from %s", host); 374 375 cleanup_url_get: 376 if (s != -1) 377 close(s); 378 if (proxy) 379 free(proxy); 380 free(line); 381 return (-1); 382 } 383 384 /* 385 * Abort a http retrieval 386 */ 387 void 388 aborthttp(notused) 389 int notused; 390 { 391 392 alarmtimer(0); 393 puts("\nhttp fetch aborted."); 394 (void)fflush(stdout); 395 longjmp(httpabort, 1); 396 } 397 398 /* 399 * Retrieve multiple files from the command line, transferring 400 * files of the form "host:path", "ftp://host/path" using the 401 * ftp protocol, and files of the form "http://host/path" using 402 * the http protocol. 403 * If path has a trailing "/", then return (-1); 404 * the path will be cd-ed into and the connection remains open, 405 * and the function will return -1 (to indicate the connection 406 * is alive). 407 * If an error occurs the return value will be the offset+1 in 408 * argv[] of the file that caused a problem (i.e, argv[x] 409 * returns x+1) 410 * Otherwise, 0 is returned if all files retrieved successfully. 411 */ 412 int 413 auto_fetch(argc, argv) 414 int argc; 415 char *argv[]; 416 { 417 static char lasthost[MAXHOSTNAMELEN]; 418 char *xargv[5]; 419 char *cp, *line, *host, *dir, *file, *portnum; 420 char *user, *pass; 421 char *ftpproxy, *httpproxy; 422 int rval, xargc; 423 volatile int argpos; 424 int dirhasglob, filehasglob; 425 char rempath[MAXPATHLEN]; 426 427 argpos = 0; 428 429 if (setjmp(toplevel)) { 430 if (connected) 431 disconnect(0, NULL); 432 return (argpos + 1); 433 } 434 (void)signal(SIGINT, (sig_t)intr); 435 (void)signal(SIGPIPE, (sig_t)lostpeer); 436 437 ftpproxy = getenv(FTP_PROXY); 438 httpproxy = getenv(HTTP_PROXY); 439 440 /* 441 * Loop through as long as there's files to fetch. 442 */ 443 for (rval = 0; (rval == 0) && (argpos < argc); free(line), argpos++) { 444 if (strchr(argv[argpos], ':') == NULL) 445 break; 446 host = dir = file = portnum = user = pass = NULL; 447 448 /* 449 * We muck with the string, so we make a copy. 450 */ 451 line = strdup(argv[argpos]); 452 if (line == NULL) 453 errx(1, "Can't allocate memory for auto-fetch."); 454 455 /* 456 * Try HTTP URL-style arguments first. 457 */ 458 if (strncasecmp(line, HTTP_URL, sizeof(HTTP_URL) - 1) == 0) { 459 if (url_get(line, httpproxy) == -1) 460 rval = argpos + 1; 461 continue; 462 } 463 464 /* 465 * Try FTP URL-style arguments next. If ftpproxy is 466 * set, use url_get() instead of standard ftp. 467 * Finally, try host:file. 468 */ 469 host = line; 470 if (strncasecmp(line, FTP_URL, sizeof(FTP_URL) - 1) == 0) { 471 if (ftpproxy) { 472 if (url_get(line, ftpproxy) == -1) 473 rval = argpos + 1; 474 continue; 475 } 476 host += sizeof(FTP_URL) - 1; 477 dir = strchr(host, '/'); 478 479 /* look for [user:pass@]host[:port] */ 480 pass = strpbrk(host, ":@/"); 481 if (pass == NULL || *pass == '/') { 482 pass = NULL; 483 goto parsed_url; 484 } 485 if (pass == host || *pass == '@') { 486 bad_ftp_url: 487 warnx("Invalid URL: %s", argv[argpos]); 488 rval = argpos + 1; 489 continue; 490 } 491 *pass++ = '\0'; 492 cp = strpbrk(pass, ":@/"); 493 if (cp == NULL || *cp == '/') { 494 portnum = pass; 495 pass = NULL; 496 goto parsed_url; 497 } 498 if (EMPTYSTRING(cp) || *cp == ':') 499 goto bad_ftp_url; 500 *cp++ = '\0'; 501 user = host; 502 if (EMPTYSTRING(user)) 503 goto bad_ftp_url; 504 host = cp; 505 portnum = strchr(host, ':'); 506 if (portnum != NULL) 507 *portnum++ = '\0'; 508 } else { /* classic style `host:file' */ 509 dir = strchr(host, ':'); 510 } 511 parsed_url: 512 if (EMPTYSTRING(host)) { 513 rval = argpos + 1; 514 continue; 515 } 516 517 /* 518 * If dir is NULL, the file wasn't specified 519 * (URL looked something like ftp://host) 520 */ 521 if (dir != NULL) 522 *dir++ = '\0'; 523 524 /* 525 * Extract the file and (if present) directory name. 526 */ 527 if (! EMPTYSTRING(dir)) { 528 cp = strrchr(dir, '/'); 529 if (cp != NULL) { 530 *cp++ = '\0'; 531 file = cp; 532 } else { 533 file = dir; 534 dir = NULL; 535 } 536 } 537 if (debug) 538 printf("user %s:%s host %s port %s dir %s file %s\n", 539 user, pass, host, portnum, dir, file); 540 541 /* 542 * Set up the connection if we don't have one. 543 */ 544 if (strcmp(host, lasthost) != 0) { 545 int oautologin; 546 547 (void)strcpy(lasthost, host); 548 if (connected) 549 disconnect(0, NULL); 550 xargv[0] = __progname; 551 xargv[1] = host; 552 xargv[2] = NULL; 553 xargc = 2; 554 if (! EMPTYSTRING(portnum)) { 555 xargv[2] = portnum; 556 xargv[3] = NULL; 557 xargc = 3; 558 } 559 oautologin = autologin; 560 if (user != NULL) 561 autologin = 0; 562 setpeer(xargc, xargv); 563 autologin = oautologin; 564 if ((connected == 0) 565 || ((connected == 1) && !login(host, user, pass)) ) { 566 warnx("Can't connect or login to host `%s'", 567 host); 568 rval = argpos + 1; 569 continue; 570 } 571 572 /* Always use binary transfers. */ 573 setbinary(0, NULL); 574 } 575 /* cd back to '/' */ 576 xargv[0] = "cd"; 577 xargv[1] = "/"; 578 xargv[2] = NULL; 579 cd(2, xargv); 580 if (! dirchange) { 581 rval = argpos + 1; 582 continue; 583 } 584 585 dirhasglob = filehasglob = 0; 586 if (doglob) { 587 if (! EMPTYSTRING(dir) && 588 strpbrk(dir, "*?[]{}") != NULL) 589 dirhasglob = 1; 590 if (! EMPTYSTRING(file) && 591 strpbrk(file, "*?[]{}") != NULL) 592 filehasglob = 1; 593 } 594 595 /* Change directories, if necessary. */ 596 if (! EMPTYSTRING(dir) && !dirhasglob) { 597 xargv[0] = "cd"; 598 xargv[1] = dir; 599 xargv[2] = NULL; 600 cd(2, xargv); 601 if (! dirchange) { 602 rval = argpos + 1; 603 continue; 604 } 605 } 606 607 if (EMPTYSTRING(file)) { 608 rval = -1; 609 continue; 610 } 611 612 if (!verbose) 613 printf("Retrieving %s/%s\n", dir ? dir : "", file); 614 615 if (dirhasglob) { 616 snprintf(rempath, sizeof(rempath), "%s/%s", dir, file); 617 file = rempath; 618 } 619 620 /* Fetch the file(s). */ 621 xargv[0] = "get"; 622 xargv[1] = file; 623 xargv[2] = NULL; 624 if (dirhasglob || filehasglob) { 625 int ointeractive; 626 627 ointeractive = interactive; 628 interactive = 0; 629 xargv[0] = "mget"; 630 mget(2, xargv); 631 interactive = ointeractive; 632 } else 633 get(2, xargv); 634 635 if ((code / 100) != COMPLETE) 636 rval = argpos + 1; 637 } 638 if (connected && rval != -1) 639 disconnect(0, NULL); 640 return (rval); 641 } 642