diff options
author | rsc <devnull@localhost> | 2005-10-29 16:26:44 +0000 |
---|---|---|
committer | rsc <devnull@localhost> | 2005-10-29 16:26:44 +0000 |
commit | 5cdb17983ae6e6367ad7a940cb219eab247a9304 (patch) | |
tree | 8ca1ef49af2a96e7daebe624d91fdf679814a057 /src/cmd/upas/marshal/marshal.c | |
parent | cd3745196389579fb78b9b01ef1daefb5a57aa71 (diff) | |
download | plan9port-5cdb17983ae6e6367ad7a940cb219eab247a9304.tar.gz plan9port-5cdb17983ae6e6367ad7a940cb219eab247a9304.tar.bz2 plan9port-5cdb17983ae6e6367ad7a940cb219eab247a9304.zip |
Thanks to John Cummings.
Diffstat (limited to 'src/cmd/upas/marshal/marshal.c')
-rw-r--r-- | src/cmd/upas/marshal/marshal.c | 1857 |
1 files changed, 1857 insertions, 0 deletions
diff --git a/src/cmd/upas/marshal/marshal.c b/src/cmd/upas/marshal/marshal.c new file mode 100644 index 00000000..cfc186c0 --- /dev/null +++ b/src/cmd/upas/marshal/marshal.c @@ -0,0 +1,1857 @@ +#include "common.h" +#include <ctype.h> + +typedef struct Attach Attach; +typedef struct Alias Alias; +typedef struct Addr Addr; +typedef struct Ctype Ctype; + +struct Attach { + Attach *next; + char *path; + char *type; + int tinline; + Ctype *ctype; +}; + +struct Alias +{ + Alias *next; + int n; + Addr *addr; +}; + +struct Addr +{ + Addr *next; + char *v; +}; + +enum { + Hfrom, + Hto, + Hcc, + Hbcc, + Hsender, + Hreplyto, + Hinreplyto, + Hdate, + Hsubject, + Hmime, + Hpriority, + Hmsgid, + Hcontent, + Hx, + Hprecedence, + Nhdr, +}; + +enum { + PGPsign = 1, + PGPencrypt = 2, +}; + +char *hdrs[Nhdr] = { +[Hfrom] "from:", +[Hto] "to:", +[Hcc] "cc:", +[Hbcc] "bcc:", +[Hreplyto] "reply-to:", +[Hinreplyto] "in-reply-to:", +[Hsender] "sender:", +[Hdate] "date:", +[Hsubject] "subject:", +[Hpriority] "priority:", +[Hmsgid] "message-id:", +[Hmime] "mime-", +[Hcontent] "content-", +[Hx] "x-", +[Hprecedence] "precedence", +}; + +struct Ctype { + char *type; + char *ext; + int display; +}; + +Ctype ctype[] = { + { "text/plain", "txt", 1, }, + { "text/html", "html", 1, }, + { "text/html", "htm", 1, }, + { "text/tab-separated-values", "tsv", 1, }, + { "text/richtext", "rtx", 1, }, + { "message/rfc822", "txt", 1, }, + { "", 0, 0, }, +}; + +Ctype *mimetypes; + +int pid = -1; +int pgppid = -1; + +Attach* mkattach(char*, char*, int); +int readheaders(Biobuf*, int*, String**, Addr**, int); +void body(Biobuf*, Biobuf*, int); +char* mkboundary(void); +int printdate(Biobuf*); +int printfrom(Biobuf*); +int printto(Biobuf*, Addr*); +int printcc(Biobuf*, Addr*); +int printsubject(Biobuf*, char*); +int printinreplyto(Biobuf*, char*); +int sendmail(Addr*, Addr*, int*, char*); +void attachment(Attach*, Biobuf*); +int cistrncmp(char*, char*, int); +int cistrcmp(char*, char*); +char* waitforsubprocs(void); +int enc64(char*, int, uchar*, int); +Addr* expand(int, char**); +Alias* readaliases(void); +Addr* expandline(String**, Addr*); +void Bdrain(Biobuf*); +void freeaddr(Addr *); +int pgpopts(char*); +int pgpfilter(int*, int, int); +void readmimetypes(void); +char* estrdup(char*); +void* emalloc(int); +void* erealloc(void*, int); +void freeaddr(Addr*); +void freeaddrs(Addr*); +void freealias(Alias*); +void freealiases(Alias*); +int doublequote(Fmt*); + +int rflag, lbflag, xflag, holding, nflag, Fflag, eightflag, dflag; +int pgpflag = 0; +char *user; +char *login; +Alias *aliases; +int rfc822syntaxerror; +char lastchar; +char *replymsg; + +enum +{ + Ok = 0, + Nomessage = 1, + Nobody = 2, + Error = -1, +}; + +#pragma varargck type "Z" char* + +void +usage(void) +{ + fprint(2, "usage: %s [-Fr#xn] [-s subject] [-c ccrecipient] [-t type] [-aA attachment] [-p[es]] [-R replymsg] -8 | recipient-list\n", + argv0); + exits("usage"); +} + +void +fatal(char *fmt, ...) +{ + char buf[1024]; + va_list arg; + + if(pid >= 0) + postnote(PNPROC, pid, "die"); + if(pgppid >= 0) + postnote(PNPROC, pgppid, "die"); + + va_start(arg, fmt); + vseprint(buf, buf+sizeof(buf), fmt, arg); + va_end(arg); + fprint(2, "%s: %s\n", argv0, buf); + holdoff(holding); + exits(buf); +} + +void +main(int argc, char **argv) +{ + Attach *first, **l, *a; + char *subject, *type, *boundary; + int flags, fd; + Biobuf in, out, *b; + Addr *to; + Addr *cc; + String *file, *hdrstring; + int noinput, headersrv; + int ccargc; + char *ccargv[32]; + + noinput = 0; + subject = nil; + first = nil; + l = &first; + type = nil; + hdrstring = nil; + ccargc = 0; + + quotefmtinstall(); + fmtinstall('Z', doublequote); + + ARGBEGIN{ + case 't': + type = ARGF(); + if(type == nil) + usage(); + break; + case 'a': + flags = 0; + goto aflag; + case 'A': + flags = 1; + aflag: + a = mkattach(ARGF(), type, flags); + if(a == nil) + exits("bad args"); + type = nil; + *l = a; + l = &a->next; + break; + case 'C': + if(ccargc >= nelem(ccargv)-1) + sysfatal("too many cc's"); + ccargv[ccargc] = ARGF(); + if(ccargv[ccargc] == nil) + usage(); + ccargc++; + break; + case 'R': + replymsg = ARGF(); + break; + case 's': + subject = ARGF(); + break; + case 'F': + Fflag = 1; // file message + break; + case 'r': + rflag = 1; // for sendmail + break; + case 'd': + dflag = 1; // for sendmail + break; + case '#': + lbflag = 1; // for sendmail + break; + case 'x': + xflag = 1; // for sendmail + break; + case 'n': // no standard input + nflag = 1; + break; + case '8': // read recipients from rfc822 header + eightflag = 1; + break; + case 'p': // pgp flag: encrypt, sign, or both + if(pgpopts(ARGF()) < 0) + sysfatal("bad pgp options"); + break; + default: + usage(); + break; + }ARGEND; + + login = getlog(); + user = getenv("upasname"); + if(user == nil || *user == 0) + user = login; + if(user == nil || *user == 0) + sysfatal("can't read user name"); + + if(Binit(&in, 0, OREAD) < 0) + sysfatal("can't Binit 0: %r"); + + if(nflag && eightflag) + sysfatal("can't use both -n and -8"); + if(eightflag && argc >= 1) + usage(); + else if(!eightflag && argc < 1) + usage(); + + aliases = readaliases(); + if(!eightflag){ + to = expand(argc, argv); + cc = expand(ccargc, ccargv); + } else { + to = nil; + cc = nil; + } + + flags = 0; + headersrv = Nomessage; + if(!nflag && !xflag && !lbflag &&!dflag) { + // pass through headers, keeping track of which we've seen, + // perhaps building to list. + holding = holdon(); + headersrv = readheaders(&in, &flags, &hdrstring, eightflag ? &to : nil, 1); + if(rfc822syntaxerror){ + Bdrain(&in); + fatal("rfc822 syntax error, message not sent"); + } + if(to == nil){ + Bdrain(&in); + fatal("no addresses found, message not sent"); + } + + switch(headersrv){ + case Error: // error + fatal("reading"); + break; + case Nomessage: // no message, just exit mimicking old behavior + noinput = 1; + if(first == nil) + exits(0); + break; + } + } + + fd = sendmail(to, cc, &pid, Fflag ? argv[0] : nil); + if(fd < 0) + sysfatal("execing sendmail: %r\n:"); + if(xflag || lbflag || dflag){ + close(fd); + exits(waitforsubprocs()); + } + + if(Binit(&out, fd, OWRITE) < 0) + fatal("can't Binit 1: %r"); + + if(!nflag){ + if(Bwrite(&out, s_to_c(hdrstring), s_len(hdrstring)) != s_len(hdrstring)) + fatal("write error"); + s_free(hdrstring); + hdrstring = nil; + + // read user's standard headers + file = s_new(); + mboxpath("headers", user, file, 0); + b = Bopen(s_to_c(file), OREAD); + if(b != nil){ + switch(readheaders(b, &flags, &hdrstring, nil, 0)){ + case Error: // error + fatal("reading"); + } + Bterm(b); + if(Bwrite(&out, s_to_c(hdrstring), s_len(hdrstring)) != s_len(hdrstring)) + fatal("write error"); + s_free(hdrstring); + hdrstring = nil; + } + } + + // add any headers we need + if((flags & (1<<Hdate)) == 0) + if(printdate(&out) < 0) + fatal("writing"); + if((flags & (1<<Hfrom)) == 0) + if(printfrom(&out) < 0) + fatal("writing"); + if((flags & (1<<Hto)) == 0) + if(printto(&out, to) < 0) + fatal("writing"); + if((flags & (1<<Hcc)) == 0) + if(printcc(&out, cc) < 0) + fatal("writing"); + if((flags & (1<<Hsubject)) == 0 && subject != nil) + if(printsubject(&out, subject) < 0) + fatal("writing"); + if(replymsg != nil) + if(printinreplyto(&out, replymsg) < 0) + fatal("writing"); + Bprint(&out, "MIME-Version: 1.0\n"); + + if(pgpflag){ // interpose pgp process between us and sendmail to handle body + Bflush(&out); + Bterm(&out); + fd = pgpfilter(&pgppid, fd, pgpflag); + if(Binit(&out, fd, OWRITE) < 0) + fatal("can't Binit 1: %r"); + } + + // if attachments, stick in multipart headers + boundary = nil; + if(first != nil){ + boundary = mkboundary(); + Bprint(&out, "Content-Type: multipart/mixed;\n"); + Bprint(&out, "\tboundary=\"%s\"\n\n", boundary); + Bprint(&out, "This is a multi-part message in MIME format.\n"); + Bprint(&out, "--%s\n", boundary); + Bprint(&out, "Content-Disposition: inline\n"); + } + + if(!nflag){ + if(!noinput && headersrv == Ok){ + body(&in, &out, 1); + } + } else + Bprint(&out, "\n"); + holdoff(holding); + + Bflush(&out); + for(a = first; a != nil; a = a->next){ + if(lastchar != '\n') + Bprint(&out, "\n"); + Bprint(&out, "--%s\n", boundary); + attachment(a, &out); + } + + if(first != nil){ + if(lastchar != '\n') + Bprint(&out, "\n"); + Bprint(&out, "--%s--\n", boundary); + } + + Bterm(&out); + close(fd); + exits(waitforsubprocs()); +} + +// evaluate pgp option string +int +pgpopts(char *s) +{ + if(s == nil || s[0] == '\0') + return -1; + while(*s){ + switch(*s++){ + case 's': case 'S': + pgpflag |= PGPsign; + break; + case 'e': case 'E': + pgpflag |= PGPencrypt; + break; + default: + return -1; + } + } + return 0; +} + +// read headers from stdin into a String, expanding local aliases, +// keep track of which headers are there, which addresses we have +// remove Bcc: line. +int +readheaders(Biobuf *in, int *fp, String **sp, Addr **top, int strict) +{ + Addr *to; + String *s, *sline; + char *p; + int i, seen, hdrtype; + + s = s_new(); + sline = nil; + to = nil; + hdrtype = -1; + seen = 0; + for(;;) { + if((p = Brdline(in, '\n')) != nil) { + seen = 1; + p[Blinelen(in)-1] = 0; + + // coalesce multiline headers + if((*p == ' ' || *p == '\t') && sline){ + s_append(sline, "\n"); + s_append(sline, p); + p[Blinelen(in)-1] = '\n'; + continue; + } + } + + // process the current header, it's all been read + if(sline) { + assert(hdrtype != -1); + if(top){ + switch(hdrtype){ + case Hto: + case Hcc: + case Hbcc: + to = expandline(&sline, to); + break; + } + } + if(top==nil || hdrtype!=Hbcc){ + s_append(s, s_to_c(sline)); + s_append(s, "\n"); + } + s_free(sline); + sline = nil; + } + + if(p == nil) + break; + + // if no :, it's not a header, seek back and break + if(strchr(p, ':') == nil){ + p[Blinelen(in)-1] = '\n'; + Bseek(in, -Blinelen(in), 1); + break; + } + + sline = s_copy(p); + + // classify the header. If we don't recognize it, break. This is + // to take care of user's that start messages with lines that contain + // ':'s but that aren't headers. This is a bit hokey. Since I decided + // to let users type headers, I need some way to distinguish. Therefore, + // marshal tries to know all likely headers and will indeed screw up if + // the user types an unlikely one. -- presotto + hdrtype = -1; + for(i = 0; i < nelem(hdrs); i++){ + if(cistrncmp(hdrs[i], p, strlen(hdrs[i])) == 0){ + *fp |= 1<<i; + hdrtype = i; + break; + } + } + if(strict){ + if(hdrtype == -1){ + p[Blinelen(in)-1] = '\n'; + Bseek(in, -Blinelen(in), 1); + break; + } + } else + hdrtype = 0; + p[Blinelen(in)-1] = '\n'; + } + + *sp = s; + if(top) + *top = to; + + if(seen == 0){ + if(Blinelen(in) == 0) + return Nomessage; + else + return Ok; + } + if(p == nil) + return Nobody; + return Ok; +} + +// pass the body to sendmail, make sure body starts and ends with a newline +void +body(Biobuf *in, Biobuf *out, int docontenttype) +{ + char *buf, *p; + int i, n, len; + + n = 0; + len = 16*1024; + buf = emalloc(len); + + // first char must be newline + i = Bgetc(in); + if(i > 0){ + if(i != '\n') + buf[n++] = '\n'; + buf[n++] = i; + } else { + buf[n++] = '\n'; + } + + // read into memory + if(docontenttype){ + while(docontenttype){ + if(n == len){ + len += len>>2; + buf = realloc(buf, len); + if(buf == nil) + sysfatal("%r"); + } + p = buf+n; + i = Bread(in, p, len - n); + if(i < 0) + fatal("input error2"); + if(i == 0) + break; + n += i; + for(; i > 0; i--) + if((*p++ & 0x80) && docontenttype){ + Bprint(out, "Content-Type: text/plain; charset=\"UTF-8\"\n"); + Bprint(out, "Content-Transfer-Encoding: 8bit\n"); + docontenttype = 0; + break; + } + } + if(docontenttype){ + Bprint(out, "Content-Type: text/plain; charset=\"US-ASCII\"\n"); + Bprint(out, "Content-Transfer-Encoding: 7bit\n"); + } + } + + // write what we already read + if(Bwrite(out, buf, n) < 0) + fatal("output error"); + if(n > 0) + lastchar = buf[n-1]; + else + lastchar = '\n'; + + + // pass the rest + for(;;){ + n = Bread(in, buf, len); + if(n < 0) + fatal("input error2"); + if(n == 0) + break; + if(Bwrite(out, buf, n) < 0) + fatal("output error"); + lastchar = buf[n-1]; + } +} + +// pass the body to sendmail encoding with base64 +// +// the size of buf is very important to enc64. Anything other than +// a multiple of 3 will cause enc64 to output a termination sequence. +// To ensure that a full buf corresponds to a multiple of complete lines, +// we make buf a multiple of 3*18 since that's how many enc64 sticks on +// a single line. This avoids short lines in the output which is pleasing +// but not necessary. +// +void +body64(Biobuf *in, Biobuf *out) +{ + uchar buf[3*18*54]; + char obuf[3*18*54*2]; + int m, n; + + Bprint(out, "\n"); + for(;;){ + n = Bread(in, buf, sizeof(buf)); + fprint(2,"read %d bytes\n",n); + if(n < 0) + fatal("input error"); + if(n == 0) + break; + m = enc64(obuf, sizeof(obuf), buf, n); + fprint(2,"encoded %d bytes\n",m); + fprint(2,"writing to %x\n",out); + if((n=Bwrite(out, obuf, m)) < 0) + fatal("output error"); + fprint(2,"wrote %d bytes\n",n); + } + lastchar = '\n'; + fprint(2,"done with attachment\n"); +} + +// pass message to sendmail, make sure body starts with a newline +void +copy(Biobuf *in, Biobuf *out) +{ + char buf[4*1024]; + int n; + + for(;;){ + n = Bread(in, buf, sizeof(buf)); + if(n < 0) + fatal("input error"); + if(n == 0) + break; + if(Bwrite(out, buf, n) < 0) + fatal("output error"); + } +} + +void +attachment(Attach *a, Biobuf *out) +{ + Biobuf *f; + char *p; + + // if it's already mime encoded, just copy + if(strcmp(a->type, "mime") == 0){ + f = Bopen(a->path, OREAD); + if(f == nil){ + /* hack: give marshal time to stdin, before we kill it (for dead.letter) */ + sleep(500); + postnote(PNPROC, pid, "interrupt"); + sysfatal("opening %s: %r", a->path); + } + copy(f, out); + Bterm(f); + } + + // if it's not already mime encoded ... + if(strcmp(a->type, "text/plain") != 0) + Bprint(out, "Content-Type: %s\n", a->type); + + if(a->tinline){ + Bprint(out, "Content-Disposition: inline\n"); + } else { + p = strrchr(a->path, '/'); + if(p == nil) + p = a->path; + else + p++; + Bprint(out, "Content-Disposition: attachment; filename=%Z\n", p); + } + + f = Bopen(a->path, OREAD); + if(f == nil){ + /* hack: give marshal time to stdin, before we kill it (for dead.letter) */ + sleep(500); + postnote(PNPROC, pid, "interrupt"); + sysfatal("opening %s: %r", a->path); + } + + /* dump our local 'From ' line when passing along mail messages */ + if(strcmp(a->type, "message/rfc822") == 0){ + p = Brdline(f, '\n'); + if(strncmp(p, "From ", 5) != 0) + Bseek(f, 0, 0); + } + if(a->ctype->display){ + body(f, out, strcmp(a->type, "text/plain") == 0); + } else { + Bprint(out, "Content-Transfer-Encoding: base64\n"); + body64(f, out); + } + Bterm(f); +} + +char *ascwday[] = +{ + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" +}; + +char *ascmon[] = +{ + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" +}; + +int +printdate(Biobuf *b) +{ + Tm *tm; + int tz; + + tm = localtime(time(0)); + tz = (tm->tzoff/3600)*100 + ((tm->tzoff/60)%60); + + return Bprint(b, "Date: %s, %d %s %d %2.2d:%2.2d:%2.2d %s%.4d\n", + ascwday[tm->wday], tm->mday, ascmon[tm->mon], 1900+tm->year, + tm->hour, tm->min, tm->sec, tz>=0?"+":"", tz); +} + +int +printfrom(Biobuf *b) +{ + return Bprint(b, "From: %s\n", user); +} + +int +printto(Biobuf *b, Addr *a) +{ + int i; + + if(Bprint(b, "To: %s", a->v) < 0) + return -1; + i = 0; + for(a = a->next; a != nil; a = a->next) + if(Bprint(b, "%s%s", ((i++ & 7) == 7)?",\n\t":", ", a->v) < 0) + return -1; + if(Bprint(b, "\n") < 0) + return -1; + return 0; +} + +int +printcc(Biobuf *b, Addr *a) +{ + int i; + + if(a == nil) + return 0; + if(Bprint(b, "CC: %s", a->v) < 0) + return -1; + i = 0; + for(a = a->next; a != nil; a = a->next) + if(Bprint(b, "%s%s", ((i++ & 7) == 7)?",\n\t":", ", a->v) < 0) + return -1; + if(Bprint(b, "\n") < 0) + return -1; + return 0; +} + +int +printsubject(Biobuf *b, char *subject) +{ + return Bprint(b, "Subject: %s\n", subject); +} + +int +printinreplyto(Biobuf *out, char *dir) +{ + String *s = s_copy(dir); + char buf[256]; + int fd; + int n; + + s_append(s, "/messageid"); + fd = open(s_to_c(s), OREAD); + s_free(s); + if(fd < 0) + return 0; + n = read(fd, buf, sizeof(buf)-1); + close(fd); + if(n <= 0) + return 0; + buf[n] = 0; + return Bprint(out, "In-Reply-To: %s\n", buf); +} + +Attach* +mkattach(char *file, char *type, int tinline) +{ + Ctype *c; + Attach *a; + char ftype[64]; + char *p; + int n, pfd[2]; + + if(file == nil) + return nil; + if(access(file, 4) == -1){ + fprint(2, "%s: %s can't read file\n", argv0, file); + return nil; + } + a = emalloc(sizeof(*a)); + a->path = file; + a->next = nil; + a->type = type; + a->tinline = tinline; + a->ctype = nil; + if(type != nil){ + for(c = ctype; ; c++) + if(strncmp(type, c->type, strlen(c->type)) == 0){ + a->ctype = c; + break; + } + return a; + } + + // pick a type depending on extension + p = strchr(file, '.'); + if(p != nil) + p++; + + // check the builtin extensions + if(p != nil){ + for(c = ctype; c->ext != nil; c++) + if(strcmp(p, c->ext) == 0){ + a->type = c->type; + a->ctype = c; + return a; + } + } + + // try the mime types file + if(p != nil){ + if(mimetypes == nil) + readmimetypes(); + for(c = mimetypes; c != nil && c->ext != nil; c++) + if(strcmp(p, c->ext) == 0){ + a->type = c->type; + a->ctype = c; + return a; + } + } + + // run file to figure out the type + a->type = "application/octet-stream"; // safest default + if(pipe(pfd) < 0) + return a; + switch(fork()){ + case -1: + break; + case 0: + close(pfd[1]); + close(0); + dup(pfd[0], 0); + close(1); + dup(pfd[0], 1); + execl(unsharp("#9/bin/file"), "file", "-m", file, nil); + exits(0); + default: + close(pfd[0]); + n = read(pfd[1], ftype, sizeof(ftype)); + if(n > 0){ + ftype[n-1] = 0; + a->type = estrdup(ftype); + } + close(pfd[1]); + waitpid(); + break; + } + + for(c = ctype; ; c++) + if(strncmp(a->type, c->type, strlen(c->type)) == 0){ + a->ctype = c; + break; + } + + return a; +} + +char* +mkboundary(void) +{ + char buf[32]; + int i; + + srand((time(0)<<16)|getpid()); + strcpy(buf, "upas-"); + for(i = 5; i < sizeof(buf)-1; i++) + buf[i] = 'a' + nrand(26); + buf[i] = 0; + return estrdup(buf); +} + +// copy types to two fd's +static void +tee(int in, int out1, int out2) +{ + char buf[8*1024]; + int n; + + for(;;){ + n = read(in, buf, sizeof(buf)); + if(n <= 0) + break; + if(write(out1, buf, n) < 0) + break; + if(write(out2, buf, n) < 0) + break; + } +} + +// print the unix from line +int +printunixfrom(int fd) +{ + Tm *tm; + int tz; + + tm = localtime(time(0)); + tz = (tm->tzoff/3600)*100 + ((tm->tzoff/60)%60); + + return fprint(fd, "From %s %s %s %d %2.2d:%2.2d:%2.2d %s%.4d %d\n", + user, + ascwday[tm->wday], ascmon[tm->mon], tm->mday, + tm->hour, tm->min, tm->sec, tz>=0?"+":"", tz, 1900+tm->year); +} + +char *specialfile[] = +{ + "pipeto", + "pipefrom", + "L.mbox", + "forward", + "names" +}; + +// return 1 if this is a special file +static int +special(String *s) +{ + char *p; + int i; + + p = strrchr(s_to_c(s), '/'); + if(p == nil) + p = s_to_c(s); + else + p++; + for(i = 0; i < nelem(specialfile); i++) + if(strcmp(p, specialfile[i]) == 0) + return 1; + return 0; +} + +// open the folder using the recipients account name +static int +openfolder(char *rcvr) +{ + char *p; + int c; + String *file; + Dir *d; + int fd; + int scarey; + + file = s_new(); + mboxpath("f", user, file, 0); + + // if $mail/f exists, store there, otherwise in $mail + d = dirstat(s_to_c(file)); + if(d == nil || d->qid.type != QTDIR){ + scarey = 1; + file->ptr -= 1; + } else { + s_putc(file, '/'); + scarey = 0; + } + free(d); + + p = strrchr(rcvr, '!'); + if(p != nil) + rcvr = p+1; + + while(*rcvr && *rcvr != '@'){ + c = *rcvr++; + if(c == '/') + c = '_'; + s_putc(file, c); + } + s_terminate(file); + + if(scarey && special(file)){ + fprint(2, "%s: won't overwrite %s\n", argv0, s_to_c(file)); + s_free(file); + return -1; + } + + fd = open(s_to_c(file), OWRITE); + if(fd < 0) + fd = create(s_to_c(file), OWRITE, 0660); + + s_free(file); + return fd; +} + +// start up sendmail and return an fd to talk to it with +int +sendmail(Addr *to, Addr *cc, int *pid, char *rcvr) +{ + char **av, **v; + int ac, fd; + int pfd[2]; + String *cmd; + Addr *a; + + fd = -1; + if(rcvr != nil) + fd = openfolder(rcvr); + + ac = 0; + for(a = to; a != nil; a = a->next) + ac++; + for(a = cc; a != nil; a = a->next) + ac++; + v = av = emalloc(sizeof(char*)*(ac+20)); + ac = 0; + v[ac++] = "sendmail"; + if(xflag) + v[ac++] = "-x"; + if(rflag) + v[ac++] = "-r"; + if(lbflag) + v[ac++] = "-#"; + if(dflag) + v[ac++] = "-d"; + for(a = to; a != nil; a = a->next) + v[ac++] = a->v; + for(a = cc; a != nil; a = a->next) + v[ac++] = a->v; + v[ac] = 0; + + if(pipe(pfd) < 0) + fatal("%r"); + switch(*pid = rfork(RFFDG|RFPROC)){ // jpc - removed |RFENVG|RFREND| + case -1: + fatal("%r"); + break; + case 0: + if(holding) + close(holding); + close(pfd[1]); + dup(pfd[0], 0); + close(pfd[0]); + + if(rcvr != nil){ + if(pipe(pfd) < 0) + fatal("%r"); + switch(fork()){ + case -1: + fatal("%r"); + break; + case 0: + close(pfd[0]); + seek(fd, 0, 2); + printunixfrom(fd); + tee(0, pfd[1], fd); + write(fd, "\n", 1); + exits(0); + default: + close(fd); + close(pfd[1]); + dup(pfd[0], 0); + break; + } + } + + if(replymsg != nil) + putenv("replymsg", replymsg); + + cmd = mboxpath("pipefrom", login, s_new(), 0); + exec(s_to_c(cmd), av); + exec(unsharp("#9/bin/myupassend"), av); + exec(unsharp("#9/bin/upas/send"), av); + fatal("execing: %r"); + break; + default: + if(rcvr != nil) + close(fd); + close(pfd[0]); + break; + } + return pfd[1]; +} + +// start up pgp process and return an fd to talk to it with. +// its standard output will be the original fd, which goes to sendmail. +int +pgpfilter(int *pid, int fd, int pgpflag) +{ + char **av, **v; + int ac; + int pfd[2]; + + v = av = emalloc(sizeof(char*)*8); + ac = 0; + v[ac++] = "pgp"; + if(pgpflag & PGPsign) + v[ac++] = "-s"; + if(pgpflag & PGPencrypt) + v[ac++] = "-e"; + v[ac] = 0; + + if(pipe(pfd) < 0) + fatal("%r"); + switch(*pid = fork()){ + case -1: + fatal("%r"); + break; + case 0: + close(pfd[1]); + dup(pfd[0], 0); + close(pfd[0]); + dup(fd, 1); + close(fd); + + exec("/bin/upas/pgp", av); + fatal("execing: %r"); + break; + default: + close(pfd[0]); + break; + } + close(fd); + return pfd[1]; +} + +// wait for sendmail and pgp to exit; exit here if either failed +char* +waitforsubprocs(void) +{ + Waitmsg *w; + char *err; + + err = nil; + while((w = wait()) != nil){ + if(w->pid == pid || w->pid == pgppid){ + if(w->msg[0] != 0) + err = estrdup(w->msg); + } + free(w); + } + if(err) + exits(err); + return nil; +} + +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) +{ + for(;;){ + if(tolower(*a) != tolower(*b++)) + return -1; + if(*a++ == 0) + break; + } + return 0; +} + +static uchar t64d[256]; +static char t64e[64]; + +static void +init64(void) +{ + int c, i; + + memset(t64d, 255, 256); + memset(t64e, '=', 64); + i = 0; + for(c = 'A'; c <= 'Z'; c++){ + t64e[i] = c; + t64d[c] = i++; + } + for(c = 'a'; c <= 'z'; c++){ + t64e[i] = c; + t64d[c] = i++; + } + for(c = '0'; c <= '9'; c++){ + t64e[i] = c; + t64d[c] = i++; + } + t64e[i] = '+'; + t64d['+'] = i++; + t64e[i] = '/'; + t64d['/'] = i; +} + +int +enc64(char *out, int lim, uchar *in, int n) +{ + int i; + ulong b24; + char *start = out; + char *e = out + lim; + + if(t64e[0] == 0) + init64(); + for(i = 0; i < n/3; i++){ + b24 = (*in++)<<16; + b24 |= (*in++)<<8; + b24 |= *in++; + if(out + 5 >= e) + goto exhausted; + *out++ = t64e[(b24>>18)]; + *out++ = t64e[(b24>>12)&0x3f]; + *out++ = t64e[(b24>>6)&0x3f]; + *out++ = t64e[(b24)&0x3f]; + if((i%18) == 17) + *out++ = '\n'; + } + + switch(n%3){ + case 2: + b24 = (*in++)<<16; + b24 |= (*in)<<8; + if(out + 4 >= e) + goto exhausted; + *out++ = t64e[(b24>>18)]; + *out++ = t64e[(b24>>12)&0x3f]; + *out++ = t64e[(b24>>6)&0x3f]; + break; + case 1: + b24 = (*in)<<16; + if(out + 4 >= e) + goto exhausted; + *out++ = t64e[(b24>>18)]; + *out++ = t64e[(b24>>12)&0x3f]; + *out++ = '='; + break; + case 0: + if((i%18) != 0) + *out++ = '\n'; + *out = 0; + return out - start; + } +exhausted: + *out++ = '='; + *out++ = '\n'; + *out = 0; + return out - start; +} + +void +freealias(Alias *a) +{ + freeaddrs(a->addr); + free(a); +} + +void +freealiases(Alias *a) +{ + Alias *next; + + while(a != nil){ + next = a->next; + freealias(a); + a = next; + } +} + +// +// read alias file +// +Alias* +readaliases(void) +{ + Alias *a, **l, *first; + Addr *addr, **al; + String *file, *line, *token; + // jpc - static int already; + Sinstack *sp; + + first = nil; + file = s_new(); + line = s_new(); + token = s_new(); + + // open and get length + mboxpath("names", login, file, 0); + sp = s_allocinstack(s_to_c(file)); + if(sp == nil) + goto out; + + l = &first; + + // read a line at a time. + while(s_rdinstack(sp, s_restart(line))!=nil) { + s_restart(line); + a = emalloc(sizeof(Alias)); + al = &a->addr; + for(;;){ + if(s_parse(line, s_restart(token))==0) + break; + addr = emalloc(sizeof(Addr)); + addr->v = strdup(s_to_c(token)); + addr->next = 0; + *al = addr; + al = &addr->next; + } + if(a->addr == nil || a->addr->next == nil){ + freealias(a); + continue; + } + a->next = nil; + *l = a; + l = &a->next; + } + s_freeinstack(sp); + +out: + s_free(file); + s_free(line); + s_free(token); + return first; +} + +Addr* +newaddr(char *name) +{ + Addr *a; + + a = emalloc(sizeof(*a)); + a->next = nil; + a->v = estrdup(name); + if(a->v == nil) + sysfatal("%r"); + return a; +} + +// +// expand personal aliases since the names are meaningless in +// other contexts +// +Addr* +_expand(Addr *old, int *changedp) +{ + Alias *al; + Addr *first, *next, **l, *a; + + *changedp = 0; + first = nil; + l = &first; + for(;old != nil; old = next){ + next = old->next; + for(al = aliases; al != nil; al = al->next){ + if(strcmp(al->addr->v, old->v) == 0){ + for(a = al->addr->next; a != nil; a = a->next){ + *l = newaddr(a->v); + if(*l == nil) + sysfatal("%r"); + l = &(*l)->next; + *changedp = 1; + } + break; + } + } + if(al != nil){ + freeaddr(old); + continue; + } + *l = old; + old->next = nil; + l = &(*l)->next; + } + return first; +} + +Addr* +rexpand(Addr *old) +{ + int i, changed; + + changed = 0; + for(i=0; i<32; i++){ + old = _expand(old, &changed); + if(changed == 0) + break; + } + return old; +} + +Addr* +unique(Addr *first) +{ + Addr *a, **l, *x; + + for(a = first; a != nil; a = a->next){ + for(l = &a->next; *l != nil;){ + if(strcmp(a->v, (*l)->v) == 0){ + x = *l; + *l = x->next; + freeaddr(x); + } else + l = &(*l)->next; + } + } + return first; +} + +Addr* +expand(int ac, char **av) +{ + Addr *first, **l; + int i; + + first = nil; + + // make a list of the starting addresses + l = &first; + for(i = 0; i < ac; i++){ + *l = newaddr(av[i]); + if(*l == nil) + sysfatal("%r"); + l = &(*l)->next; + } + + // recurse till we don't change any more + return unique(rexpand(first)); +} + +Addr* +concataddr(Addr *a, Addr *b) +{ + Addr *oa; + + if(a == nil) + return b; + + oa = a; + for(; a->next; a=a->next) + ; + a->next = b; + return oa; +} + +void +freeaddr(Addr *ap) +{ + free(ap->v); + free(ap); +} + +void +freeaddrs(Addr *ap) +{ + Addr *next; + + for(; ap; ap=next) { + next = ap->next; + freeaddr(ap); + } +} + +String* +s_copyn(char *s, int n) +{ + return s_nappend(s_reset(nil), s, n); +} + +// fetch the next token from an RFC822 address string +// we assume the header is RFC822-conformant in that +// we recognize escaping anywhere even though it is only +// supposed to be in quoted-strings, domain-literals, and comments. +// +// i'd use yylex or yyparse here, but we need to preserve +// things like comments, which i think it tosses away. +// +// we're not strictly RFC822 compliant. we misparse such nonsense as +// +// To: gre @ (Grace) plan9 . (Emlin) bell-labs.com +// +// make sure there's no whitespace in your addresses and +// you'll be fine. +// +enum { + Twhite, + Tcomment, + Twords, + Tcomma, + Tleftangle, + Trightangle, + Terror, + Tend, +}; +//char *ty82[] = {"white", "comment", "words", "comma", "<", ">", "err", "end"}; +#define ISWHITE(p) ((p)==' ' || (p)=='\t' || (p)=='\n' || (p)=='\r') +int +get822token(String **tok, char *p, char **pp) +{ + char *op; + int type; + int quoting; + + op = p; + switch(*p){ + case '\0': + *tok = nil; + *pp = nil; + return Tend; + + case ' ': // get whitespace + case '\t': + case '\n': + case '\r': + type = Twhite; + while(ISWHITE(*p)) + p++; + break; + + case '(': // get comment + type = Tcomment; + for(p++; *p && *p != ')'; p++) + if(*p == '\\') { + if(*(p+1) == '\0') { + *tok = nil; + return Terror; + } + p++; + } + + if(*p != ')') { + *tok = nil; + return Terror; + } + p++; + break; + case ',': + type = Tcomma; + p++; + break; + case '<': + type = Tleftangle; + p++; + break; + case '>': + type = Trightangle; + p++; + break; + default: // bunch of letters, perhaps quoted strings tossed in + type = Twords; + quoting = 0; + for(; *p && (quoting || (!ISWHITE(*p) && *p != '>' && *p != '<' && *p != ',')); p++) { + if(*p == '"') + quoting = !quoting; + if(*p == '\\') { + if(*(p+1) == '\0') { + *tok = nil; + return Terror; + } + p++; + } + } + break; + } + + if(pp) + *pp = p; + *tok = s_copyn(op, p-op); + return type; +} + +// expand local aliases in an RFC822 mail line +// add list of expanded addresses to to. +Addr* +expandline(String **s, Addr *to) +{ + Addr *na, *nto, *ap; + char *p; + int tok, inangle, hadangle, nword; + String *os, *ns, *stok, *lastword, *sinceword; + + os = s_copy(s_to_c(*s)); + p = strchr(s_to_c(*s), ':'); + assert(p != nil); + p++; + + ns = s_copyn(s_to_c(*s), p-s_to_c(*s)); + stok = nil; + nto = nil; + // + // the only valid mailbox namings are word + // and word* < addr > + // without comments this would be simple. + // we keep the following: + // lastword - current guess at the address + // sinceword - whitespace and comment seen since lastword + // + lastword = s_new(); + sinceword = s_new(); + inangle = 0; + nword = 0; + hadangle = 0; + for(;;) { + stok = nil; + switch(tok = get822token(&stok, p, &p)){ + default: + abort(); + case Tcomma: + case Tend: + if(inangle) + goto Error; + if(nword != 1) + goto Error; + na = rexpand(newaddr(s_to_c(lastword))); + s_append(ns, na->v); + s_append(ns, s_to_c(sinceword)); + for(ap=na->next; ap; ap=ap->next) { + s_append(ns, ", "); + s_append(ns, ap->v); + } + nto = concataddr(na, nto); + if(tok == Tcomma){ + s_append(ns, ","); + s_free(stok); + } + if(tok == Tend) + goto Break2; + inangle = 0; + nword = 0; + hadangle = 0; + s_reset(sinceword); + s_reset(lastword); + break; + case Twhite: + case Tcomment: + s_append(sinceword, s_to_c(stok)); + s_free(stok); + break; + case Trightangle: + if(!inangle) + goto Error; + inangle = 0; + hadangle = 1; + s_append(sinceword, s_to_c(stok)); + s_free(stok); + break; + case Twords: + case Tleftangle: + if(hadangle) + goto Error; + if(tok != Tleftangle && inangle && s_len(lastword)) + goto Error; + if(tok == Tleftangle) { + inangle = 1; + nword = 1; + } + s_append(ns, s_to_c(lastword)); + s_append(ns, s_to_c(sinceword)); + s_reset(sinceword); + if(tok == Tleftangle) { + s_append(ns, "<"); + s_reset(lastword); + } else { + s_free(lastword); + lastword = stok; + } + if(!inangle) + nword++; + break; + case Terror: // give up, use old string, addrs + Error: + ns = os; + os = nil; + freeaddrs(nto); + nto = nil; + werrstr("rfc822 syntax error"); + rfc822syntaxerror = 1; + goto Break2; + } + } +Break2: + s_free(*s); + s_free(os); + *s = ns; + nto = concataddr(nto, to); + return nto; +} + +void +Bdrain(Biobuf *b) +{ + char buf[8192]; + + while(Bread(b, buf, sizeof buf) > 0) + ; +} + +void +readmimetypes(void) +{ + Biobuf *b; + char *p; + char *f[6]; + char type[256]; + static int alloced, inuse; + + if(mimetypes == 0){ + alloced = 256; + mimetypes = emalloc(alloced*sizeof(Ctype)); + mimetypes[0].ext = ""; + } + + b = Bopen(unsharp("#9/sys/lib/mimetype"), OREAD); + if(b == nil) + return; + for(;;){ + p = Brdline(b, '\n'); + if(p == nil) + break; + p[Blinelen(b)-1] = 0; + if(tokenize(p, f, 6) < 4) + continue; + if(strcmp(f[0], "-") == 0 || strcmp(f[1], "-") == 0 || strcmp(f[2], "-") == 0) + continue; + if(inuse + 1 >= alloced){ + alloced += 256; + mimetypes = erealloc(mimetypes, alloced*sizeof(Ctype)); + } + snprint(type, sizeof(type), "%s/%s", f[1], f[2]); + mimetypes[inuse].type = estrdup(type); + mimetypes[inuse].ext = estrdup(f[0]+1); + mimetypes[inuse].display = !strcmp(type, "text/plain"); + inuse++; + + // always make sure there's a terminator + mimetypes[inuse].ext = 0; + } + Bterm(b); +} + +char* +estrdup(char *x) +{ + x = strdup(x); + if(x == nil) + fatal("memory"); + return x; +} + +void* +emalloc(int n) +{ + void *x; + + x = malloc(n); + if(x == nil) + fatal("%r"); + return x; +} + +void* +erealloc(void *x, int n) +{ + x = realloc(x, n); + if(x == nil) + fatal("%r"); + return x; +} + +// +// Formatter for %" +// Use double quotes to protect white space, frogs, \ and " +// +enum +{ + Qok = 0, + Qquote, + Qbackslash, +}; + +static int +needtoquote(Rune r) +{ + if(r >= Runeself) + return Qquote; + if(r <= ' ') + return Qquote; + if(r=='\\' || r=='"') + return Qbackslash; + return Qok; +} + +int +doublequote(Fmt *f) +{ + char *s, *t; + int w, quotes; + Rune r; + + s = va_arg(f->args, char*); + if(s == nil || *s == '\0') + return fmtstrcpy(f, "\"\""); + + quotes = 0; + for(t=s; *t; t+=w){ + w = chartorune(&r, t); + quotes |= needtoquote(r); + } + if(quotes == 0) + return fmtstrcpy(f, s); + + fmtrune(f, '"'); + for(t=s; *t; t+=w){ + w = chartorune(&r, t); + if(needtoquote(r) == Qbackslash) + fmtrune(f, '\\'); + fmtrune(f, r); + } + return fmtrune(f, '"'); +} |