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