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