1 #include <u.h> 2 #include <libc.h> 3 #include <ctype.h> 4 #include <bio.h> 5 #include <ip.h> 6 #include <libsec.h> 7 #include <auth.h> 8 9 typedef struct URL URL; 10 struct URL 11 { 12 int method; 13 char *host; 14 char *port; 15 char *page; 16 char *etag; 17 char *redirect; 18 char *postbody; 19 char *cred; 20 char *rhead; 21 long mtime; 22 }; 23 24 typedef struct Range Range; 25 struct Range 26 { 27 long start; /* only 2 gig supported, tdb */ 28 long end; 29 }; 30 31 typedef struct Out Out; 32 struct Out 33 { 34 int fd; 35 int offset; /* notional current offset in output */ 36 int written; /* number of bytes successfully transferred to output */ 37 DigestState *curr; /* digest state up to offset (if known) */ 38 DigestState *hiwat; /* digest state of all bytes written */ 39 }; 40 41 enum 42 { 43 Other, 44 Http, 45 Https, 46 Ftp, 47 }; 48 49 enum 50 { 51 Eof = 0, 52 Error = -1, 53 Server = -2, 54 Changed = -3, 55 }; 56 57 int debug; 58 char *ofile; 59 60 61 int doftp(URL*, URL*, Range*, Out*, long); 62 int dohttp(URL*, URL*, Range*, Out*, long); 63 int crackurl(URL*, char*); 64 Range* crackrange(char*); 65 int getheader(int, char*, int); 66 int httpheaders(int, int, URL*, Range*); 67 int httprcode(int); 68 int cistrncmp(char*, char*, int); 69 int cistrcmp(char*, char*); 70 void initibuf(void); 71 int readline(int, char*, int); 72 int readibuf(int, char*, int); 73 int dfprint(int, char*, ...); 74 void unreadline(char*); 75 int output(Out*, char*, int); 76 void setoffset(Out*, int); 77 78 int verbose; 79 char *net; 80 char tcpdir[NETPATHLEN]; 81 int headerprint; 82 83 struct { 84 char *name; 85 int (*f)(URL*, URL*, Range*, Out*, long); 86 } method[] = { 87 [Http] { "http", dohttp }, 88 [Https] { "https", dohttp }, 89 [Ftp] { "ftp", doftp }, 90 [Other] { "_______", nil }, 91 }; 92 93 void 94 usage(void) 95 { 96 fprint(2, "usage: %s [-dhv] [-o outfile] [-p body] [-x netmtpt] [-r header] url\n", argv0); 97 exits("usage"); 98 } 99 100 void 101 main(int argc, char **argv) 102 { 103 URL u; 104 Range r; 105 int errs, n; 106 ulong mtime; 107 Dir *d; 108 char postbody[4096], *p, *e, *t, *hpx; 109 URL px; // Proxy 110 Out out; 111 112 ofile = nil; 113 p = postbody; 114 e = p + sizeof(postbody); 115 r.start = 0; 116 r.end = -1; 117 mtime = 0; 118 memset(&u, 0, sizeof(u)); 119 memset(&px, 0, sizeof(px)); 120 hpx = getenv("httpproxy"); 121 122 ARGBEGIN { 123 case 'o': 124 ofile = EARGF(usage()); 125 break; 126 case 'd': 127 debug = 1; 128 break; 129 case 'h': 130 headerprint = 1; 131 break; 132 case 'v': 133 verbose = 1; 134 break; 135 case 'x': 136 net = EARGF(usage()); 137 break; 138 case 'r': 139 u.rhead = EARGF(usage()); 140 break; 141 case 'p': 142 t = EARGF(usage()); 143 if(p != postbody) 144 p = seprint(p, e, "&%s", t); 145 else 146 p = seprint(p, e, "%s", t); 147 u.postbody = postbody; 148 149 break; 150 default: 151 usage(); 152 } ARGEND; 153 154 if(net != nil){ 155 if(strlen(net) > sizeof(tcpdir)-5) 156 sysfatal("network mount point too long"); 157 snprint(tcpdir, sizeof(tcpdir), "%s/tcp", net); 158 } else 159 snprint(tcpdir, sizeof(tcpdir), "tcp"); 160 161 if(argc != 1) 162 usage(); 163 164 165 out.fd = 1; 166 out.written = 0; 167 out.offset = 0; 168 out.curr = nil; 169 out.hiwat = nil; 170 if(ofile != nil){ 171 d = dirstat(ofile); 172 if(d == nil){ 173 out.fd = create(ofile, OWRITE, 0664); 174 if(out.fd < 0) 175 sysfatal("creating %s: %r", ofile); 176 } else { 177 out.fd = open(ofile, OWRITE); 178 if(out.fd < 0) 179 sysfatal("can't open %s: %r", ofile); 180 r.start = d->length; 181 mtime = d->mtime; 182 free(d); 183 } 184 } 185 186 errs = 0; 187 188 if(crackurl(&u, argv[0]) < 0) 189 sysfatal("%r"); 190 if(hpx && crackurl(&px, hpx) < 0) 191 sysfatal("%r"); 192 193 for(;;){ 194 setoffset(&out, 0); 195 /* transfer data */ 196 werrstr(""); 197 n = (*method[u.method].f)(&u, &px, &r, &out, mtime); 198 199 switch(n){ 200 case Eof: 201 exits(0); 202 break; 203 case Error: 204 if(errs++ < 10) 205 continue; 206 sysfatal("too many errors with no progress %r"); 207 break; 208 case Server: 209 sysfatal("server returned: %r"); 210 break; 211 } 212 213 /* forward progress */ 214 errs = 0; 215 r.start += n; 216 if(r.start >= r.end) 217 break; 218 } 219 220 exits(0); 221 } 222 223 int 224 crackurl(URL *u, char *s) 225 { 226 char *p; 227 int i; 228 229 if(u->page != nil){ 230 free(u->page); 231 u->page = nil; 232 } 233 234 /* get type */ 235 for(p = s; *p; p++){ 236 if(*p == '/'){ 237 p = s; 238 if(u->method == Other){ 239 werrstr("missing method"); 240 return -1; 241 } 242 if(u->host == nil){ 243 werrstr("missing host"); 244 return -1; 245 } 246 u->page = strdup(p); 247 return 0; 248 } 249 if(*p == ':' && *(p+1)=='/' && *(p+2)=='/'){ 250 *p = 0; 251 p += 3; 252 for(i = 0; i < nelem(method); i++){ 253 if(cistrcmp(s, method[i].name) == 0){ 254 u->method = i; 255 break; 256 } 257 } 258 break; 259 } 260 } 261 262 if(u->method == Other){ 263 werrstr("unsupported URL type %s", s); 264 return -1; 265 } 266 267 /* get system */ 268 free(u->host); 269 s = p; 270 p = strchr(s, '/'); 271 if(p == nil){ 272 u->host = strdup(s); 273 u->page = strdup("/"); 274 } else { 275 u->page = strdup(p); 276 *p = 0; 277 u->host = strdup(s); 278 *p = '/'; 279 } 280 281 if(p = strchr(u->host, ':')) { 282 *p++ = 0; 283 u->port = p; 284 } else 285 u->port = method[u->method].name; 286 287 if(*(u->host) == 0){ 288 werrstr("bad url, null host"); 289 return -1; 290 } 291 292 return 0; 293 } 294 295 char *day[] = { 296 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" 297 }; 298 299 char *month[] = { 300 "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" 301 }; 302 303 struct 304 { 305 int fd; 306 long mtime; 307 } note; 308 309 void 310 catch(void*, char*) 311 { 312 Dir d; 313 314 nulldir(&d); 315 d.mtime = note.mtime; 316 if(dirfwstat(note.fd, &d) < 0) 317 sysfatal("catch: can't dirfwstat: %r"); 318 noted(NDFLT); 319 } 320 321 int 322 dohttp(URL *u, URL *px, Range *r, Out *out, long mtime) 323 { 324 int fd, cfd; 325 int redirect, auth, loop; 326 int n, rv, code; 327 long tot, vtime; 328 Tm *tm; 329 char buf[1024]; 330 char err[ERRMAX]; 331 332 333 /* always move back to a previous 512 byte bound because some 334 * servers can't seem to deal with requests that start at the 335 * end of the file 336 */ 337 if(r->start) 338 r->start = ((r->start-1)/512)*512; 339 340 /* loop for redirects, requires reading both response code and headers */ 341 fd = -1; 342 for(loop = 0; loop < 32; loop++){ 343 if(px->host == nil){ 344 fd = dial(netmkaddr(u->host, tcpdir, u->port), 0, 0, 0); 345 } else { 346 fd = dial(netmkaddr(px->host, tcpdir, px->port), 0, 0, 0); 347 } 348 if(fd < 0) 349 return Error; 350 351 if(u->method == Https){ 352 int tfd; 353 TLSconn conn; 354 355 memset(&conn, 0, sizeof conn); 356 tfd = tlsClient(fd, &conn); 357 if(tfd < 0){ 358 fprint(2, "tlsClient: %r\n"); 359 close(fd); 360 return Error; 361 } 362 /* BUG: check cert here? */ 363 if(conn.cert) 364 free(conn.cert); 365 close(fd); 366 fd = tfd; 367 } 368 369 /* write request, use range if not start of file */ 370 if(u->postbody == nil){ 371 if(px->host == nil){ 372 dfprint(fd, "GET %s HTTP/1.0\r\n" 373 "Host: %s\r\n" 374 "User-agent: Plan9/hget\r\n" 375 "Cache-Control: no-cache\r\n" 376 "Pragma: no-cache\r\n", 377 u->page, u->host); 378 } else { 379 dfprint(fd, "GET http://%s%s HTTP/1.0\r\n" 380 "Host: %s\r\n" 381 "User-agent: Plan9/hget\r\n" 382 "Cache-Control: no-cache\r\n" 383 "Pragma: no-cache\r\n", 384 u->host, u->page, u->host); 385 } 386 } else { 387 dfprint(fd, "POST %s HTTP/1.0\r\n" 388 "Host: %s\r\n" 389 "Content-type: application/x-www-form-urlencoded\r\n" 390 "Content-length: %d\r\n" 391 "User-agent: Plan9/hget\r\n", 392 u->page, u->host, strlen(u->postbody)); 393 } 394 if(u->cred) 395 dfprint(fd, "Authorization: Basic %s\r\n", u->cred); 396 if(u->rhead) 397 dfprint(fd, "%s\r\n", u->rhead); 398 if(r->start != 0){ 399 dfprint(fd, "Range: bytes=%d-\n", r->start); 400 if(u->etag != nil){ 401 dfprint(fd, "If-range: %s\n", u->etag); 402 } else { 403 tm = gmtime(mtime); 404 dfprint(fd, "If-range: %s, %d %s %d %2d:%2.2d:%2.2d GMT\n", 405 day[tm->wday], tm->mday, month[tm->mon], 406 tm->year+1900, tm->hour, tm->min, tm->sec); 407 } 408 } 409 if((cfd = open("/mnt/webcookies/http", ORDWR)) >= 0){ 410 if(fprint(cfd, "http://%s%s", u->host, u->page) > 0){ 411 while((n = read(cfd, buf, sizeof buf)) > 0){ 412 if(debug) 413 write(2, buf, n); 414 write(fd, buf, n); 415 } 416 }else{ 417 close(cfd); 418 cfd = -1; 419 } 420 } 421 422 dfprint(fd, "\r\n", u->host); 423 if(u->postbody) 424 dfprint(fd, "%s", u->postbody); 425 426 auth = 0; 427 redirect = 0; 428 initibuf(); 429 code = httprcode(fd); 430 switch(code){ 431 case Error: /* connection timed out */ 432 case Eof: 433 close(fd); 434 close(cfd); 435 return code; 436 437 case 200: /* OK */ 438 case 201: /* Created */ 439 case 202: /* Accepted */ 440 if(ofile == nil && r->start != 0) 441 sysfatal("page changed underfoot"); 442 break; 443 444 case 204: /* No Content */ 445 sysfatal("No Content"); 446 447 case 206: /* Partial Content */ 448 setoffset(out, r->start); 449 break; 450 451 case 301: /* Moved Permanently */ 452 case 302: /* Moved Temporarily (actually Found) */ 453 case 303: /* See Other */ 454 case 307: /* Temporary Redirect (HTTP/1.1) */ 455 redirect = 1; 456 u->postbody = nil; 457 break; 458 459 case 304: /* Not Modified */ 460 break; 461 462 case 400: /* Bad Request */ 463 sysfatal("Bad Request"); 464 465 case 401: /* Unauthorized */ 466 if (auth) 467 sysfatal("Authentication failed"); 468 auth = 1; 469 break; 470 471 case 402: /* ??? */ 472 sysfatal("Unauthorized"); 473 474 case 403: /* Forbidden */ 475 sysfatal("Forbidden by server"); 476 477 case 404: /* Not Found */ 478 sysfatal("Not found on server"); 479 480 case 407: /* Proxy Authentication */ 481 sysfatal("Proxy authentication required"); 482 483 case 500: /* Internal server error */ 484 sysfatal("Server choked"); 485 486 case 501: /* Not implemented */ 487 sysfatal("Server can't do it!"); 488 489 case 502: /* Bad gateway */ 490 sysfatal("Bad gateway"); 491 492 case 503: /* Service unavailable */ 493 sysfatal("Service unavailable"); 494 495 default: 496 sysfatal("Unknown response code %d", code); 497 } 498 499 if(u->redirect != nil){ 500 free(u->redirect); 501 u->redirect = nil; 502 } 503 504 rv = httpheaders(fd, cfd, u, r); 505 close(cfd); 506 if(rv != 0){ 507 close(fd); 508 return rv; 509 } 510 511 if(!redirect && !auth) 512 break; 513 514 if (redirect){ 515 if(u->redirect == nil) 516 sysfatal("redirect: no URL"); 517 if(crackurl(u, u->redirect) < 0) 518 sysfatal("redirect: %r"); 519 } 520 } 521 522 /* transfer whatever you get */ 523 if(ofile != nil && u->mtime != 0){ 524 note.fd = out->fd; 525 note.mtime = u->mtime; 526 notify(catch); 527 } 528 529 tot = 0; 530 vtime = 0; 531 for(;;){ 532 n = readibuf(fd, buf, sizeof(buf)); 533 if(n <= 0) 534 break; 535 if(output(out, buf, n) != n) 536 break; 537 tot += n; 538 if(verbose && (vtime != time(0) || r->start == r->end)) { 539 vtime = time(0); 540 fprint(2, "%ld %ld\n", r->start+tot, r->end); 541 } 542 } 543 notify(nil); 544 close(fd); 545 546 if(ofile != nil && u->mtime != 0){ 547 Dir d; 548 549 rerrstr(err, sizeof err); 550 nulldir(&d); 551 d.mtime = u->mtime; 552 if(dirfwstat(out->fd, &d) < 0) 553 fprint(2, "couldn't set mtime: %r\n"); 554 errstr(err, sizeof err); 555 } 556 557 return tot; 558 } 559 560 /* get the http response code */ 561 int 562 httprcode(int fd) 563 { 564 int n; 565 char *p; 566 char buf[256]; 567 568 n = readline(fd, buf, sizeof(buf)-1); 569 if(n <= 0) 570 return n; 571 if(debug) 572 fprint(2, "%d <- %s\n", fd, buf); 573 p = strchr(buf, ' '); 574 if(strncmp(buf, "HTTP/", 5) != 0 || p == nil){ 575 werrstr("bad response from server"); 576 return -1; 577 } 578 buf[n] = 0; 579 return atoi(p+1); 580 } 581 582 /* read in and crack the http headers, update u and r */ 583 void hhetag(char*, URL*, Range*); 584 void hhmtime(char*, URL*, Range*); 585 void hhclen(char*, URL*, Range*); 586 void hhcrange(char*, URL*, Range*); 587 void hhuri(char*, URL*, Range*); 588 void hhlocation(char*, URL*, Range*); 589 void hhauth(char*, URL*, Range*); 590 591 struct { 592 char *name; 593 void (*f)(char*, URL*, Range*); 594 } headers[] = { 595 { "etag:", hhetag }, 596 { "last-modified:", hhmtime }, 597 { "content-length:", hhclen }, 598 { "content-range:", hhcrange }, 599 { "uri:", hhuri }, 600 { "location:", hhlocation }, 601 { "WWW-Authenticate:", hhauth }, 602 }; 603 int 604 httpheaders(int fd, int cfd, URL *u, Range *r) 605 { 606 char buf[2048]; 607 char *p; 608 int i, n; 609 610 for(;;){ 611 n = getheader(fd, buf, sizeof(buf)); 612 if(n <= 0) 613 break; 614 if(cfd >= 0) 615 fprint(cfd, "%s\n", buf); 616 for(i = 0; i < nelem(headers); i++){ 617 n = strlen(headers[i].name); 618 if(cistrncmp(buf, headers[i].name, n) == 0){ 619 /* skip field name and leading white */ 620 p = buf + n; 621 while(*p == ' ' || *p == '\t') 622 p++; 623 624 (*headers[i].f)(p, u, r); 625 break; 626 } 627 } 628 } 629 return n; 630 } 631 632 /* 633 * read a single mime header, collect continuations. 634 * 635 * this routine assumes that there is a blank line twixt 636 * the header and the message body, otherwise bytes will 637 * be lost. 638 */ 639 int 640 getheader(int fd, char *buf, int n) 641 { 642 char *p, *e; 643 int i; 644 645 n--; 646 p = buf; 647 for(e = p + n; ; p += i){ 648 i = readline(fd, p, e-p); 649 if(i < 0) 650 return i; 651 652 if(p == buf){ 653 /* first line */ 654 if(strchr(buf, ':') == nil) 655 break; /* end of headers */ 656 } else { 657 /* continuation line */ 658 if(*p != ' ' && *p != '\t'){ 659 unreadline(p); 660 *p = 0; 661 break; /* end of this header */ 662 } 663 } 664 } 665 if(headerprint) 666 print("%s\n", buf); 667 668 if(debug) 669 fprint(2, "%d <- %s\n", fd, buf); 670 return p-buf; 671 } 672 673 void 674 hhetag(char *p, URL *u, Range*) 675 { 676 if(u->etag != nil){ 677 if(strcmp(u->etag, p) != 0) 678 sysfatal("file changed underfoot"); 679 } else 680 u->etag = strdup(p); 681 } 682 683 char* monthchars = "janfebmaraprmayjunjulaugsepoctnovdec"; 684 685 void 686 hhmtime(char *p, URL *u, Range*) 687 { 688 char *month, *day, *yr, *hms; 689 char *fields[6]; 690 Tm tm, now; 691 int i; 692 693 i = getfields(p, fields, 6, 1, " \t"); 694 if(i < 5) 695 return; 696 697 day = fields[1]; 698 month = fields[2]; 699 yr = fields[3]; 700 hms = fields[4]; 701 702 /* default time */ 703 now = *gmtime(time(0)); 704 tm = now; 705 tm.yday = 0; 706 707 /* convert ascii month to a number twixt 1 and 12 */ 708 if(*month >= '0' && *month <= '9'){ 709 tm.mon = atoi(month) - 1; 710 if(tm.mon < 0 || tm.mon > 11) 711 tm.mon = 5; 712 } else { 713 for(p = month; *p; p++) 714 *p = tolower(*p); 715 for(i = 0; i < 12; i++) 716 if(strncmp(&monthchars[i*3], month, 3) == 0){ 717 tm.mon = i; 718 break; 719 } 720 } 721 722 tm.mday = atoi(day); 723 724 if(hms) { 725 tm.hour = strtoul(hms, &p, 10); 726 if(*p == ':') { 727 p++; 728 tm.min = strtoul(p, &p, 10); 729 if(*p == ':') { 730 p++; 731 tm.sec = strtoul(p, &p, 10); 732 } 733 } 734 if(tolower(*p) == 'p') 735 tm.hour += 12; 736 } 737 738 if(yr) { 739 tm.year = atoi(yr); 740 if(tm.year >= 1900) 741 tm.year -= 1900; 742 } else { 743 if(tm.mon > now.mon || (tm.mon == now.mon && tm.mday > now.mday+1)) 744 tm.year--; 745 } 746 747 strcpy(tm.zone, "GMT"); 748 /* convert to epoch seconds */ 749 u->mtime = tm2sec(&tm); 750 } 751 752 void 753 hhclen(char *p, URL*, Range *r) 754 { 755 r->end = atoi(p); 756 } 757 758 void 759 hhcrange(char *p, URL*, Range *r) 760 { 761 char *x; 762 vlong l; 763 764 l = 0; 765 x = strchr(p, '/'); 766 if(x) 767 l = atoll(x+1); 768 if(l == 0) { 769 x = strchr(p, '-'); 770 if(x) 771 l = atoll(x+1); 772 } 773 if(l) 774 r->end = l; 775 } 776 777 void 778 hhuri(char *p, URL *u, Range*) 779 { 780 if(*p != '<') 781 return; 782 u->redirect = strdup(p+1); 783 p = strchr(u->redirect, '>'); 784 if(p != nil) 785 *p = 0; 786 } 787 788 void 789 hhlocation(char *p, URL *u, Range*) 790 { 791 u->redirect = strdup(p); 792 } 793 794 void 795 hhauth(char *p, URL *u, Range*) 796 { 797 char *f[4]; 798 UserPasswd *up; 799 char *s, cred[64]; 800 801 if (cistrncmp(p, "basic ", 6) != 0) 802 sysfatal("only Basic authentication supported"); 803 804 if (gettokens(p, f, nelem(f), "\"") < 2) 805 sysfatal("garbled auth data"); 806 807 if ((up = auth_getuserpasswd(auth_getkey, "proto=pass service=http server=%q realm=%q", 808 u->host, f[1])) == nil) 809 sysfatal("cannot authenticate"); 810 811 s = smprint("%s:%s", up->user, up->passwd); 812 if(enc64(cred, sizeof(cred), (uchar *)s, strlen(s)) == -1) 813 sysfatal("enc64"); 814 free(s); 815 816 assert(u->cred = strdup(cred)); 817 } 818 819 enum 820 { 821 /* ftp return codes */ 822 Extra= 1, 823 Success= 2, 824 Incomplete= 3, 825 TempFail= 4, 826 PermFail= 5, 827 828 Nnetdir= 64, /* max length of network directory paths */ 829 Ndialstr= 64, /* max length of dial strings */ 830 }; 831 832 int ftpcmd(int, char*, ...); 833 int ftprcode(int, char*, int); 834 int hello(int); 835 int logon(int); 836 int xfertype(int, char*); 837 int passive(int, URL*); 838 int active(int, URL*); 839 int ftpxfer(int, Out*, Range*); 840 int terminateftp(int, int); 841 int getaddrport(char*, uchar*, uchar*); 842 int ftprestart(int, Out*, URL*, Range*, long); 843 844 int 845 doftp(URL *u, URL *px, Range *r, Out *out, long mtime) 846 { 847 int pid, ctl, data, rv; 848 Waitmsg *w; 849 char msg[64]; 850 char conndir[NETPATHLEN]; 851 char *p; 852 853 /* untested, proxy doesn't work with ftp (I think) */ 854 if(px->host == nil){ 855 ctl = dial(netmkaddr(u->host, tcpdir, u->port), 0, conndir, 0); 856 } else { 857 ctl = dial(netmkaddr(px->host, tcpdir, px->port), 0, conndir, 0); 858 } 859 860 if(ctl < 0) 861 return Error; 862 if(net == nil){ 863 p = strrchr(conndir, '/'); 864 *p = 0; 865 snprint(tcpdir, sizeof(tcpdir), conndir); 866 } 867 868 initibuf(); 869 870 rv = hello(ctl); 871 if(rv < 0) 872 return terminateftp(ctl, rv); 873 874 rv = logon(ctl); 875 if(rv < 0) 876 return terminateftp(ctl, rv); 877 878 rv = xfertype(ctl, "I"); 879 if(rv < 0) 880 return terminateftp(ctl, rv); 881 882 /* if file is up to date and the right size, stop */ 883 if(ftprestart(ctl, out, u, r, mtime) > 0){ 884 close(ctl); 885 return Eof; 886 } 887 888 /* first try passive mode, then active */ 889 data = passive(ctl, u); 890 if(data < 0){ 891 data = active(ctl, u); 892 if(data < 0) 893 return Error; 894 } 895 896 /* fork */ 897 switch(pid = rfork(RFPROC|RFFDG|RFMEM)){ 898 case -1: 899 close(data); 900 return terminateftp(ctl, Error); 901 case 0: 902 ftpxfer(data, out, r); 903 close(data); 904 _exits(0); 905 default: 906 close(data); 907 break; 908 } 909 910 /* wait for reply message */ 911 rv = ftprcode(ctl, msg, sizeof(msg)); 912 close(ctl); 913 914 /* wait for process to terminate */ 915 w = nil; 916 for(;;){ 917 free(w); 918 w = wait(); 919 if(w == nil) 920 return Error; 921 if(w->pid == pid){ 922 if(w->msg[0] == 0){ 923 free(w); 924 break; 925 } 926 werrstr("xfer: %s", w->msg); 927 free(w); 928 return Error; 929 } 930 } 931 932 switch(rv){ 933 case Success: 934 return Eof; 935 case TempFail: 936 return Server; 937 default: 938 return Error; 939 } 940 } 941 942 int 943 ftpcmd(int ctl, char *fmt, ...) 944 { 945 va_list arg; 946 char buf[2*1024], *s; 947 948 va_start(arg, fmt); 949 s = vseprint(buf, buf + (sizeof(buf)-4) / sizeof(*buf), fmt, arg); 950 va_end(arg); 951 if(debug) 952 fprint(2, "%d -> %s\n", ctl, buf); 953 *s++ = '\r'; 954 *s++ = '\n'; 955 if(write(ctl, buf, s - buf) != s - buf) 956 return -1; 957 return 0; 958 } 959 960 int 961 ftprcode(int ctl, char *msg, int len) 962 { 963 int rv; 964 int i; 965 char *p; 966 967 len--; /* room for terminating null */ 968 for(;;){ 969 *msg = 0; 970 i = readline(ctl, msg, len); 971 if(i < 0) 972 break; 973 if(debug) 974 fprint(2, "%d <- %s\n", ctl, msg); 975 976 /* stop if not a continuation */ 977 rv = strtol(msg, &p, 10); 978 if(rv >= 100 && rv < 600 && p==msg+3 && *p == ' ') 979 return rv/100; 980 } 981 *msg = 0; 982 983 return -1; 984 } 985 986 int 987 hello(int ctl) 988 { 989 char msg[1024]; 990 991 /* wait for hello from other side */ 992 if(ftprcode(ctl, msg, sizeof(msg)) != Success){ 993 werrstr("HELLO: %s", msg); 994 return Server; 995 } 996 return 0; 997 } 998 999 int 1000 getdec(char *p, int n) 1001 { 1002 int x = 0; 1003 int i; 1004 1005 for(i = 0; i < n; i++) 1006 x = x*10 + (*p++ - '0'); 1007 return x; 1008 } 1009 1010 int 1011 ftprestart(int ctl, Out *out, URL *u, Range *r, long mtime) 1012 { 1013 Tm tm; 1014 char msg[1024]; 1015 long x, rmtime; 1016 1017 ftpcmd(ctl, "MDTM %s", u->page); 1018 if(ftprcode(ctl, msg, sizeof(msg)) != Success){ 1019 r->start = 0; 1020 return 0; /* need to do something */ 1021 } 1022 1023 /* decode modification time */ 1024 if(strlen(msg) < 4 + 4 + 2 + 2 + 2 + 2 + 2){ 1025 r->start = 0; 1026 return 0; /* need to do something */ 1027 } 1028 memset(&tm, 0, sizeof(tm)); 1029 tm.year = getdec(msg+4, 4) - 1900; 1030 tm.mon = getdec(msg+4+4, 2) - 1; 1031 tm.mday = getdec(msg+4+4+2, 2); 1032 tm.hour = getdec(msg+4+4+2+2, 2); 1033 tm.min = getdec(msg+4+4+2+2+2, 2); 1034 tm.sec = getdec(msg+4+4+2+2+2+2, 2); 1035 strcpy(tm.zone, "GMT"); 1036 rmtime = tm2sec(&tm); 1037 if(rmtime > mtime) 1038 r->start = 0; 1039 1040 /* get size */ 1041 ftpcmd(ctl, "SIZE %s", u->page); 1042 if(ftprcode(ctl, msg, sizeof(msg)) == Success){ 1043 x = atol(msg+4); 1044 if(r->start == x) 1045 return 1; /* we're up to date */ 1046 r->end = x; 1047 } 1048 1049 /* seek to restart point */ 1050 if(r->start > 0){ 1051 ftpcmd(ctl, "REST %lud", r->start); 1052 if(ftprcode(ctl, msg, sizeof(msg)) == Incomplete){ 1053 setoffset(out, r->start); 1054 }else 1055 r->start = 0; 1056 } 1057 1058 return 0; /* need to do something */ 1059 } 1060 1061 int 1062 logon(int ctl) 1063 { 1064 char msg[1024]; 1065 1066 /* login anonymous */ 1067 ftpcmd(ctl, "USER anonymous"); 1068 switch(ftprcode(ctl, msg, sizeof(msg))){ 1069 case Success: 1070 return 0; 1071 case Incomplete: 1072 break; /* need password */ 1073 default: 1074 werrstr("USER: %s", msg); 1075 return Server; 1076 } 1077 1078 /* send user id as password */ 1079 sprint(msg, "%s@closedmind.org", getuser()); 1080 ftpcmd(ctl, "PASS %s", msg); 1081 if(ftprcode(ctl, msg, sizeof(msg)) != Success){ 1082 werrstr("PASS: %s", msg); 1083 return Server; 1084 } 1085 1086 return 0; 1087 } 1088 1089 int 1090 xfertype(int ctl, char *t) 1091 { 1092 char msg[1024]; 1093 1094 ftpcmd(ctl, "TYPE %s", t); 1095 if(ftprcode(ctl, msg, sizeof(msg)) != Success){ 1096 werrstr("TYPE %s: %s", t, msg); 1097 return Server; 1098 } 1099 1100 return 0; 1101 } 1102 1103 int 1104 passive(int ctl, URL *u) 1105 { 1106 char msg[1024]; 1107 char ipaddr[32]; 1108 char *f[6]; 1109 char *p; 1110 int fd; 1111 int port; 1112 char aport[12]; 1113 1114 ftpcmd(ctl, "PASV"); 1115 if(ftprcode(ctl, msg, sizeof(msg)) != Success) 1116 return Error; 1117 1118 /* get address and port number from reply, this is AI */ 1119 p = strchr(msg, '('); 1120 if(p == nil){ 1121 for(p = msg+3; *p; p++) 1122 if(isdigit(*p)) 1123 break; 1124 } else 1125 p++; 1126 if(getfields(p, f, 6, 0, ",)") < 6){ 1127 werrstr("ftp protocol botch"); 1128 return Server; 1129 } 1130 snprint(ipaddr, sizeof(ipaddr), "%s.%s.%s.%s", 1131 f[0], f[1], f[2], f[3]); 1132 port = ((atoi(f[4])&0xff)<<8) + (atoi(f[5])&0xff); 1133 sprint(aport, "%d", port); 1134 1135 /* open data connection */ 1136 fd = dial(netmkaddr(ipaddr, tcpdir, aport), 0, 0, 0); 1137 if(fd < 0){ 1138 werrstr("passive mode failed: %r"); 1139 return Error; 1140 } 1141 1142 /* tell remote to send a file */ 1143 ftpcmd(ctl, "RETR %s", u->page); 1144 if(ftprcode(ctl, msg, sizeof(msg)) != Extra){ 1145 werrstr("RETR %s: %s", u->page, msg); 1146 return Error; 1147 } 1148 return fd; 1149 } 1150 1151 int 1152 active(int ctl, URL *u) 1153 { 1154 char msg[1024]; 1155 char dir[40], ldir[40]; 1156 uchar ipaddr[4]; 1157 uchar port[2]; 1158 int lcfd, dfd, afd; 1159 1160 /* announce a port for the call back */ 1161 snprint(msg, sizeof(msg), "%s!*!0", tcpdir); 1162 afd = announce(msg, dir); 1163 if(afd < 0) 1164 return Error; 1165 1166 /* get a local address/port of the annoucement */ 1167 if(getaddrport(dir, ipaddr, port) < 0){ 1168 close(afd); 1169 return Error; 1170 } 1171 1172 /* tell remote side address and port*/ 1173 ftpcmd(ctl, "PORT %d,%d,%d,%d,%d,%d", ipaddr[0], ipaddr[1], ipaddr[2], 1174 ipaddr[3], port[0], port[1]); 1175 if(ftprcode(ctl, msg, sizeof(msg)) != Success){ 1176 close(afd); 1177 werrstr("active: %s", msg); 1178 return Error; 1179 } 1180 1181 /* tell remote to send a file */ 1182 ftpcmd(ctl, "RETR %s", u->page); 1183 if(ftprcode(ctl, msg, sizeof(msg)) != Extra){ 1184 close(afd); 1185 werrstr("RETR: %s", msg); 1186 return Server; 1187 } 1188 1189 /* wait for a connection */ 1190 lcfd = listen(dir, ldir); 1191 if(lcfd < 0){ 1192 close(afd); 1193 return Error; 1194 } 1195 dfd = accept(lcfd, ldir); 1196 if(dfd < 0){ 1197 close(afd); 1198 close(lcfd); 1199 return Error; 1200 } 1201 close(afd); 1202 close(lcfd); 1203 1204 return dfd; 1205 } 1206 1207 int 1208 ftpxfer(int in, Out *out, Range *r) 1209 { 1210 char buf[1024]; 1211 long vtime; 1212 int i, n; 1213 1214 vtime = 0; 1215 for(n = 0;;n += i){ 1216 i = read(in, buf, sizeof(buf)); 1217 if(i == 0) 1218 break; 1219 if(i < 0) 1220 return Error; 1221 if(output(out, buf, i) != i) 1222 return Error; 1223 r->start += i; 1224 if(verbose && (vtime != time(0) || r->start == r->end)) { 1225 vtime = time(0); 1226 fprint(2, "%ld %ld\n", r->start, r->end); 1227 } 1228 } 1229 return n; 1230 } 1231 1232 int 1233 terminateftp(int ctl, int rv) 1234 { 1235 close(ctl); 1236 return rv; 1237 } 1238 1239 /* 1240 * case insensitive strcmp (why aren't these in libc?) 1241 */ 1242 int 1243 cistrncmp(char *a, char *b, int n) 1244 { 1245 while(n-- > 0){ 1246 if(tolower(*a++) != tolower(*b++)) 1247 return -1; 1248 } 1249 return 0; 1250 } 1251 1252 int 1253 cistrcmp(char *a, char *b) 1254 { 1255 while(*a || *b) 1256 if(tolower(*a++) != tolower(*b++)) 1257 return -1; 1258 1259 return 0; 1260 } 1261 1262 /* 1263 * buffered io 1264 */ 1265 struct 1266 { 1267 char *rp; 1268 char *wp; 1269 char buf[4*1024]; 1270 } b; 1271 1272 void 1273 initibuf(void) 1274 { 1275 b.rp = b.wp = b.buf; 1276 } 1277 1278 /* 1279 * read a possibly buffered line, strip off trailing while 1280 */ 1281 int 1282 readline(int fd, char *buf, int len) 1283 { 1284 int n; 1285 char *p; 1286 int eof = 0; 1287 1288 len--; 1289 1290 for(p = buf;;){ 1291 if(b.rp >= b.wp){ 1292 n = read(fd, b.wp, sizeof(b.buf)/2); 1293 if(n < 0) 1294 return -1; 1295 if(n == 0){ 1296 eof = 1; 1297 break; 1298 } 1299 b.wp += n; 1300 } 1301 n = *b.rp++; 1302 if(len > 0){ 1303 *p++ = n; 1304 len--; 1305 } 1306 if(n == '\n') 1307 break; 1308 } 1309 1310 /* drop trailing white */ 1311 for(;;){ 1312 if(p <= buf) 1313 break; 1314 n = *(p-1); 1315 if(n != ' ' && n != '\t' && n != '\r' && n != '\n') 1316 break; 1317 p--; 1318 } 1319 *p = 0; 1320 1321 if(eof && p == buf) 1322 return -1; 1323 1324 return p-buf; 1325 } 1326 1327 void 1328 unreadline(char *line) 1329 { 1330 int i, n; 1331 1332 i = strlen(line); 1333 n = b.wp-b.rp; 1334 memmove(&b.buf[i+1], b.rp, n); 1335 memmove(b.buf, line, i); 1336 b.buf[i] = '\n'; 1337 b.rp = b.buf; 1338 b.wp = b.rp + i + 1 + n; 1339 } 1340 1341 int 1342 readibuf(int fd, char *buf, int len) 1343 { 1344 int n; 1345 1346 n = b.wp-b.rp; 1347 if(n > 0){ 1348 if(n > len) 1349 n = len; 1350 memmove(buf, b.rp, n); 1351 b.rp += n; 1352 return n; 1353 } 1354 return read(fd, buf, len); 1355 } 1356 1357 int 1358 dfprint(int fd, char *fmt, ...) 1359 { 1360 char buf[4*1024]; 1361 va_list arg; 1362 1363 va_start(arg, fmt); 1364 vseprint(buf, buf+sizeof(buf), fmt, arg); 1365 va_end(arg); 1366 if(debug) 1367 fprint(2, "%d -> %s", fd, buf); 1368 return fprint(fd, "%s", buf); 1369 } 1370 1371 int 1372 getaddrport(char *dir, uchar *ipaddr, uchar *port) 1373 { 1374 char buf[256]; 1375 int fd, i; 1376 char *p; 1377 1378 snprint(buf, sizeof(buf), "%s/local", dir); 1379 fd = open(buf, OREAD); 1380 if(fd < 0) 1381 return -1; 1382 i = read(fd, buf, sizeof(buf)-1); 1383 close(fd); 1384 if(i <= 0) 1385 return -1; 1386 buf[i] = 0; 1387 p = strchr(buf, '!'); 1388 if(p != nil) 1389 *p++ = 0; 1390 v4parseip(ipaddr, buf); 1391 i = atoi(p); 1392 port[0] = i>>8; 1393 port[1] = i; 1394 return 0; 1395 } 1396 1397 void 1398 md5free(DigestState *state) 1399 { 1400 uchar x[MD5dlen]; 1401 md5(nil, 0, x, state); 1402 } 1403 1404 DigestState* 1405 md5dup(DigestState *state) 1406 { 1407 char *p; 1408 1409 p = md5pickle(state); 1410 if(p == nil) 1411 sysfatal("md5pickle: %r"); 1412 state = md5unpickle(p); 1413 if(state == nil) 1414 sysfatal("md5unpickle: %r"); 1415 free(p); 1416 return state; 1417 } 1418 1419 void 1420 setoffset(Out *out, int offset) 1421 { 1422 md5free(out->curr); 1423 if(offset == 0) 1424 out->curr = md5(nil, 0, nil, nil); 1425 else 1426 out->curr = nil; 1427 out->offset = offset; 1428 out->written = offset; 1429 if(ofile != nil) 1430 if(seek(out->fd, offset, 0) != offset) 1431 sysfatal("seek: %r"); 1432 } 1433 1434 /* 1435 * write some output, discarding it (but keeping track) 1436 * if we've already written it. if we've gone backwards, 1437 * verify that everything previously written matches 1438 * that which would have been written from the current 1439 * output. 1440 */ 1441 int 1442 output(Out *out, char *buf, int nb) 1443 { 1444 int n, d; 1445 uchar m0[MD5dlen], m1[MD5dlen]; 1446 1447 n = nb; 1448 d = out->written - out->offset; 1449 assert(d >= 0); 1450 if(d > 0){ 1451 if(n < d){ 1452 if(out->curr != nil) 1453 md5((uchar*)buf, n, nil, out->curr); 1454 out->offset += n; 1455 return n; 1456 } 1457 if(out->curr != nil){ 1458 md5((uchar*)buf, d, m0, out->curr); 1459 out->curr = nil; 1460 md5(nil, 0, m1, md5dup(out->hiwat)); 1461 if(memcmp(m0, m1, MD5dlen) != 0){ 1462 fprint(2, "integrity check failure at offset %d\n", out->written); 1463 return -1; 1464 } 1465 } 1466 buf += d; 1467 n -= d; 1468 out->offset += d; 1469 } 1470 if(n > 0){ 1471 out->hiwat = md5((uchar*)buf, n, nil, out->hiwat); 1472 n = write(out->fd, buf, n); 1473 if(n > 0){ 1474 out->offset += n; 1475 out->written += n; 1476 } 1477 } 1478 return n + d; 1479 } 1480 1481