1 /* $NetBSD: fetch.c,v 1.17 1997/11/01 14:36:55 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.17 1997/11/01 14:36:55 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 len = snprintf(buf, sizeof(buf), "GET %s%s HTTP/1.0\r\n\r\n", 239 proxy ? "" : "/", path); 240 if (write(s, buf, len) < len) { 241 warn("Writing HTTP request"); 242 goto cleanup_url_get; 243 } 244 memset(buf, 0, sizeof(buf)); 245 for (cp = buf; cp < buf + sizeof(buf); ) { 246 if (read(s, cp, 1) != 1) 247 goto improper; 248 if (*cp == '\r') 249 continue; 250 if (*cp == '\n') 251 break; 252 cp++; 253 } 254 buf[sizeof(buf) - 1] = '\0'; /* sanity */ 255 cp = strchr(buf, ' '); 256 if (cp == NULL) 257 goto improper; 258 else 259 cp++; 260 if (strncmp(cp, "200", 3)) { 261 warnx("Error retrieving file: %s", cp); 262 goto cleanup_url_get; 263 } 264 265 /* 266 * Read the rest of the header. 267 */ 268 memset(buf, 0, sizeof(buf)); 269 c = '\0'; 270 for (cp = buf; cp < buf + sizeof(buf); ) { 271 if (read(s, cp, 1) != 1) 272 goto improper; 273 if (*cp == '\r') 274 continue; 275 if (*cp == '\n' && c == '\n') 276 break; 277 c = *cp; 278 cp++; 279 } 280 buf[sizeof(buf) - 1] = '\0'; /* sanity */ 281 282 /* 283 * Look for the "Content-length: " header. 284 */ 285 #define CONTENTLEN "Content-Length: " 286 for (cp = buf; *cp != '\0'; cp++) { 287 if (tolower(*cp) == 'c' && 288 strncasecmp(cp, CONTENTLEN, sizeof(CONTENTLEN) - 1) == 0) 289 break; 290 } 291 if (*cp != '\0') { 292 cp += sizeof(CONTENTLEN) - 1; 293 ep = strchr(cp, '\n'); 294 if (ep == NULL) 295 goto improper; 296 else 297 *ep = '\0'; 298 filesize = strtol(cp, &ep, 10); 299 if (filesize < 1 || *ep != '\0') 300 goto improper; 301 } else 302 filesize = -1; 303 304 /* Open the output file. */ 305 out = open(savefile, O_CREAT | O_WRONLY | O_TRUNC, 0666); 306 if (out < 0) { 307 warn("Can't open %s", savefile); 308 goto cleanup_url_get; 309 } 310 311 /* Trap signals */ 312 oldintr = NULL; 313 if (setjmp(httpabort)) { 314 if (oldintr) 315 (void)signal(SIGINT, oldintr); 316 goto cleanup_url_get; 317 } 318 oldintr = signal(SIGINT, aborthttp); 319 320 bytes = 0; 321 hashbytes = mark; 322 progressmeter(-1); 323 324 /* Finally, suck down the file. */ 325 i = 0; 326 while ((len = read(s, buf, sizeof(buf))) > 0) { 327 bytes += len; 328 for (cp = buf; len > 0; len -= i, cp += i) { 329 if ((i = write(out, cp, len)) == -1) { 330 warn("Writing %s", savefile); 331 goto cleanup_url_get; 332 } 333 else if (i == 0) 334 break; 335 } 336 if (hash && !progress) { 337 while (bytes >= hashbytes) { 338 (void)putchar('#'); 339 hashbytes += mark; 340 } 341 (void)fflush(stdout); 342 } 343 } 344 if (hash && !progress && bytes > 0) { 345 if (bytes < mark) 346 (void)putchar('#'); 347 (void)putchar('\n'); 348 (void)fflush(stdout); 349 } 350 if (len != 0) { 351 warn("Reading from socket"); 352 goto cleanup_url_get; 353 } 354 progressmeter(1); 355 if (verbose) 356 puts("Successfully retrieved file."); 357 (void)signal(SIGINT, oldintr); 358 359 close(s); 360 close(out); 361 if (proxy) 362 free(proxy); 363 free(line); 364 return (0); 365 366 noftpautologin: 367 warnx( 368 "Auto-login using ftp URLs isn't supported when using $ftp_proxy"); 369 goto cleanup_url_get; 370 371 improper: 372 warnx("Improper response from %s", host); 373 374 cleanup_url_get: 375 if (s != -1) 376 close(s); 377 if (proxy) 378 free(proxy); 379 free(line); 380 return (-1); 381 } 382 383 /* 384 * Abort a http retrieval 385 */ 386 void 387 aborthttp(notused) 388 int notused; 389 { 390 391 alarmtimer(0); 392 puts("\nhttp fetch aborted."); 393 (void)fflush(stdout); 394 longjmp(httpabort, 1); 395 } 396 397 /* 398 * Retrieve multiple files from the command line, transferring 399 * files of the form "host:path", "ftp://host/path" using the 400 * ftp protocol, and files of the form "http://host/path" using 401 * the http protocol. 402 * If path has a trailing "/", then return (-1); 403 * the path will be cd-ed into and the connection remains open, 404 * and the function will return -1 (to indicate the connection 405 * is alive). 406 * If an error occurs the return value will be the offset+1 in 407 * argv[] of the file that caused a problem (i.e, argv[x] 408 * returns x+1) 409 * Otherwise, 0 is returned if all files retrieved successfully. 410 */ 411 int 412 auto_fetch(argc, argv) 413 int argc; 414 char *argv[]; 415 { 416 static char lasthost[MAXHOSTNAMELEN]; 417 char *xargv[5]; 418 char *cp, *line, *host, *dir, *file, *portnum; 419 char *user, *pass; 420 char *ftpproxy, *httpproxy; 421 int rval, xargc; 422 volatile int argpos; 423 int dirhasglob, filehasglob; 424 char rempath[MAXPATHLEN]; 425 426 argpos = 0; 427 428 if (setjmp(toplevel)) { 429 if (connected) 430 disconnect(0, NULL); 431 return (argpos + 1); 432 } 433 (void)signal(SIGINT, (sig_t)intr); 434 (void)signal(SIGPIPE, (sig_t)lostpeer); 435 436 ftpproxy = getenv(FTP_PROXY); 437 httpproxy = getenv(HTTP_PROXY); 438 439 /* 440 * Loop through as long as there's files to fetch. 441 */ 442 for (rval = 0; (rval == 0) && (argpos < argc); free(line), argpos++) { 443 if (strchr(argv[argpos], ':') == NULL) 444 break; 445 host = dir = file = portnum = user = pass = NULL; 446 447 /* 448 * We muck with the string, so we make a copy. 449 */ 450 line = strdup(argv[argpos]); 451 if (line == NULL) 452 errx(1, "Can't allocate memory for auto-fetch."); 453 454 /* 455 * Try HTTP URL-style arguments first. 456 */ 457 if (strncasecmp(line, HTTP_URL, sizeof(HTTP_URL) - 1) == 0) { 458 if (url_get(line, httpproxy) == -1) 459 rval = argpos + 1; 460 continue; 461 } 462 463 /* 464 * Try FTP URL-style arguments next. If ftpproxy is 465 * set, use url_get() instead of standard ftp. 466 * Finally, try host:file. 467 */ 468 host = line; 469 if (strncasecmp(line, FTP_URL, sizeof(FTP_URL) - 1) == 0) { 470 if (ftpproxy) { 471 if (url_get(line, ftpproxy) == -1) 472 rval = argpos + 1; 473 continue; 474 } 475 host += sizeof(FTP_URL) - 1; 476 dir = strchr(host, '/'); 477 478 /* look for [user:pass@]host[:port] */ 479 pass = strpbrk(host, ":@/"); 480 if (pass == NULL || *pass == '/') { 481 pass = NULL; 482 goto parsed_url; 483 } 484 if (pass == host || *pass == '@') { 485 bad_ftp_url: 486 warnx("Invalid URL: %s", argv[argpos]); 487 rval = argpos + 1; 488 continue; 489 } 490 *pass++ = '\0'; 491 cp = strpbrk(pass, ":@/"); 492 if (cp == NULL || *cp == '/') { 493 portnum = pass; 494 pass = NULL; 495 goto parsed_url; 496 } 497 if (EMPTYSTRING(cp) || *cp == ':') 498 goto bad_ftp_url; 499 *cp++ = '\0'; 500 user = host; 501 if (EMPTYSTRING(user)) 502 goto bad_ftp_url; 503 host = cp; 504 portnum = strchr(host, ':'); 505 if (portnum != NULL) 506 *portnum++ = '\0'; 507 } else { /* classic style `host:file' */ 508 dir = strchr(host, ':'); 509 } 510 parsed_url: 511 if (EMPTYSTRING(host)) { 512 rval = argpos + 1; 513 continue; 514 } 515 516 /* 517 * If dir is NULL, the file wasn't specified 518 * (URL looked something like ftp://host) 519 */ 520 if (dir != NULL) 521 *dir++ = '\0'; 522 523 /* 524 * Extract the file and (if present) directory name. 525 */ 526 if (! EMPTYSTRING(dir)) { 527 cp = strrchr(dir, '/'); 528 if (cp != NULL) { 529 *cp++ = '\0'; 530 file = cp; 531 } else { 532 file = dir; 533 dir = NULL; 534 } 535 } 536 if (debug) 537 printf("user %s:%s host %s port %s dir %s file %s\n", 538 user, pass, host, portnum, dir, file); 539 540 /* 541 * Set up the connection if we don't have one. 542 */ 543 if (strcmp(host, lasthost) != 0) { 544 int oautologin; 545 546 (void)strcpy(lasthost, host); 547 if (connected) 548 disconnect(0, NULL); 549 xargv[0] = __progname; 550 xargv[1] = host; 551 xargv[2] = NULL; 552 xargc = 2; 553 if (! EMPTYSTRING(portnum)) { 554 xargv[2] = portnum; 555 xargv[3] = NULL; 556 xargc = 3; 557 } 558 oautologin = autologin; 559 if (user != NULL) 560 autologin = 0; 561 setpeer(xargc, xargv); 562 autologin = oautologin; 563 if ((connected == 0) 564 || ((connected == 1) && !login(host, user, pass)) ) { 565 warnx("Can't connect or login to host `%s'", 566 host); 567 rval = argpos + 1; 568 continue; 569 } 570 571 /* Always use binary transfers. */ 572 setbinary(0, NULL); 573 } 574 /* cd back to '/' */ 575 xargv[0] = "cd"; 576 xargv[1] = "/"; 577 xargv[2] = NULL; 578 cd(2, xargv); 579 if (! dirchange) { 580 rval = argpos + 1; 581 continue; 582 } 583 584 dirhasglob = filehasglob = 0; 585 if (doglob) { 586 if (! EMPTYSTRING(dir) && 587 strpbrk(dir, "*?[]{}") != NULL) 588 dirhasglob = 1; 589 if (! EMPTYSTRING(file) && 590 strpbrk(file, "*?[]{}") != NULL) 591 filehasglob = 1; 592 } 593 594 /* Change directories, if necessary. */ 595 if (! EMPTYSTRING(dir) && !dirhasglob) { 596 xargv[0] = "cd"; 597 xargv[1] = dir; 598 xargv[2] = NULL; 599 cd(2, xargv); 600 if (! dirchange) { 601 rval = argpos + 1; 602 continue; 603 } 604 } 605 606 if (EMPTYSTRING(file)) { 607 rval = -1; 608 continue; 609 } 610 611 if (!verbose) 612 printf("Retrieving %s/%s\n", dir ? dir : "", file); 613 614 if (dirhasglob) { 615 snprintf(rempath, sizeof(rempath), "%s/%s", dir, file); 616 file = rempath; 617 } 618 619 /* Fetch the file(s). */ 620 xargv[0] = "get"; 621 xargv[1] = file; 622 xargv[2] = NULL; 623 if (dirhasglob || filehasglob) { 624 int ointeractive; 625 626 ointeractive = interactive; 627 interactive = 0; 628 xargv[0] = "mget"; 629 mget(2, xargv); 630 interactive = ointeractive; 631 } else 632 get(2, xargv); 633 634 if ((code / 100) != COMPLETE) 635 rval = argpos + 1; 636 } 637 if (connected && rval != -1) 638 disconnect(0, NULL); 639 return (rval); 640 } 641