Blob


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>
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 };
24 typedef struct Range Range;
25 struct Range
26 {
27 long start; /* only 2 gig supported, tdb */
28 long end;
29 };
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 };
41 enum
42 {
43 Http,
44 Https,
45 Ftp,
46 Other
47 };
49 enum
50 {
51 Eof = 0,
52 Error = -1,
53 Server = -2,
54 Changed = -3,
55 };
57 int debug;
58 char *ofile;
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);
78 int verbose;
79 char *net;
80 char tcpdir[NETPATHLEN];
81 int headerprint;
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 };
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 }
100 void
101 threadmain(int argc, char **argv)
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;
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");
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;
150 break;
151 default:
152 usage();
153 } ARGEND;
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");
162 if(argc != 1)
163 usage();
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);
187 errs = 0;
189 if(crackurl(&u, argv[0]) < 0)
190 sysfatal("%r");
191 if(hpx && crackurl(&px, hpx) < 0)
192 sysfatal("%r");
194 for(;;){
195 setoffset(&out, 0);
196 /* transfer data */
197 werrstr("");
198 n = (*method[u.method].f)(&u, &px, &r, &out, mtime);
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;
214 /* forward progress */
215 errs = 0;
216 r.start += n;
217 if(r.start >= r.end)
218 break;
221 threadexitsall(0);
224 int
225 crackurl(URL *u, char *s)
227 char *p;
228 int i;
230 if(u->host != nil){
231 free(u->host);
232 u->host = nil;
234 if(u->page != nil){
235 free(u->page);
236 u->page = nil;
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;
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;
256 break;
260 if(u->method == Other){
261 werrstr("unsupported URL type %s", s);
262 return -1;
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 = '/';
278 if(p = strchr(u->host, ':')) {
279 *p++ = 0;
280 u->port = p;
281 } else
282 u->port = method[u->method].name;
284 if(*(u->host) == 0){
285 werrstr("bad url, null host");
286 return -1;
289 return 0;
292 char *day[] = {
293 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
294 };
296 char *month[] = {
297 "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
298 };
300 struct
302 int fd;
303 long mtime;
304 } note;
306 void
307 catch(void *v, char *s)
309 Dir d;
311 USED(v);
312 USED(s);
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);
321 int
322 dohttp(URL *u, URL *px, Range *r, Out *out, long mtime)
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];
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;
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);
348 if(fd < 0)
349 return Error;
351 if(u->method == Https){
352 int tfd;
353 TLSconn conn;
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;
362 /* BUG: check cert here? */
363 if(conn.cert)
364 free(conn.cert);
365 close(fd);
366 fd = tfd;
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);
386 if(u->cred)
387 dfprint(fd, "Authorization: Basic %s\r\n",
388 u->cred);
389 } else {
390 dfprint(fd, "POST %s HTTP/1.0\r\n"
391 "Host: %s\r\n"
392 "Content-type: application/x-www-form-urlencoded\r\n"
393 "Content-length: %d\r\n"
394 "User-agent: Plan9/hget\r\n"
395 "\r\n",
396 u->page, u->host, strlen(u->postbody));
397 dfprint(fd, "%s", u->postbody);
399 if(r->start != 0){
400 dfprint(fd, "Range: bytes=%d-\n", r->start);
401 if(u->etag != nil){
402 dfprint(fd, "If-range: %s\n", u->etag);
403 } else {
404 tm = gmtime(mtime);
405 dfprint(fd, "If-range: %s, %d %s %d %2d:%2.2d:%2.2d GMT\n",
406 day[tm->wday], tm->mday, month[tm->mon],
407 tm->year+1900, tm->hour, tm->min, tm->sec);
410 if((cfd = open("/mnt/webcookies/http", ORDWR)) >= 0){
411 if(fprint(cfd, "http://%s%s", u->host, u->page) > 0){
412 while((n = read(cfd, buf, sizeof buf)) > 0){
413 if(debug)
414 write(2, buf, n);
415 write(fd, buf, n);
417 }else{
418 close(cfd);
419 cfd = -1;
423 dfprint(fd, "\r\n", u->host);
425 auth = 0;
426 redirect = 0;
427 initibuf();
428 code = httprcode(fd);
429 switch(code){
430 case Error: /* connection timed out */
431 case Eof:
432 close(fd);
433 close(cfd);
434 return code;
436 case 200: /* OK */
437 case 201: /* Created */
438 case 202: /* Accepted */
439 if(ofile == nil && r->start != 0)
440 sysfatal("page changed underfoot");
441 break;
443 case 204: /* No Content */
444 sysfatal("No Content");
446 case 206: /* Partial Content */
447 setoffset(out, r->start);
448 break;
450 case 301: /* Moved Permanently */
451 case 302: /* Moved Temporarily */
452 redirect = 1;
453 u->postbody = nil;
454 break;
456 case 304: /* Not Modified */
457 break;
459 case 400: /* Bad Request */
460 sysfatal("Bad Request");
462 case 401: /* Unauthorized */
463 if (auth)
464 sysfatal("Authentication failed");
465 auth = 1;
466 break;
468 case 402: /* ??? */
469 sysfatal("Unauthorized");
471 case 403: /* Forbidden */
472 sysfatal("Forbidden by server");
474 case 404: /* Not Found */
475 sysfatal("Not found on server");
477 case 407: /* Proxy Authentication */
478 sysfatal("Proxy authentication required");
480 case 500: /* Internal server error */
481 sysfatal("Server choked");
483 case 501: /* Not implemented */
484 sysfatal("Server can't do it!");
486 case 502: /* Bad gateway */
487 sysfatal("Bad gateway");
489 case 503: /* Service unavailable */
490 sysfatal("Service unavailable");
492 default:
493 sysfatal("Unknown response code %d", code);
496 if(u->redirect != nil){
497 free(u->redirect);
498 u->redirect = nil;
501 rv = httpheaders(fd, cfd, u, r);
502 close(cfd);
503 if(rv != 0){
504 close(fd);
505 return rv;
508 if(!redirect && !auth)
509 break;
511 if (redirect){
512 if(u->redirect == nil)
513 sysfatal("redirect: no URL");
514 if(crackurl(u, u->redirect) < 0)
515 sysfatal("redirect: %r");
519 /* transfer whatever you get */
520 if(ofile != nil && u->mtime != 0){
521 note.fd = out->fd;
522 note.mtime = u->mtime;
523 notify(catch);
526 tot = 0;
527 vtime = 0;
528 for(;;){
529 n = readibuf(fd, buf, sizeof(buf));
530 if(n <= 0)
531 break;
532 if(output(out, buf, n) != n)
533 break;
534 tot += n;
535 if(verbose && vtime != time(0)) {
536 vtime = time(0);
537 fprint(2, "%ld %ld\n", r->start+tot, r->end);
540 notify(nil);
541 close(fd);
543 if(ofile != nil && u->mtime != 0){
544 Dir d;
546 rerrstr(err, sizeof err);
547 nulldir(&d);
548 d.mtime = u->mtime;
549 if(dirfwstat(out->fd, &d) < 0)
550 fprint(2, "couldn't set mtime: %r\n");
551 errstr(err, sizeof err);
554 return tot;
557 /* get the http response code */
558 int
559 httprcode(int fd)
561 int n;
562 char *p;
563 char buf[256];
565 n = readline(fd, buf, sizeof(buf)-1);
566 if(n <= 0)
567 return n;
568 if(debug)
569 fprint(2, "%d <- %s\n", fd, buf);
570 p = strchr(buf, ' ');
571 if(strncmp(buf, "HTTP/", 5) != 0 || p == nil){
572 werrstr("bad response from server");
573 return -1;
575 buf[n] = 0;
576 return atoi(p+1);
579 /* read in and crack the http headers, update u and r */
580 void hhetag(char*, URL*, Range*);
581 void hhmtime(char*, URL*, Range*);
582 void hhclen(char*, URL*, Range*);
583 void hhcrange(char*, URL*, Range*);
584 void hhuri(char*, URL*, Range*);
585 void hhlocation(char*, URL*, Range*);
586 void hhauth(char*, URL*, Range*);
588 struct {
589 char *name;
590 void (*f)(char*, URL*, Range*);
591 } headers[] = {
592 { "etag:", hhetag },
593 { "last-modified:", hhmtime },
594 { "content-length:", hhclen },
595 { "content-range:", hhcrange },
596 { "uri:", hhuri },
597 { "location:", hhlocation },
598 { "WWW-Authenticate:", hhauth },
599 };
600 int
601 httpheaders(int fd, int cfd, URL *u, Range *r)
603 char buf[2048];
604 char *p;
605 int i, n;
607 for(;;){
608 n = getheader(fd, buf, sizeof(buf));
609 if(n <= 0)
610 break;
611 if(cfd >= 0)
612 fprint(cfd, "%s\n", buf);
613 for(i = 0; i < nelem(headers); i++){
614 n = strlen(headers[i].name);
615 if(cistrncmp(buf, headers[i].name, n) == 0){
616 /* skip field name and leading white */
617 p = buf + n;
618 while(*p == ' ' || *p == '\t')
619 p++;
621 (*headers[i].f)(p, u, r);
622 break;
626 return n;
629 /*
630 * read a single mime header, collect continuations.
632 * this routine assumes that there is a blank line twixt
633 * the header and the message body, otherwise bytes will
634 * be lost.
635 */
636 int
637 getheader(int fd, char *buf, int n)
639 char *p, *e;
640 int i;
642 n--;
643 p = buf;
644 for(e = p + n; ; p += i){
645 i = readline(fd, p, e-p);
646 if(i < 0)
647 return i;
649 if(p == buf){
650 /* first line */
651 if(strchr(buf, ':') == nil)
652 break; /* end of headers */
653 } else {
654 /* continuation line */
655 if(*p != ' ' && *p != '\t'){
656 unreadline(p);
657 *p = 0;
658 break; /* end of this header */
662 if(headerprint)
663 print("%s\n", buf);
665 if(debug)
666 fprint(2, "%d <- %s\n", fd, buf);
667 return p-buf;
670 void
671 hhetag(char *p, URL *u, Range *r)
673 USED(r);
675 if(u->etag != nil){
676 if(strcmp(u->etag, p) != 0)
677 sysfatal("file changed underfoot");
678 } else
679 u->etag = strdup(p);
682 char* monthchars = "janfebmaraprmayjunjulaugsepoctnovdec";
684 void
685 hhmtime(char *p, URL *u, Range *r)
687 char *month, *day, *yr, *hms;
688 char *fields[6];
689 Tm tm, now;
690 int i;
692 USED(r);
694 i = getfields(p, fields, 6, 1, " \t");
695 if(i < 5)
696 return;
698 day = fields[1];
699 month = fields[2];
700 yr = fields[3];
701 hms = fields[4];
703 /* default time */
704 now = *gmtime(time(0));
705 tm = now;
706 tm.yday = 0;
708 /* convert ascii month to a number twixt 1 and 12 */
709 if(*month >= '0' && *month <= '9'){
710 tm.mon = atoi(month) - 1;
711 if(tm.mon < 0 || tm.mon > 11)
712 tm.mon = 5;
713 } else {
714 for(p = month; *p; p++)
715 *p = tolower((uchar)*p);
716 for(i = 0; i < 12; i++)
717 if(strncmp(&monthchars[i*3], month, 3) == 0){
718 tm.mon = i;
719 break;
723 tm.mday = atoi(day);
725 if(hms) {
726 tm.hour = strtoul(hms, &p, 10);
727 if(*p == ':') {
728 p++;
729 tm.min = strtoul(p, &p, 10);
730 if(*p == ':') {
731 p++;
732 tm.sec = strtoul(p, &p, 10);
735 if(tolower((uchar)*p) == 'p')
736 tm.hour += 12;
739 if(yr) {
740 tm.year = atoi(yr);
741 if(tm.year >= 1900)
742 tm.year -= 1900;
743 } else {
744 if(tm.mon > now.mon || (tm.mon == now.mon && tm.mday > now.mday+1))
745 tm.year--;
748 strcpy(tm.zone, "GMT");
749 /* convert to epoch seconds */
750 u->mtime = tm2sec(&tm);
753 void
754 hhclen(char *p, URL *u, Range *r)
756 USED(u);
758 r->end = atoi(p);
761 void
762 hhcrange(char *p, URL *u, Range *r)
764 char *x;
765 vlong l;
767 USED(u);
768 l = 0;
769 x = strchr(p, '/');
770 if(x)
771 l = atoll(x+1);
772 if(l == 0)
773 x = strchr(p, '-');
774 if(x)
775 l = atoll(x+1);
776 if(l)
777 r->end = l;
780 void
781 hhuri(char *p, URL *u, Range *r)
783 USED(r);
785 if(*p != '<')
786 return;
787 u->redirect = strdup(p+1);
788 p = strchr(u->redirect, '>');
789 if(p != nil)
790 *p = 0;
793 void
794 hhlocation(char *p, URL *u, Range *r)
796 USED(r);
798 u->redirect = strdup(p);
801 void
802 hhauth(char *p, URL *u, Range *r)
804 char *f[4];
805 UserPasswd *up;
806 char *s, cred[64];
808 USED(r);
810 if (cistrncmp(p, "basic ", 6) != 0)
811 sysfatal("only Basic authentication supported");
813 if (gettokens(p, f, nelem(f), "\"") < 2)
814 sysfatal("garbled auth data");
816 if ((up = auth_getuserpasswd(auth_getkey, "proto=pass service=http dom=%q relm=%q",
817 u->host, f[1])) == nil)
818 sysfatal("cannot authenticate");
820 s = smprint("%s:%s", up->user, up->passwd);
821 if(enc64(cred, sizeof(cred), (uchar *)s, strlen(s)) == -1)
822 sysfatal("enc64");
823 free(s);
825 assert(u->cred = strdup(cred));
828 enum
830 /* ftp return codes */
831 Extra= 1,
832 Success= 2,
833 Incomplete= 3,
834 TempFail= 4,
835 PermFail= 5,
837 Nnetdir= 64, /* max length of network directory paths */
838 Ndialstr= 64, /* max length of dial strings */
839 };
841 int ftpcmd(int, char*, ...);
842 int ftprcode(int, char*, int);
843 int hello(int);
844 int logon(int);
845 int xfertype(int, char*);
846 int passive(int, URL*);
847 int active(int, URL*);
848 int ftpxfer(int, Out*, Range*);
849 int terminateftp(int, int);
850 int getaddrport(char*, uchar*, uchar*);
851 int ftprestart(int, Out*, URL*, Range*, long);
853 int
854 doftp(URL *u, URL *px, Range *r, Out *out, long mtime)
856 int pid, ctl, data, rv;
857 Waitmsg *w;
858 char msg[64];
859 char conndir[NETPATHLEN];
860 char *p;
862 /* untested, proxy dosn't work with ftp (I think) */
863 if(px->host == nil){
864 ctl = dial(netmkaddr(u->host, tcpdir, u->port), 0, conndir, 0);
865 } else {
866 ctl = dial(netmkaddr(px->host, tcpdir, px->port), 0, conndir, 0);
869 if(ctl < 0)
870 return Error;
871 if(net == nil){
872 p = strrchr(conndir, '/');
873 *p = 0;
874 snprint(tcpdir, sizeof(tcpdir), conndir);
877 initibuf();
879 rv = hello(ctl);
880 if(rv < 0)
881 return terminateftp(ctl, rv);
883 rv = logon(ctl);
884 if(rv < 0)
885 return terminateftp(ctl, rv);
887 rv = xfertype(ctl, "I");
888 if(rv < 0)
889 return terminateftp(ctl, rv);
891 /* if file is up to date and the right size, stop */
892 if(ftprestart(ctl, out, u, r, mtime) > 0){
893 close(ctl);
894 return Eof;
897 /* first try passive mode, then active */
898 data = passive(ctl, u);
899 if(data < 0){
900 data = active(ctl, u);
901 if(data < 0)
902 return Error;
905 /* fork */
906 switch(pid = fork()){
907 case -1:
908 close(data);
909 return terminateftp(ctl, Error);
910 case 0:
911 ftpxfer(data, out, r);
912 close(data);
913 #undef _exits
914 _exits(0);
915 default:
916 close(data);
917 break;
920 /* wait for reply message */
921 rv = ftprcode(ctl, msg, sizeof(msg));
922 close(ctl);
924 /* wait for process to terminate */
925 w = nil;
926 for(;;){
927 free(w);
928 w = wait();
929 if(w == nil)
930 return Error;
931 if(w->pid == pid){
932 if(w->msg[0] == 0){
933 free(w);
934 break;
936 werrstr("xfer: %s", w->msg);
937 free(w);
938 return Error;
942 switch(rv){
943 case Success:
944 return Eof;
945 case TempFail:
946 return Server;
947 default:
948 return Error;
952 int
953 ftpcmd(int ctl, char *fmt, ...)
955 va_list arg;
956 char buf[2*1024], *s;
958 va_start(arg, fmt);
959 s = vseprint(buf, buf + (sizeof(buf)-4) / sizeof(*buf), fmt, arg);
960 va_end(arg);
961 if(debug)
962 fprint(2, "%d -> %s\n", ctl, buf);
963 *s++ = '\r';
964 *s++ = '\n';
965 if(write(ctl, buf, s - buf) != s - buf)
966 return -1;
967 return 0;
970 int
971 ftprcode(int ctl, char *msg, int len)
973 int rv;
974 int i;
975 char *p;
977 len--; /* room for terminating null */
978 for(;;){
979 *msg = 0;
980 i = readline(ctl, msg, len);
981 if(i < 0)
982 break;
983 if(debug)
984 fprint(2, "%d <- %s\n", ctl, msg);
986 /* stop if not a continuation */
987 rv = strtol(msg, &p, 10);
988 if(rv >= 100 && rv < 600 && p==msg+3 && *p == ' ')
989 return rv/100;
991 *msg = 0;
993 return -1;
996 int
997 hello(int ctl)
999 char msg[1024];
1001 /* wait for hello from other side */
1002 if(ftprcode(ctl, msg, sizeof(msg)) != Success){
1003 werrstr("HELLO: %s", msg);
1004 return Server;
1006 return 0;
1009 int
1010 getdec(char *p, int n)
1012 int x = 0;
1013 int i;
1015 for(i = 0; i < n; i++)
1016 x = x*10 + (*p++ - '0');
1017 return x;
1020 int
1021 ftprestart(int ctl, Out *out, URL *u, Range *r, long mtime)
1023 Tm tm;
1024 char msg[1024];
1025 long x, rmtime;
1027 ftpcmd(ctl, "MDTM %s", u->page);
1028 if(ftprcode(ctl, msg, sizeof(msg)) != Success){
1029 r->start = 0;
1030 return 0; /* need to do something */
1033 /* decode modification time */
1034 if(strlen(msg) < 4 + 4 + 2 + 2 + 2 + 2 + 2){
1035 r->start = 0;
1036 return 0; /* need to do something */
1038 memset(&tm, 0, sizeof(tm));
1039 tm.year = getdec(msg+4, 4) - 1900;
1040 tm.mon = getdec(msg+4+4, 2) - 1;
1041 tm.mday = getdec(msg+4+4+2, 2);
1042 tm.hour = getdec(msg+4+4+2+2, 2);
1043 tm.min = getdec(msg+4+4+2+2+2, 2);
1044 tm.sec = getdec(msg+4+4+2+2+2+2, 2);
1045 strcpy(tm.zone, "GMT");
1046 rmtime = tm2sec(&tm);
1047 if(rmtime > mtime)
1048 r->start = 0;
1050 /* get size */
1051 ftpcmd(ctl, "SIZE %s", u->page);
1052 if(ftprcode(ctl, msg, sizeof(msg)) == Success){
1053 x = atol(msg+4);
1054 if(r->start == x)
1055 return 1; /* we're up to date */
1056 r->end = x;
1059 /* seek to restart point */
1060 if(r->start > 0){
1061 ftpcmd(ctl, "REST %lud", r->start);
1062 if(ftprcode(ctl, msg, sizeof(msg)) == Incomplete){
1063 setoffset(out, r->start);
1064 }else
1065 r->start = 0;
1068 return 0; /* need to do something */
1071 int
1072 logon(int ctl)
1074 char msg[1024];
1076 /* login anonymous */
1077 ftpcmd(ctl, "USER anonymous");
1078 switch(ftprcode(ctl, msg, sizeof(msg))){
1079 case Success:
1080 return 0;
1081 case Incomplete:
1082 break; /* need password */
1083 default:
1084 werrstr("USER: %s", msg);
1085 return Server;
1088 /* send user id as password */
1089 sprint(msg, "%s@closedmind.org", getuser());
1090 ftpcmd(ctl, "PASS %s", msg);
1091 if(ftprcode(ctl, msg, sizeof(msg)) != Success){
1092 werrstr("PASS: %s", msg);
1093 return Server;
1096 return 0;
1099 int
1100 xfertype(int ctl, char *t)
1102 char msg[1024];
1104 ftpcmd(ctl, "TYPE %s", t);
1105 if(ftprcode(ctl, msg, sizeof(msg)) != Success){
1106 werrstr("TYPE %s: %s", t, msg);
1107 return Server;
1110 return 0;
1113 int
1114 passive(int ctl, URL *u)
1116 char msg[1024];
1117 char ipaddr[32];
1118 char *f[6];
1119 char *p;
1120 int fd;
1121 int port;
1122 char aport[12];
1124 ftpcmd(ctl, "PASV");
1125 if(ftprcode(ctl, msg, sizeof(msg)) != Success)
1126 return Error;
1128 /* get address and port number from reply, this is AI */
1129 p = strchr(msg, '(');
1130 if(p == nil){
1131 for(p = msg+3; *p; p++)
1132 if(isdigit((uchar)*p))
1133 break;
1134 } else
1135 p++;
1136 if(getfields(p, f, 6, 0, ",)") < 6){
1137 werrstr("ftp protocol botch");
1138 return Server;
1140 snprint(ipaddr, sizeof(ipaddr), "%s.%s.%s.%s",
1141 f[0], f[1], f[2], f[3]);
1142 port = ((atoi(f[4])&0xff)<<8) + (atoi(f[5])&0xff);
1143 sprint(aport, "%d", port);
1145 /* open data connection */
1146 fd = dial(netmkaddr(ipaddr, tcpdir, aport), 0, 0, 0);
1147 if(fd < 0){
1148 werrstr("passive mode failed: %r");
1149 return Error;
1152 /* tell remote to send a file */
1153 ftpcmd(ctl, "RETR %s", u->page);
1154 if(ftprcode(ctl, msg, sizeof(msg)) != Extra){
1155 werrstr("RETR %s: %s", u->page, msg);
1156 return Error;
1158 return fd;
1161 int
1162 active(int ctl, URL *u)
1164 char msg[1024];
1165 char dir[40], ldir[40];
1166 uchar ipaddr[4];
1167 uchar port[2];
1168 int lcfd, dfd, afd;
1170 /* announce a port for the call back */
1171 snprint(msg, sizeof(msg), "%s!*!0", tcpdir);
1172 afd = announce(msg, dir);
1173 if(afd < 0)
1174 return Error;
1176 /* get a local address/port of the annoucement */
1177 if(getaddrport(dir, ipaddr, port) < 0){
1178 close(afd);
1179 return Error;
1182 /* tell remote side address and port*/
1183 ftpcmd(ctl, "PORT %d,%d,%d,%d,%d,%d", ipaddr[0], ipaddr[1], ipaddr[2],
1184 ipaddr[3], port[0], port[1]);
1185 if(ftprcode(ctl, msg, sizeof(msg)) != Success){
1186 close(afd);
1187 werrstr("active: %s", msg);
1188 return Error;
1191 /* tell remote to send a file */
1192 ftpcmd(ctl, "RETR %s", u->page);
1193 if(ftprcode(ctl, msg, sizeof(msg)) != Extra){
1194 close(afd);
1195 werrstr("RETR: %s", msg);
1196 return Server;
1199 /* wait for a connection */
1200 lcfd = listen(dir, ldir);
1201 if(lcfd < 0){
1202 close(afd);
1203 return Error;
1205 dfd = accept(lcfd, ldir);
1206 if(dfd < 0){
1207 close(afd);
1208 close(lcfd);
1209 return Error;
1211 close(afd);
1212 close(lcfd);
1214 return dfd;
1217 int
1218 ftpxfer(int in, Out *out, Range *r)
1220 char buf[1024];
1221 long vtime;
1222 int i, n;
1224 vtime = 0;
1225 for(n = 0;;n += i){
1226 i = read(in, buf, sizeof(buf));
1227 if(i == 0)
1228 break;
1229 if(i < 0)
1230 return Error;
1231 if(output(out, buf, i) != i)
1232 return Error;
1233 r->start += i;
1234 if(verbose && vtime != time(0)) {
1235 vtime = time(0);
1236 fprint(2, "%ld %ld\n", r->start, r->end);
1239 return n;
1242 int
1243 terminateftp(int ctl, int rv)
1245 close(ctl);
1246 return rv;
1250 * case insensitive strcmp (why aren't these in libc?)
1252 int
1253 cistrncmp(char *a, char *b, int n)
1255 while(n-- > 0){
1256 if(tolower((uchar)*a++) != tolower((uchar)*b++))
1257 return -1;
1259 return 0;
1262 int
1263 cistrcmp(char *a, char *b)
1265 while(*a || *b)
1266 if(tolower((uchar)*a++) != tolower((uchar)*b++))
1267 return -1;
1269 return 0;
1273 * buffered io
1275 struct
1277 char *rp;
1278 char *wp;
1279 char buf[4*1024];
1280 } b;
1282 void
1283 initibuf(void)
1285 b.rp = b.wp = b.buf;
1289 * read a possibly buffered line, strip off trailing while
1291 int
1292 readline(int fd, char *buf, int len)
1294 int n;
1295 char *p;
1296 int eof = 0;
1298 len--;
1300 for(p = buf;;){
1301 if(b.rp >= b.wp){
1302 n = read(fd, b.wp, sizeof(b.buf)/2);
1303 if(n < 0)
1304 return -1;
1305 if(n == 0){
1306 eof = 1;
1307 break;
1309 b.wp += n;
1311 n = *b.rp++;
1312 if(len > 0){
1313 *p++ = n;
1314 len--;
1316 if(n == '\n')
1317 break;
1320 /* drop trailing white */
1321 for(;;){
1322 if(p <= buf)
1323 break;
1324 n = *(p-1);
1325 if(n != ' ' && n != '\t' && n != '\r' && n != '\n')
1326 break;
1327 p--;
1329 *p = 0;
1331 if(eof && p == buf)
1332 return -1;
1334 return p-buf;
1337 void
1338 unreadline(char *line)
1340 int i, n;
1342 i = strlen(line);
1343 n = b.wp-b.rp;
1344 memmove(&b.buf[i+1], b.rp, n);
1345 memmove(b.buf, line, i);
1346 b.buf[i] = '\n';
1347 b.rp = b.buf;
1348 b.wp = b.rp + i + 1 + n;
1351 int
1352 readibuf(int fd, char *buf, int len)
1354 int n;
1356 n = b.wp-b.rp;
1357 if(n > 0){
1358 if(n > len)
1359 n = len;
1360 memmove(buf, b.rp, n);
1361 b.rp += n;
1362 return n;
1364 return read(fd, buf, len);
1367 int
1368 dfprint(int fd, char *fmt, ...)
1370 char buf[4*1024];
1371 va_list arg;
1373 va_start(arg, fmt);
1374 vseprint(buf, buf+sizeof(buf), fmt, arg);
1375 va_end(arg);
1376 if(debug)
1377 fprint(2, "%d -> %s", fd, buf);
1378 return fprint(fd, "%s", buf);
1381 int
1382 getaddrport(char *dir, uchar *ipaddr, uchar *port)
1384 char buf[256];
1385 int fd, i;
1386 char *p;
1388 snprint(buf, sizeof(buf), "%s/local", dir);
1389 fd = open(buf, OREAD);
1390 if(fd < 0)
1391 return -1;
1392 i = read(fd, buf, sizeof(buf)-1);
1393 close(fd);
1394 if(i <= 0)
1395 return -1;
1396 buf[i] = 0;
1397 p = strchr(buf, '!');
1398 if(p != nil)
1399 *p++ = 0;
1400 v4parseip(ipaddr, buf);
1401 i = atoi(p);
1402 port[0] = i>>8;
1403 port[1] = i;
1404 return 0;
1407 void
1408 md5free(DigestState *state)
1410 uchar x[MD5dlen];
1411 md5(nil, 0, x, state);
1414 DigestState*
1415 md5dup(DigestState *state)
1417 DigestState *s2;
1419 s2 = malloc(sizeof(DigestState));
1420 if(s2 == nil)
1421 sysfatal("malloc: %r");
1422 *s2 = *state;
1423 s2->malloced = 1;
1424 return s2;
1427 void
1428 setoffset(Out *out, int offset)
1430 md5free(out->curr);
1431 if(offset == 0)
1432 out->curr = md5(nil, 0, nil, nil);
1433 else
1434 out->curr = nil;
1435 out->offset = offset;
1439 * write some output, discarding it (but keeping track)
1440 * if we've already written it. if we've gone backwards,
1441 * verify that everything previously written matches
1442 * that which would have been written from the current
1443 * output.
1445 int
1446 output(Out *out, char *buf, int nb)
1448 int n, d;
1449 uchar m0[MD5dlen], m1[MD5dlen];
1451 n = nb;
1452 d = out->written - out->offset;
1453 assert(d >= 0);
1454 if(d > 0){
1455 if(n < d){
1456 if(out->curr != nil)
1457 md5((uchar*)buf, n, nil, out->curr);
1458 out->offset += n;
1459 return n;
1461 if(out->curr != nil){
1462 md5((uchar*)buf, d, m0, out->curr);
1463 out->curr = nil;
1464 md5(nil, 0, m1, md5dup(out->hiwat));
1465 if(memcmp(m0, m1, MD5dlen) != 0){
1466 fprint(2, "integrity check failure at offset %d\n", out->written);
1467 return -1;
1470 buf += d;
1471 n -= d;
1472 out->offset += d;
1474 if(n > 0){
1475 out->hiwat = md5((uchar*)buf, n, nil, out->hiwat);
1476 n = write(out->fd, buf, n);
1477 if(n > 0){
1478 out->offset += n;
1479 out->written += n;
1482 return n + d;