diff options
author | rsc <devnull@localhost> | 2005-03-18 18:57:16 +0000 |
---|---|---|
committer | rsc <devnull@localhost> | 2005-03-18 18:57:16 +0000 |
commit | 56a48c69aacd0a0deef24f7608c10cf3dbaab0a0 (patch) | |
tree | 4ff391176f88315073b053f9f275c6e26a7fb2e8 /src/cmd/hget.c | |
parent | 8dd8a81f71771d3f2d95a95992fdddebd3063919 (diff) | |
download | plan9port-56a48c69aacd0a0deef24f7608c10cf3dbaab0a0.tar.gz plan9port-56a48c69aacd0a0deef24f7608c10cf3dbaab0a0.tar.bz2 plan9port-56a48c69aacd0a0deef24f7608c10cf3dbaab0a0.zip |
add hget (no ftp support)
Diffstat (limited to 'src/cmd/hget.c')
-rw-r--r-- | src/cmd/hget.c | 1484 |
1 files changed, 1484 insertions, 0 deletions
diff --git a/src/cmd/hget.c b/src/cmd/hget.c new file mode 100644 index 00000000..6c1986dc --- /dev/null +++ b/src/cmd/hget.c @@ -0,0 +1,1484 @@ +#include <u.h> +#include <libc.h> +#include <ctype.h> +#include <bio.h> +#include <ip.h> +#include <libsec.h> +#include <auth.h> +#include <thread.h> + +typedef struct URL URL; +struct URL +{ + int method; + char *host; + char *port; + char *page; + char *etag; + char *redirect; + char *postbody; + char *cred; + long mtime; +}; + +typedef struct Range Range; +struct Range +{ + long start; /* only 2 gig supported, tdb */ + long end; +}; + +typedef struct Out Out; +struct Out +{ + int fd; + int offset; /* notional current offset in output */ + int written; /* number of bytes successfully transferred to output */ + DigestState *curr; /* digest state up to offset (if known) */ + DigestState *hiwat; /* digest state of all bytes written */ +}; + +enum +{ + Http, + Https, + Ftp, + Other +}; + +enum +{ + Eof = 0, + Error = -1, + Server = -2, + Changed = -3, +}; + +int debug; +char *ofile; + + +int doftp(URL*, URL*, Range*, Out*, long); +int dohttp(URL*, URL*, Range*, Out*, long); +int crackurl(URL*, char*); +Range* crackrange(char*); +int getheader(int, char*, int); +int httpheaders(int, int, URL*, Range*); +int httprcode(int); +int cistrncmp(char*, char*, int); +int cistrcmp(char*, char*); +void initibuf(void); +int readline(int, char*, int); +int readibuf(int, char*, int); +int dfprint(int, char*, ...); +void unreadline(char*); +int output(Out*, char*, int); +void setoffset(Out*, int); + +int verbose; +char *net; +char tcpdir[NETPATHLEN]; +int headerprint; + +struct { + char *name; + int (*f)(URL*, URL*, Range*, Out*, long); +} method[] = { + [Http] { "http", dohttp }, + [Https] { "https", dohttp }, + [Ftp] { "ftp", doftp }, + [Other] { "_______", nil }, +}; + +void +usage(void) +{ + fprint(2, "usage: %s [-hv] [-o outfile] [-p body] [-x netmtpt] url\n", argv0); + threadexitsall("usage"); +} + +void +threadmain(int argc, char **argv) +{ + URL u; + Range r; + int errs, n; + ulong mtime; + Dir *d; + char postbody[4096], *p, *e, *t, *hpx; + URL px; // Proxy + Out out; + + ofile = nil; + p = postbody; + e = p + sizeof(postbody); + r.start = 0; + r.end = -1; + mtime = 0; + memset(&u, 0, sizeof(u)); + memset(&px, 0, sizeof(px)); + hpx = getenv("httpproxy"); + + ARGBEGIN { + case 'o': + ofile = ARGF(); + break; + case 'd': + debug = 1; + break; + case 'h': + headerprint = 1; + break; + case 'v': + verbose = 1; + break; + case 'x': + net = ARGF(); + if(net == nil) + usage(); + break; + case 'p': + t = ARGF(); + if(t == nil) + usage(); + if(p != postbody) + p = seprint(p, e, "&%s", t); + else + p = seprint(p, e, "%s", t); + u.postbody = postbody; + + break; + default: + usage(); + } ARGEND; + + if(net != nil){ + if(strlen(net) > sizeof(tcpdir)-5) + sysfatal("network mount point too long"); + snprint(tcpdir, sizeof(tcpdir), "%s/tcp", net); + } else + snprint(tcpdir, sizeof(tcpdir), "tcp"); + + if(argc != 1) + usage(); + + + out.fd = 1; + out.written = 0; + out.offset = 0; + out.curr = nil; + out.hiwat = nil; + if(ofile != nil){ + d = dirstat(ofile); + if(d == nil){ + out.fd = create(ofile, OWRITE, 0664); + if(out.fd < 0) + sysfatal("creating %s: %r", ofile); + } else { + out.fd = open(ofile, OWRITE); + if(out.fd < 0) + sysfatal("can't open %s: %r", ofile); + r.start = d->length; + mtime = d->mtime; + free(d); + } + } + + errs = 0; + + if(crackurl(&u, argv[0]) < 0) + sysfatal("%r"); + if(hpx && crackurl(&px, hpx) < 0) + sysfatal("%r"); + + for(;;){ + setoffset(&out, 0); + /* transfer data */ + werrstr(""); + n = (*method[u.method].f)(&u, &px, &r, &out, mtime); + + switch(n){ + case Eof: + threadexitsall(0); + break; + case Error: + if(errs++ < 10) + continue; + sysfatal("too many errors with no progress %r"); + break; + case Server: + sysfatal("server returned: %r"); + break; + } + + /* forward progress */ + errs = 0; + r.start += n; + if(r.start >= r.end) + break; + } + + threadexitsall(0); +} + +int +crackurl(URL *u, char *s) +{ + char *p; + int i; + + if(u->host != nil){ + free(u->host); + u->host = nil; + } + if(u->page != nil){ + free(u->page); + u->page = nil; + } + + /* get type */ + u->method = Other; + for(p = s; *p; p++){ + if(*p == '/'){ + u->method = Http; + p = s; + break; + } + if(*p == ':' && *(p+1)=='/' && *(p+2)=='/'){ + *p = 0; + p += 3; + for(i = 0; i < nelem(method); i++){ + if(cistrcmp(s, method[i].name) == 0){ + u->method = i; + break; + } + } + break; + } + } + + if(u->method == Other){ + werrstr("unsupported URL type %s", s); + return -1; + } + + /* get system */ + s = p; + p = strchr(s, '/'); + if(p == nil){ + u->host = strdup(s); + u->page = strdup("/"); + } else { + u->page = strdup(p); + *p = 0; + u->host = strdup(s); + *p = '/'; + } + + if(p = strchr(u->host, ':')) { + *p++ = 0; + u->port = p; + } else + u->port = method[u->method].name; + + if(*(u->host) == 0){ + werrstr("bad url, null host"); + return -1; + } + + return 0; +} + +char *day[] = { + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" +}; + +char *month[] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" +}; + +struct +{ + int fd; + long mtime; +} note; + +void +catch(void *v, char *s) +{ + Dir d; + + USED(v); + USED(s); + + nulldir(&d); + d.mtime = note.mtime; + if(dirfwstat(note.fd, &d) < 0) + sysfatal("catch: can't dirfwstat: %r"); + noted(NDFLT); +} + +int +dohttp(URL *u, URL *px, Range *r, Out *out, long mtime) +{ + int fd, cfd; + int redirect, auth, loop; + int n, rv, code; + long tot, vtime; + Tm *tm; + char buf[1024]; + char err[ERRMAX]; + + + /* always move back to a previous 512 byte bound because some + * servers can't seem to deal with requests that start at the + * end of the file + */ + if(r->start) + r->start = ((r->start-1)/512)*512; + + /* loop for redirects, requires reading both response code and headers */ + fd = -1; + for(loop = 0; loop < 32; loop++){ + if(px->host == nil){ + fd = dial(netmkaddr(u->host, tcpdir, u->port), 0, 0, 0); + } else { + fd = dial(netmkaddr(px->host, tcpdir, px->port), 0, 0, 0); + } + if(fd < 0) + return Error; + + if(u->method == Https){ + int tfd; + TLSconn conn; + + memset(&conn, 0, sizeof conn); + tfd = tlsClient(fd, &conn); + if(tfd < 0){ + fprint(2, "tlsClient: %r\n"); + close(fd); + return Error; + } + /* BUG: check cert here? */ + if(conn.cert) + free(conn.cert); + close(fd); + fd = tfd; + } + + /* write request, use range if not start of file */ + if(u->postbody == nil){ + if(px->host == nil){ + dfprint(fd, "GET %s HTTP/1.0\r\n" + "Host: %s\r\n" + "User-agent: Plan9/hget\r\n" + "Cache-Control: no-cache\r\n" + "Pragma: no-cache\r\n", + u->page, u->host); + } else { + dfprint(fd, "GET http://%s%s HTTP/1.0\r\n" + "Host: %s\r\n" + "User-agent: Plan9/hget\r\n" + "Cache-Control: no-cache\r\n" + "Pragma: no-cache\r\n", + u->host, u->page, u->host); + } + if(u->cred) + dfprint(fd, "Authorization: Basic %s\r\n", + u->cred); + } else { + dfprint(fd, "POST %s HTTP/1.0\r\n" + "Host: %s\r\n" + "Content-type: application/x-www-form-urlencoded\r\n" + "Content-length: %d\r\n" + "User-agent: Plan9/hget\r\n" + "\r\n", + u->page, u->host, strlen(u->postbody)); + dfprint(fd, "%s", u->postbody); + } + if(r->start != 0){ + dfprint(fd, "Range: bytes=%d-\n", r->start); + if(u->etag != nil){ + dfprint(fd, "If-range: %s\n", u->etag); + } else { + tm = gmtime(mtime); + dfprint(fd, "If-range: %s, %d %s %d %2d:%2.2d:%2.2d GMT\n", + day[tm->wday], tm->mday, month[tm->mon], + tm->year+1900, tm->hour, tm->min, tm->sec); + } + } + if((cfd = open("/mnt/webcookies/http", ORDWR)) >= 0){ + if(fprint(cfd, "http://%s%s", u->host, u->page) > 0){ + while((n = read(cfd, buf, sizeof buf)) > 0){ + if(debug) + write(2, buf, n); + write(fd, buf, n); + } + }else{ + close(cfd); + cfd = -1; + } + } + + dfprint(fd, "\r\n", u->host); + + auth = 0; + redirect = 0; + initibuf(); + code = httprcode(fd); + switch(code){ + case Error: /* connection timed out */ + case Eof: + close(fd); + close(cfd); + return code; + + case 200: /* OK */ + case 201: /* Created */ + case 202: /* Accepted */ + if(ofile == nil && r->start != 0) + sysfatal("page changed underfoot"); + break; + + case 204: /* No Content */ + sysfatal("No Content"); + + case 206: /* Partial Content */ + setoffset(out, r->start); + break; + + case 301: /* Moved Permanently */ + case 302: /* Moved Temporarily */ + redirect = 1; + u->postbody = nil; + break; + + case 304: /* Not Modified */ + break; + + case 400: /* Bad Request */ + sysfatal("Bad Request"); + + case 401: /* Unauthorized */ + if (auth) + sysfatal("Authentication failed"); + auth = 1; + break; + + case 402: /* ??? */ + sysfatal("Unauthorized"); + + case 403: /* Forbidden */ + sysfatal("Forbidden by server"); + + case 404: /* Not Found */ + sysfatal("Not found on server"); + + case 407: /* Proxy Authentication */ + sysfatal("Proxy authentication required"); + + case 500: /* Internal server error */ + sysfatal("Server choked"); + + case 501: /* Not implemented */ + sysfatal("Server can't do it!"); + + case 502: /* Bad gateway */ + sysfatal("Bad gateway"); + + case 503: /* Service unavailable */ + sysfatal("Service unavailable"); + + default: + sysfatal("Unknown response code %d", code); + } + + if(u->redirect != nil){ + free(u->redirect); + u->redirect = nil; + } + + rv = httpheaders(fd, cfd, u, r); + close(cfd); + if(rv != 0){ + close(fd); + return rv; + } + + if(!redirect && !auth) + break; + + if (redirect){ + if(u->redirect == nil) + sysfatal("redirect: no URL"); + if(crackurl(u, u->redirect) < 0) + sysfatal("redirect: %r"); + } + } + + /* transfer whatever you get */ + if(ofile != nil && u->mtime != 0){ + note.fd = out->fd; + note.mtime = u->mtime; + notify(catch); + } + + tot = 0; + vtime = 0; + for(;;){ + n = readibuf(fd, buf, sizeof(buf)); + if(n <= 0) + break; + if(output(out, buf, n) != n) + break; + tot += n; + if(verbose && vtime != time(0)) { + vtime = time(0); + fprint(2, "%ld %ld\n", r->start+tot, r->end); + } + } + notify(nil); + close(fd); + + if(ofile != nil && u->mtime != 0){ + Dir d; + + rerrstr(err, sizeof err); + nulldir(&d); + d.mtime = u->mtime; + if(dirfwstat(out->fd, &d) < 0) + fprint(2, "couldn't set mtime: %r\n"); + errstr(err, sizeof err); + } + + return tot; +} + +/* get the http response code */ +int +httprcode(int fd) +{ + int n; + char *p; + char buf[256]; + + n = readline(fd, buf, sizeof(buf)-1); + if(n <= 0) + return n; + if(debug) + fprint(2, "%d <- %s\n", fd, buf); + p = strchr(buf, ' '); + if(strncmp(buf, "HTTP/", 5) != 0 || p == nil){ + werrstr("bad response from server"); + return -1; + } + buf[n] = 0; + return atoi(p+1); +} + +/* read in and crack the http headers, update u and r */ +void hhetag(char*, URL*, Range*); +void hhmtime(char*, URL*, Range*); +void hhclen(char*, URL*, Range*); +void hhcrange(char*, URL*, Range*); +void hhuri(char*, URL*, Range*); +void hhlocation(char*, URL*, Range*); +void hhauth(char*, URL*, Range*); + +struct { + char *name; + void (*f)(char*, URL*, Range*); +} headers[] = { + { "etag:", hhetag }, + { "last-modified:", hhmtime }, + { "content-length:", hhclen }, + { "content-range:", hhcrange }, + { "uri:", hhuri }, + { "location:", hhlocation }, + { "WWW-Authenticate:", hhauth }, +}; +int +httpheaders(int fd, int cfd, URL *u, Range *r) +{ + char buf[2048]; + char *p; + int i, n; + + for(;;){ + n = getheader(fd, buf, sizeof(buf)); + if(n <= 0) + break; + if(cfd >= 0) + fprint(cfd, "%s\n", buf); + for(i = 0; i < nelem(headers); i++){ + n = strlen(headers[i].name); + if(cistrncmp(buf, headers[i].name, n) == 0){ + /* skip field name and leading white */ + p = buf + n; + while(*p == ' ' || *p == '\t') + p++; + + (*headers[i].f)(p, u, r); + break; + } + } + } + return n; +} + +/* + * read a single mime header, collect continuations. + * + * this routine assumes that there is a blank line twixt + * the header and the message body, otherwise bytes will + * be lost. + */ +int +getheader(int fd, char *buf, int n) +{ + char *p, *e; + int i; + + n--; + p = buf; + for(e = p + n; ; p += i){ + i = readline(fd, p, e-p); + if(i < 0) + return i; + + if(p == buf){ + /* first line */ + if(strchr(buf, ':') == nil) + break; /* end of headers */ + } else { + /* continuation line */ + if(*p != ' ' && *p != '\t'){ + unreadline(p); + *p = 0; + break; /* end of this header */ + } + } + } + if(headerprint) + print("%s\n", buf); + + if(debug) + fprint(2, "%d <- %s\n", fd, buf); + return p-buf; +} + +void +hhetag(char *p, URL *u, Range *r) +{ + USED(r); + + if(u->etag != nil){ + if(strcmp(u->etag, p) != 0) + sysfatal("file changed underfoot"); + } else + u->etag = strdup(p); +} + +char* monthchars = "janfebmaraprmayjunjulaugsepoctnovdec"; + +void +hhmtime(char *p, URL *u, Range *r) +{ + char *month, *day, *yr, *hms; + char *fields[6]; + Tm tm, now; + int i; + + USED(r); + + i = getfields(p, fields, 6, 1, " \t"); + if(i < 5) + return; + + day = fields[1]; + month = fields[2]; + yr = fields[3]; + hms = fields[4]; + + /* default time */ + now = *gmtime(time(0)); + tm = now; + tm.yday = 0; + + /* convert ascii month to a number twixt 1 and 12 */ + if(*month >= '0' && *month <= '9'){ + tm.mon = atoi(month) - 1; + if(tm.mon < 0 || tm.mon > 11) + tm.mon = 5; + } else { + for(p = month; *p; p++) + *p = tolower(*p); + for(i = 0; i < 12; i++) + if(strncmp(&monthchars[i*3], month, 3) == 0){ + tm.mon = i; + break; + } + } + + tm.mday = atoi(day); + + if(hms) { + tm.hour = strtoul(hms, &p, 10); + if(*p == ':') { + p++; + tm.min = strtoul(p, &p, 10); + if(*p == ':') { + p++; + tm.sec = strtoul(p, &p, 10); + } + } + if(tolower(*p) == 'p') + tm.hour += 12; + } + + if(yr) { + tm.year = atoi(yr); + if(tm.year >= 1900) + tm.year -= 1900; + } else { + if(tm.mon > now.mon || (tm.mon == now.mon && tm.mday > now.mday+1)) + tm.year--; + } + + strcpy(tm.zone, "GMT"); + /* convert to epoch seconds */ + u->mtime = tm2sec(&tm); +} + +void +hhclen(char *p, URL *u, Range *r) +{ + USED(u); + + r->end = atoi(p); +} + +void +hhcrange(char *p, URL *u, Range *r) +{ + char *x; + vlong l; + + USED(u); + l = 0; + x = strchr(p, '/'); + if(x) + l = atoll(x+1); + if(l == 0) + x = strchr(p, '-'); + if(x) + l = atoll(x+1); + if(l) + r->end = l; +} + +void +hhuri(char *p, URL *u, Range *r) +{ + USED(r); + + if(*p != '<') + return; + u->redirect = strdup(p+1); + p = strchr(u->redirect, '>'); + if(p != nil) + *p = 0; +} + +void +hhlocation(char *p, URL *u, Range *r) +{ + USED(r); + + u->redirect = strdup(p); +} + +void +hhauth(char *p, URL *u, Range *r) +{ + char *f[4]; + UserPasswd *up; + char *s, cred[64]; + + USED(r); + + if (cistrncmp(p, "basic ", 6) != 0) + sysfatal("only Basic authentication supported"); + + if (gettokens(p, f, nelem(f), "\"") < 2) + sysfatal("garbled auth data"); + + if ((up = auth_getuserpasswd(auth_getkey, "proto=pass service=http dom=%q relm=%q", + u->host, f[1])) == nil) + sysfatal("cannot authenticate"); + + s = smprint("%s:%s", up->user, up->passwd); + if(enc64(cred, sizeof(cred), (uchar *)s, strlen(s)) == -1) + sysfatal("enc64"); + free(s); + + assert(u->cred = strdup(cred)); +} + +enum +{ + /* ftp return codes */ + Extra= 1, + Success= 2, + Incomplete= 3, + TempFail= 4, + PermFail= 5, + + Nnetdir= 64, /* max length of network directory paths */ + Ndialstr= 64, /* max length of dial strings */ +}; + +int ftpcmd(int, char*, ...); +int ftprcode(int, char*, int); +int hello(int); +int logon(int); +int xfertype(int, char*); +int passive(int, URL*); +int active(int, URL*); +int ftpxfer(int, Out*, Range*); +int terminateftp(int, int); +int getaddrport(char*, uchar*, uchar*); +int ftprestart(int, Out*, URL*, Range*, long); + +int +doftp(URL *u, URL *px, Range *r, Out *out, long mtime) +{ + int pid, ctl, data, rv; + Waitmsg *w; + char msg[64]; + char conndir[NETPATHLEN]; + char *p; + + /* untested, proxy dosn't work with ftp (I think) */ + if(px->host == nil){ + ctl = dial(netmkaddr(u->host, tcpdir, u->port), 0, conndir, 0); + } else { + ctl = dial(netmkaddr(px->host, tcpdir, px->port), 0, conndir, 0); + } + + if(ctl < 0) + return Error; + if(net == nil){ + p = strrchr(conndir, '/'); + *p = 0; + snprint(tcpdir, sizeof(tcpdir), conndir); + } + + initibuf(); + + rv = hello(ctl); + if(rv < 0) + return terminateftp(ctl, rv); + + rv = logon(ctl); + if(rv < 0) + return terminateftp(ctl, rv); + + rv = xfertype(ctl, "I"); + if(rv < 0) + return terminateftp(ctl, rv); + + /* if file is up to date and the right size, stop */ + if(ftprestart(ctl, out, u, r, mtime) > 0){ + close(ctl); + return Eof; + } + + /* first try passive mode, then active */ + data = passive(ctl, u); + if(data < 0){ + data = active(ctl, u); + if(data < 0) + return Error; + } + + /* fork */ + switch(pid = fork()){ + case -1: + close(data); + return terminateftp(ctl, Error); + case 0: + ftpxfer(data, out, r); + close(data); + #undef _exits + _exits(0); + default: + close(data); + break; + } + + /* wait for reply message */ + rv = ftprcode(ctl, msg, sizeof(msg)); + close(ctl); + + /* wait for process to terminate */ + w = nil; + for(;;){ + free(w); + w = wait(); + if(w == nil) + return Error; + if(w->pid == pid){ + if(w->msg[0] == 0){ + free(w); + break; + } + werrstr("xfer: %s", w->msg); + free(w); + return Error; + } + } + + switch(rv){ + case Success: + return Eof; + case TempFail: + return Server; + default: + return Error; + } +} + +int +ftpcmd(int ctl, char *fmt, ...) +{ + va_list arg; + char buf[2*1024], *s; + + va_start(arg, fmt); + s = vseprint(buf, buf + (sizeof(buf)-4) / sizeof(*buf), fmt, arg); + va_end(arg); + if(debug) + fprint(2, "%d -> %s\n", ctl, buf); + *s++ = '\r'; + *s++ = '\n'; + if(write(ctl, buf, s - buf) != s - buf) + return -1; + return 0; +} + +int +ftprcode(int ctl, char *msg, int len) +{ + int rv; + int i; + char *p; + + len--; /* room for terminating null */ + for(;;){ + *msg = 0; + i = readline(ctl, msg, len); + if(i < 0) + break; + if(debug) + fprint(2, "%d <- %s\n", ctl, msg); + + /* stop if not a continuation */ + rv = strtol(msg, &p, 10); + if(rv >= 100 && rv < 600 && p==msg+3 && *p == ' ') + return rv/100; + } + *msg = 0; + + return -1; +} + +int +hello(int ctl) +{ + char msg[1024]; + + /* wait for hello from other side */ + if(ftprcode(ctl, msg, sizeof(msg)) != Success){ + werrstr("HELLO: %s", msg); + return Server; + } + return 0; +} + +int +getdec(char *p, int n) +{ + int x = 0; + int i; + + for(i = 0; i < n; i++) + x = x*10 + (*p++ - '0'); + return x; +} + +int +ftprestart(int ctl, Out *out, URL *u, Range *r, long mtime) +{ + Tm tm; + char msg[1024]; + long x, rmtime; + + ftpcmd(ctl, "MDTM %s", u->page); + if(ftprcode(ctl, msg, sizeof(msg)) != Success){ + r->start = 0; + return 0; /* need to do something */ + } + + /* decode modification time */ + if(strlen(msg) < 4 + 4 + 2 + 2 + 2 + 2 + 2){ + r->start = 0; + return 0; /* need to do something */ + } + memset(&tm, 0, sizeof(tm)); + tm.year = getdec(msg+4, 4) - 1900; + tm.mon = getdec(msg+4+4, 2) - 1; + tm.mday = getdec(msg+4+4+2, 2); + tm.hour = getdec(msg+4+4+2+2, 2); + tm.min = getdec(msg+4+4+2+2+2, 2); + tm.sec = getdec(msg+4+4+2+2+2+2, 2); + strcpy(tm.zone, "GMT"); + rmtime = tm2sec(&tm); + if(rmtime > mtime) + r->start = 0; + + /* get size */ + ftpcmd(ctl, "SIZE %s", u->page); + if(ftprcode(ctl, msg, sizeof(msg)) == Success){ + x = atol(msg+4); + if(r->start == x) + return 1; /* we're up to date */ + r->end = x; + } + + /* seek to restart point */ + if(r->start > 0){ + ftpcmd(ctl, "REST %lud", r->start); + if(ftprcode(ctl, msg, sizeof(msg)) == Incomplete){ + setoffset(out, r->start); + }else + r->start = 0; + } + + return 0; /* need to do something */ +} + +int +logon(int ctl) +{ + char msg[1024]; + + /* login anonymous */ + ftpcmd(ctl, "USER anonymous"); + switch(ftprcode(ctl, msg, sizeof(msg))){ + case Success: + return 0; + case Incomplete: + break; /* need password */ + default: + werrstr("USER: %s", msg); + return Server; + } + + /* send user id as password */ + sprint(msg, "%s@closedmind.org", getuser()); + ftpcmd(ctl, "PASS %s", msg); + if(ftprcode(ctl, msg, sizeof(msg)) != Success){ + werrstr("PASS: %s", msg); + return Server; + } + + return 0; +} + +int +xfertype(int ctl, char *t) +{ + char msg[1024]; + + ftpcmd(ctl, "TYPE %s", t); + if(ftprcode(ctl, msg, sizeof(msg)) != Success){ + werrstr("TYPE %s: %s", t, msg); + return Server; + } + + return 0; +} + +int +passive(int ctl, URL *u) +{ + char msg[1024]; + char ipaddr[32]; + char *f[6]; + char *p; + int fd; + int port; + char aport[12]; + + ftpcmd(ctl, "PASV"); + if(ftprcode(ctl, msg, sizeof(msg)) != Success) + return Error; + + /* get address and port number from reply, this is AI */ + p = strchr(msg, '('); + if(p == nil){ + for(p = msg+3; *p; p++) + if(isdigit(*p)) + break; + } else + p++; + if(getfields(p, f, 6, 0, ",)") < 6){ + werrstr("ftp protocol botch"); + return Server; + } + snprint(ipaddr, sizeof(ipaddr), "%s.%s.%s.%s", + f[0], f[1], f[2], f[3]); + port = ((atoi(f[4])&0xff)<<8) + (atoi(f[5])&0xff); + sprint(aport, "%d", port); + + /* open data connection */ + fd = dial(netmkaddr(ipaddr, tcpdir, aport), 0, 0, 0); + if(fd < 0){ + werrstr("passive mode failed: %r"); + return Error; + } + + /* tell remote to send a file */ + ftpcmd(ctl, "RETR %s", u->page); + if(ftprcode(ctl, msg, sizeof(msg)) != Extra){ + werrstr("RETR %s: %s", u->page, msg); + return Error; + } + return fd; +} + +int +active(int ctl, URL *u) +{ + char msg[1024]; + char dir[40], ldir[40]; + uchar ipaddr[4]; + uchar port[2]; + int lcfd, dfd, afd; + + /* announce a port for the call back */ + snprint(msg, sizeof(msg), "%s!*!0", tcpdir); + afd = announce(msg, dir); + if(afd < 0) + return Error; + + /* get a local address/port of the annoucement */ + if(getaddrport(dir, ipaddr, port) < 0){ + close(afd); + return Error; + } + + /* tell remote side address and port*/ + ftpcmd(ctl, "PORT %d,%d,%d,%d,%d,%d", ipaddr[0], ipaddr[1], ipaddr[2], + ipaddr[3], port[0], port[1]); + if(ftprcode(ctl, msg, sizeof(msg)) != Success){ + close(afd); + werrstr("active: %s", msg); + return Error; + } + + /* tell remote to send a file */ + ftpcmd(ctl, "RETR %s", u->page); + if(ftprcode(ctl, msg, sizeof(msg)) != Extra){ + close(afd); + werrstr("RETR: %s", msg); + return Server; + } + + /* wait for a connection */ + lcfd = listen(dir, ldir); + if(lcfd < 0){ + close(afd); + return Error; + } + dfd = accept(lcfd, ldir); + if(dfd < 0){ + close(afd); + close(lcfd); + return Error; + } + close(afd); + close(lcfd); + + return dfd; +} + +int +ftpxfer(int in, Out *out, Range *r) +{ + char buf[1024]; + long vtime; + int i, n; + + vtime = 0; + for(n = 0;;n += i){ + i = read(in, buf, sizeof(buf)); + if(i == 0) + break; + if(i < 0) + return Error; + if(output(out, buf, i) != i) + return Error; + r->start += i; + if(verbose && vtime != time(0)) { + vtime = time(0); + fprint(2, "%ld %ld\n", r->start, r->end); + } + } + return n; +} + +int +terminateftp(int ctl, int rv) +{ + close(ctl); + return rv; +} + +/* + * case insensitive strcmp (why aren't these in libc?) + */ +int +cistrncmp(char *a, char *b, int n) +{ + while(n-- > 0){ + if(tolower(*a++) != tolower(*b++)) + return -1; + } + return 0; +} + +int +cistrcmp(char *a, char *b) +{ + while(*a || *b) + if(tolower(*a++) != tolower(*b++)) + return -1; + + return 0; +} + +/* + * buffered io + */ +struct +{ + char *rp; + char *wp; + char buf[4*1024]; +} b; + +void +initibuf(void) +{ + b.rp = b.wp = b.buf; +} + +/* + * read a possibly buffered line, strip off trailing while + */ +int +readline(int fd, char *buf, int len) +{ + int n; + char *p; + int eof = 0; + + len--; + + for(p = buf;;){ + if(b.rp >= b.wp){ + n = read(fd, b.wp, sizeof(b.buf)/2); + if(n < 0) + return -1; + if(n == 0){ + eof = 1; + break; + } + b.wp += n; + } + n = *b.rp++; + if(len > 0){ + *p++ = n; + len--; + } + if(n == '\n') + break; + } + + /* drop trailing white */ + for(;;){ + if(p <= buf) + break; + n = *(p-1); + if(n != ' ' && n != '\t' && n != '\r' && n != '\n') + break; + p--; + } + *p = 0; + + if(eof && p == buf) + return -1; + + return p-buf; +} + +void +unreadline(char *line) +{ + int i, n; + + i = strlen(line); + n = b.wp-b.rp; + memmove(&b.buf[i+1], b.rp, n); + memmove(b.buf, line, i); + b.buf[i] = '\n'; + b.rp = b.buf; + b.wp = b.rp + i + 1 + n; +} + +int +readibuf(int fd, char *buf, int len) +{ + int n; + + n = b.wp-b.rp; + if(n > 0){ + if(n > len) + n = len; + memmove(buf, b.rp, n); + b.rp += n; + return n; + } + return read(fd, buf, len); +} + +int +dfprint(int fd, char *fmt, ...) +{ + char buf[4*1024]; + va_list arg; + + va_start(arg, fmt); + vseprint(buf, buf+sizeof(buf), fmt, arg); + va_end(arg); + if(debug) + fprint(2, "%d -> %s", fd, buf); + return fprint(fd, "%s", buf); +} + +int +getaddrport(char *dir, uchar *ipaddr, uchar *port) +{ + char buf[256]; + int fd, i; + char *p; + + snprint(buf, sizeof(buf), "%s/local", dir); + fd = open(buf, OREAD); + if(fd < 0) + return -1; + i = read(fd, buf, sizeof(buf)-1); + close(fd); + if(i <= 0) + return -1; + buf[i] = 0; + p = strchr(buf, '!'); + if(p != nil) + *p++ = 0; + v4parseip(ipaddr, buf); + i = atoi(p); + port[0] = i>>8; + port[1] = i; + return 0; +} + +void +md5free(DigestState *state) +{ + uchar x[MD5dlen]; + md5(nil, 0, x, state); +} + +DigestState* +md5dup(DigestState *state) +{ + DigestState *s2; + + s2 = malloc(sizeof(DigestState)); + if(s2 == nil) + sysfatal("malloc: %r"); + *s2 = *state; + s2->malloced = 1; + return s2; +} + +void +setoffset(Out *out, int offset) +{ + md5free(out->curr); + if(offset == 0) + out->curr = md5(nil, 0, nil, nil); + else + out->curr = nil; + out->offset = offset; +} + +/* + * write some output, discarding it (but keeping track) + * if we've already written it. if we've gone backwards, + * verify that everything previously written matches + * that which would have been written from the current + * output. + */ +int +output(Out *out, char *buf, int nb) +{ + int n, d; + uchar m0[MD5dlen], m1[MD5dlen]; + + n = nb; + d = out->written - out->offset; + assert(d >= 0); + if(d > 0){ + if(n < d){ + if(out->curr != nil) + md5((uchar*)buf, n, nil, out->curr); + out->offset += n; + return n; + } + if(out->curr != nil){ + md5((uchar*)buf, d, m0, out->curr); + out->curr = nil; + md5(nil, 0, m1, md5dup(out->hiwat)); + if(memcmp(m0, m1, MD5dlen) != 0){ + fprint(2, "integrity check failure at offset %d\n", out->written); + return -1; + } + } + buf += d; + n -= d; + out->offset += d; + } + if(n > 0){ + out->hiwat = md5((uchar*)buf, n, nil, out->hiwat); + n = write(out->fd, buf, n); + if(n > 0){ + out->offset += n; + out->written += n; + } + } + return n + d; +} + |