#include "common.h" #include 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 ainline; 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 rfc2047fmt(Fmt*); char* mksubject(char*); 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* #pragma varargck type "U" 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); fmtinstall('U', rfc2047fmt); 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<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(hdrtype == Hsubject){ s_append(s, mksubject(s_to_c(sline))); s_append(s, "\n"); }else 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< 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)); if(n < 0) fatal("input error"); if(n == 0) break; m = enc64(obuf, sizeof(obuf), buf, n); if(Bwrite(out, obuf, m) < 0) fatal("output error"); } lastchar = '\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->ainline){ Bprint(out, "Content-Disposition: ainline\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: %U\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 ainline) { 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->ainline = ainline; 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("/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|RFREND|RFPROC|RFENVG)){ 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("/bin/myupassend", av); exec("/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"; v[ac++] = "-fat"; /* operate as a filter, generate text */ 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); /* add newline to avoid confusing pgp output with 822 headers */ write(1, "\n", 1); exec("/bin/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; 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("/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, '"'); } int rfc2047fmt(Fmt *fmt) { char *s, *p; s = va_arg(fmt->args, char*); if(s == nil) return fmtstrcpy(fmt, ""); for(p=s; *p; p++) if((uchar)*p >= 0x80) goto hard; return fmtstrcpy(fmt, s); hard: fmtprint(fmt, "=?utf-8?q?"); for(p=s; *p; p++){ if(*p == ' ') fmtrune(fmt, '_'); else if(*p == '_' || *p == '\t' || *p == '=' || *p == '?' || (uchar)*p >= 0x80) fmtprint(fmt, "=%.2uX", (uchar)*p); else fmtrune(fmt, (uchar)*p); } fmtprint(fmt, "?="); return 0; } char* mksubject(char *line) { char *p, *q; static char buf[1024]; p = strchr(line, ':')+1; while(*p == ' ') p++; for(q=p; *q; q++) if((uchar)*q >= 0x80) goto hard; return line; hard: snprint(buf, sizeof buf, "Subject: %U", p); return buf; }