aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/hget.c
diff options
context:
space:
mode:
authorrsc <devnull@localhost>2005-03-18 18:57:16 +0000
committerrsc <devnull@localhost>2005-03-18 18:57:16 +0000
commit56a48c69aacd0a0deef24f7608c10cf3dbaab0a0 (patch)
tree4ff391176f88315073b053f9f275c6e26a7fb2e8 /src/cmd/hget.c
parent8dd8a81f71771d3f2d95a95992fdddebd3063919 (diff)
downloadplan9port-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.c1484
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;
+}
+