URI: 
       tmarshal.c - plan9port - [fork] Plan 9 from user space
  HTML git clone git://src.adamsgaard.dk/plan9port
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
       tmarshal.c (34357B)
       ---
            1 #include "common.h"
            2 #include <thread.h>
            3 #include <9pclient.h>
            4 #include <ctype.h>
            5 
            6 enum
            7 {
            8         STACK = 32768
            9 };
           10 
           11 #define inline _inline
           12 
           13 typedef struct Attach Attach;
           14 typedef struct Alias Alias;
           15 typedef struct Addr Addr;
           16 typedef struct Ctype Ctype;
           17 
           18 struct Attach {
           19         Attach        *next;
           20         char        *path;
           21         int        fd;
           22         char        *type;
           23         int        inline;
           24         Ctype        *ctype;
           25 };
           26 
           27 struct Alias
           28 {
           29         Alias        *next;
           30         int        n;
           31         Addr        *addr;
           32 };
           33 
           34 struct Addr
           35 {
           36         Addr        *next;
           37         char        *v;
           38 };
           39 
           40 enum {
           41         Hfrom,
           42         Hto,
           43         Hcc,
           44         Hbcc,
           45         Hsender,
           46         Hreplyto,
           47         Hinreplyto,
           48         Hdate,
           49         Hsubject,
           50         Hmime,
           51         Hpriority,
           52         Hmsgid,
           53         Hcontent,
           54         Hx,
           55         Hprecedence,
           56         Nhdr
           57 };
           58 
           59 enum {
           60         PGPsign = 1,
           61         PGPencrypt = 2
           62 };
           63 
           64 char *hdrs[Nhdr] = {
           65 [Hfrom]                "from:",
           66 [Hto]                "to:",
           67 [Hcc]                "cc:",
           68 [Hbcc]                "bcc:",
           69 [Hreplyto]        "reply-to:",
           70 [Hinreplyto]        "in-reply-to:",
           71 [Hsender]        "sender:",
           72 [Hdate]                "date:",
           73 [Hsubject]        "subject:",
           74 [Hpriority]        "priority:",
           75 [Hmsgid]        "message-id:",
           76 [Hmime]                "mime-",
           77 [Hcontent]        "content-",
           78 [Hx]                "x-",
           79 [Hprecedence]        "precedence"
           80 };
           81 
           82 struct Ctype {
           83         char        *type;
           84         char         *ext;
           85         int        display;
           86 };
           87 
           88 Ctype ctype[] = {
           89         { "text/plain",                        "txt",        1,        },
           90         { "text/html",                        "html",        1,        },
           91         { "text/html",                        "htm",        1,        },
           92         { "text/tab-separated-values",        "tsv",        1,        },
           93         { "text/richtext",                "rtx",        1,        },
           94         { "message/rfc822",                "txt",        1,        },
           95         { "",                                 0,        0,        }
           96 };
           97 
           98 Ctype *mimetypes;
           99 
          100 int pid = -1;
          101 int pgppid = -1;
          102 
          103 Attach*        mkattach(char*, char*, int);
          104 int        readheaders(Biobuf*, int*, String**, Addr**, int);
          105 void        body(Biobuf*, Biobuf*, int);
          106 char*        mkboundary(void);
          107 int        printdate(Biobuf*);
          108 int        printfrom(Biobuf*);
          109 int        printto(Biobuf*, Addr*);
          110 int        printcc(Biobuf*, Addr*);
          111 int        printsubject(Biobuf*, char*);
          112 int        printinreplyto(Biobuf*, char*);
          113 int        sendmail(Addr*, Addr*, int*, char*);
          114 void        attachment(Attach*, Biobuf*);
          115 int        cistrncmp(char*, char*, int);
          116 int        cistrcmp(char*, char*);
          117 char*        waitforsubprocs(void);
          118 int        enc64(char*, int, uchar*, int);
          119 Addr*        expand(int, char**);
          120 Alias*        readaliases(void);
          121 Addr*        expandline(String**, Addr*);
          122 void        Bdrain(Biobuf*);
          123 void        freeaddr(Addr *);
          124 int        pgpopts(char*);
          125 int        pgpfilter(int*, int, int);
          126 void        readmimetypes(void);
          127 char*        estrdup(char*);
          128 void*        emalloc(int);
          129 void*        erealloc(void*, int);
          130 void        freeaddr(Addr*);
          131 void        freeaddrs(Addr*);
          132 void        freealias(Alias*);
          133 void        freealiases(Alias*);
          134 int        doublequote(Fmt*);
          135 int        mountmail(void);
          136 int        nprocexec;
          137 int        rfc2047fmt(Fmt*);
          138 char*        mksubject(char*);
          139 
          140 int rflag, lbflag, xflag, holding, nflag, Fflag, eightflag, dflag;
          141 int pgpflag = 0;
          142 char *user;
          143 char *login;
          144 Alias *aliases;
          145 int rfc822syntaxerror;
          146 char lastchar;
          147 char *replymsg;
          148 
          149 CFsys *mailfs;
          150 
          151 enum
          152 {
          153         Ok = 0,
          154         Nomessage = 1,
          155         Nobody = 2,
          156         Error = -1
          157 };
          158 
          159 #pragma varargck        type        "Z"        char*
          160 
          161 void
          162 usage(void)
          163 {
          164         fprint(2, "usage: %s [-Fr#xn] [-s subject] [-c ccrecipient] [-t type] [-aA attachment] [-p[es]] [-R replymsg] -8 | recipient-list\n",
          165                 argv0);
          166         threadexitsall("usage");
          167 }
          168 
          169 void
          170 fatal(char *fmt, ...)
          171 {
          172         char buf[1024];
          173         va_list arg;
          174 
          175         if(pid >= 0)
          176                 postnote(PNPROC, pid, "die");
          177         if(pgppid >= 0)
          178                 postnote(PNPROC, pgppid, "die");
          179 
          180         va_start(arg, fmt);
          181         vseprint(buf, buf+sizeof(buf), fmt, arg);
          182         va_end(arg);
          183         fprint(2, "%s: %s\n", argv0, buf);
          184         holdoff(holding);
          185         threadexitsall(buf);
          186 }
          187 
          188 void
          189 threadmain(int argc, char **argv)
          190 {
          191         Attach *first, **l, *a;
          192         char *subject, *type, *boundary;
          193         int flags, fd;
          194         Biobuf in, out, *b;
          195         Addr *to;
          196         Addr *cc;
          197         String *file, *hdrstring;
          198         int noinput, headersrv;
          199         int ccargc;
          200         char *ccargv[32];
          201 
          202         noinput = 0;
          203         subject = nil;
          204         first = nil;
          205         l = &first;
          206         type = nil;
          207         hdrstring = nil;
          208         ccargc = 0;
          209 
          210         quotefmtinstall();
          211         fmtinstall('Z', doublequote);
          212         fmtinstall('U', rfc2047fmt);
          213         threadwaitchan();
          214 
          215         ARGBEGIN{
          216         case 't':
          217                 type = EARGF(usage());
          218                 break;
          219         case 'a':
          220                 flags = 0;
          221                 goto aflag;
          222         case 'A':
          223                 flags = 1;
          224         aflag:
          225                 a = mkattach(EARGF(usage()), type, flags);
          226                 if(a == nil)
          227                         threadexitsall("bad args");
          228                 type = nil;
          229                 *l = a;
          230                 l = &a->next;
          231                 break;
          232         case 'C':
          233                 if(ccargc >= nelem(ccargv)-1)
          234                         sysfatal("too many cc's");
          235                 ccargv[ccargc] = ARGF();
          236                 if(ccargv[ccargc] == nil)
          237                         usage();
          238                 ccargc++;
          239                 break;
          240         case 'R':
          241                 replymsg = EARGF(usage());
          242                 break;
          243         case 's':
          244                 subject = EARGF(usage());
          245                 break;
          246         case 'F':
          247                 Fflag = 1;                /* file message */
          248                 break;
          249         case 'r':
          250                 rflag = 1;                /* for sendmail */
          251                 break;
          252         case 'd':
          253                 dflag = 1;                /* for sendmail */
          254                 break;
          255         case '#':
          256                 lbflag = 1;                /* for sendmail */
          257                 break;
          258         case 'x':
          259                 xflag = 1;                /* for sendmail */
          260                 break;
          261         case 'n':                        /* no standard input */
          262                 nflag = 1;
          263                 break;
          264         case '8':                        /* read recipients from rfc822 header */
          265                 eightflag = 1;
          266                 break;
          267         case 'p':                        /* pgp flag: encrypt, sign, or both */
          268                 if(pgpopts(EARGF(usage())) < 0)
          269                         sysfatal("bad pgp options");
          270                 break;
          271         default:
          272                 usage();
          273                 break;
          274         }ARGEND;
          275 
          276         login = getlog();
          277         user = getenv("upasname");
          278         if(user == nil || *user == 0)
          279                 user = login;
          280         if(user == nil || *user == 0)
          281                 sysfatal("can't read user name");
          282 
          283         if(Binit(&in, 0, OREAD) < 0)
          284                 sysfatal("can't Binit 0: %r");
          285 
          286         if(nflag && eightflag)
          287                 sysfatal("can't use both -n and -8");
          288         if(eightflag && argc >= 1)
          289                 usage();
          290         else if(!eightflag && argc < 1)
          291                 usage();
          292 
          293         aliases = readaliases();
          294         if(!eightflag){
          295                 to = expand(argc, argv);
          296                 cc = expand(ccargc, ccargv);
          297         } else {
          298                 to = nil;
          299                 cc = nil;
          300         }
          301 
          302         flags = 0;
          303         headersrv = Nomessage;
          304         if(!nflag && !xflag && !lbflag &&!dflag) {
          305                 /* pass through headers, keeping track of which we've seen, */
          306                 /* perhaps building to list. */
          307                 holding = holdon();
          308                 headersrv = readheaders(&in, &flags, &hdrstring, eightflag ? &to : nil, 1);
          309                 if(rfc822syntaxerror){
          310                         Bdrain(&in);
          311                         fatal("rfc822 syntax error, message not sent");
          312                 }
          313                 if(to == nil){
          314                         Bdrain(&in);
          315                         fatal("no addresses found, message not sent");
          316                 }
          317 
          318                 switch(headersrv){
          319                 case Error:                /* error */
          320                         fatal("reading");
          321                         break;
          322                 case Nomessage:                /* no message, just exit mimicking old behavior */
          323                         noinput = 1;
          324                         if(first == nil)
          325                                 threadexitsall(0);
          326                         break;
          327                 }
          328         }
          329 
          330         fd = sendmail(to, cc, &pid, Fflag ? argv[0] : nil);
          331         if(fd < 0)
          332                 sysfatal("execing sendmail: %r\n:");
          333         if(xflag || lbflag || dflag){
          334                 close(fd);
          335                 threadexitsall(waitforsubprocs());
          336         }
          337 
          338         if(Binit(&out, fd, OWRITE) < 0)
          339                 fatal("can't Binit 1: %r");
          340 
          341         if(!nflag){
          342                 if(Bwrite(&out, s_to_c(hdrstring), s_len(hdrstring)) != s_len(hdrstring))
          343                         fatal("write error");
          344                 s_free(hdrstring);
          345                 hdrstring = nil;
          346 
          347                 /* read user's standard headers */
          348                 file = s_new();
          349                 mboxpath("headers", user, file, 0);
          350                 b = Bopen(s_to_c(file), OREAD);
          351                 if(b != nil){
          352                         switch(readheaders(b, &flags, &hdrstring, nil, 0)){
          353                         case Error:        /* error */
          354                                 fatal("reading");
          355                         }
          356                         Bterm(b);
          357                         if(Bwrite(&out, s_to_c(hdrstring), s_len(hdrstring)) != s_len(hdrstring))
          358                                 fatal("write error");
          359                         s_free(hdrstring);
          360                         hdrstring = nil;
          361                 }
          362         }
          363 
          364         /* add any headers we need */
          365         if((flags & (1<<Hdate)) == 0)
          366                 if(printdate(&out) < 0)
          367                         fatal("writing");
          368         if((flags & (1<<Hfrom)) == 0)
          369                 if(printfrom(&out) < 0)
          370                         fatal("writing");
          371         if((flags & (1<<Hto)) == 0)
          372                 if(printto(&out, to) < 0)
          373                         fatal("writing");
          374         if((flags & (1<<Hcc)) == 0)
          375                 if(printcc(&out, cc) < 0)
          376                         fatal("writing");
          377         if((flags & (1<<Hsubject)) == 0 && subject != nil)
          378                 if(printsubject(&out, subject) < 0)
          379                         fatal("writing");
          380         if(replymsg != nil)
          381                 printinreplyto(&out, replymsg);        /* ignore errors */
          382         Bprint(&out, "MIME-Version: 1.0\n");
          383 
          384         if(pgpflag){        /* interpose pgp process between us and sendmail to handle body */
          385                 Bflush(&out);
          386                 Bterm(&out);
          387                 fd = pgpfilter(&pgppid, fd, pgpflag);
          388                 if(Binit(&out, fd, OWRITE) < 0)
          389                         fatal("can't Binit 1: %r");
          390         }
          391 
          392         /* if attachments, stick in multipart headers */
          393         boundary = nil;
          394         if(first != nil){
          395                 boundary = mkboundary();
          396                 Bprint(&out, "Content-Type: multipart/mixed;\n");
          397                 Bprint(&out, "\tboundary=\"%s\"\n\n", boundary);
          398                 Bprint(&out, "This is a multi-part message in MIME format.\n");
          399                 Bprint(&out, "--%s\n", boundary);
          400                 Bprint(&out, "Content-Disposition: inline\n");
          401         }
          402 
          403         if(!nflag){
          404                 if(!noinput && headersrv == Ok){
          405                         body(&in, &out, 1);
          406                 }
          407         } else
          408                 Bprint(&out, "\n");
          409         holdoff(holding);
          410 
          411         Bflush(&out);
          412         for(a = first; a != nil; a = a->next){
          413                 if(lastchar != '\n')
          414                         Bprint(&out, "\n");
          415                 Bprint(&out, "--%s\n", boundary);
          416                 attachment(a, &out);
          417         }
          418 
          419         if(first != nil){
          420                 if(lastchar != '\n')
          421                         Bprint(&out, "\n");
          422                 Bprint(&out, "--%s--\n", boundary);
          423         }
          424 
          425         Bterm(&out);
          426         close(fd);
          427         threadexitsall(waitforsubprocs());
          428 }
          429 
          430 /* evaluate pgp option string */
          431 int
          432 pgpopts(char *s)
          433 {
          434         if(s == nil || s[0] == '\0')
          435                 return -1;
          436         while(*s){
          437                 switch(*s++){
          438                 case 's':  case 'S':
          439                         pgpflag |= PGPsign;
          440                         break;
          441                 case 'e': case 'E':
          442                         pgpflag |= PGPencrypt;
          443                         break;
          444                 default:
          445                         return -1;
          446                 }
          447         }
          448         return 0;
          449 }
          450 
          451 /* read headers from stdin into a String, expanding local aliases, */
          452 /* keep track of which headers are there, which addresses we have */
          453 /* remove Bcc: line. */
          454 int
          455 readheaders(Biobuf *in, int *fp, String **sp, Addr **top, int strict)
          456 {
          457         Addr *to;
          458         String *s, *sline;
          459         char *p;
          460         int i, seen, hdrtype;
          461 
          462         s = s_new();
          463         sline = nil;
          464         to = nil;
          465         hdrtype = -1;
          466         seen = 0;
          467         for(;;) {
          468                 if((p = Brdline(in, '\n')) != nil) {
          469                         seen = 1;
          470                         p[Blinelen(in)-1] = 0;
          471 
          472                         /* coalesce multiline headers */
          473                         if((*p == ' ' || *p == '\t') && sline){
          474                                 s_append(sline, "\n");
          475                                 s_append(sline, p);
          476                                 p[Blinelen(in)-1] = '\n';
          477                                 continue;
          478                         }
          479                 }
          480 
          481                 /* process the current header, it's all been read */
          482                 if(sline) {
          483                         assert(hdrtype != -1);
          484                         if(top){
          485                                 switch(hdrtype){
          486                                 case Hto:
          487                                 case Hcc:
          488                                 case Hbcc:
          489                                         to = expandline(&sline, to);
          490                                         break;
          491                                 }
          492                         }
          493                         if(hdrtype == Hsubject){
          494                                 s_append(s, mksubject(s_to_c(sline)));
          495                                 s_append(s, "\n");
          496                         }else if(top==nil || hdrtype!=Hbcc){
          497                                 s_append(s, s_to_c(sline));
          498                                 s_append(s, "\n");
          499                         }
          500                         s_free(sline);
          501                         sline = nil;
          502                 }
          503 
          504                 if(p == nil)
          505                         break;
          506 
          507                 /* if no :, it's not a header, seek back and break */
          508                 if(strchr(p, ':') == nil){
          509                         p[Blinelen(in)-1] = '\n';
          510                         Bseek(in, -Blinelen(in), 1);
          511                         break;
          512                 }
          513 
          514                 sline = s_copy(p);
          515 
          516                 /* classify the header.  If we don't recognize it, break.  This is */
          517                 /* to take care of user's that start messages with lines that contain */
          518                 /* ':'s but that aren't headers.  This is a bit hokey.  Since I decided */
          519                 /* to let users type headers, I need some way to distinguish.  Therefore, */
          520                 /* marshal tries to know all likely headers and will indeed screw up if */
          521                 /* the user types an unlikely one. -- presotto */
          522                 hdrtype = -1;
          523                 for(i = 0; i < nelem(hdrs); i++){
          524                         if(cistrncmp(hdrs[i], p, strlen(hdrs[i])) == 0){
          525                                 *fp |= 1<<i;
          526                                 hdrtype = i;
          527                                 break;
          528                         }
          529                 }
          530                 if(strict){
          531                         if(hdrtype == -1){
          532                                 p[Blinelen(in)-1] = '\n';
          533                                 Bseek(in, -Blinelen(in), 1);
          534                                 break;
          535                         }
          536                 } else
          537                         hdrtype = 0;
          538                 p[Blinelen(in)-1] = '\n';
          539         }
          540 
          541         *sp = s;
          542         if(top)
          543                 *top = to;
          544 
          545         if(seen == 0){
          546                 if(Blinelen(in) == 0)
          547                         return Nomessage;
          548                 else
          549                         return Ok;
          550         }
          551         if(p == nil)
          552                 return Nobody;
          553         return Ok;
          554 }
          555 
          556 /* pass the body to sendmail, make sure body starts and ends with a newline */
          557 void
          558 body(Biobuf *in, Biobuf *out, int docontenttype)
          559 {
          560         char *buf, *p;
          561         int i, n, len;
          562 
          563         n = 0;
          564         len = 16*1024;
          565         buf = emalloc(len);
          566 
          567         /* first char must be newline */
          568         i = Bgetc(in);
          569         if(i > 0){
          570                 if(i != '\n')
          571                         buf[n++] = '\n';
          572                 buf[n++] = i;
          573         } else {
          574                 buf[n++] = '\n';
          575         }
          576 
          577         /* read into memory */
          578         if(docontenttype){
          579                 while(docontenttype){
          580                         if(n == len){
          581                                 len += len>>2;
          582                                 buf = realloc(buf, len);
          583                                 if(buf == nil)
          584                                         sysfatal("%r");
          585                         }
          586                         p = buf+n;
          587                         i = Bread(in, p, len - n);
          588                         if(i < 0)
          589                                 fatal("input error2");
          590                         if(i == 0)
          591                                 break;
          592                         n += i;
          593                         for(; i > 0; i--)
          594                                 if((*p++ & 0x80) && docontenttype){
          595                                         Bprint(out, "Content-Type: text/plain; charset=\"UTF-8\"\n");
          596                                         Bprint(out, "Content-Transfer-Encoding: 8bit\n");
          597                                         docontenttype = 0;
          598                                         break;
          599                                 }
          600                 }
          601                 if(docontenttype){
          602                         Bprint(out, "Content-Type: text/plain; charset=\"US-ASCII\"\n");
          603                         Bprint(out, "Content-Transfer-Encoding: 7bit\n");
          604                 }
          605         }
          606 
          607         /* write what we already read */
          608         if(Bwrite(out, buf, n) < 0)
          609                 fatal("output error");
          610         if(n > 0)
          611                 lastchar = buf[n-1];
          612         else
          613                 lastchar = '\n';
          614 
          615 
          616         /* pass the rest */
          617         for(;;){
          618                 n = Bread(in, buf, len);
          619                 if(n < 0)
          620                         fatal("input error2");
          621                 if(n == 0)
          622                         break;
          623                 if(Bwrite(out, buf, n) < 0)
          624                         fatal("output error");
          625                 lastchar = buf[n-1];
          626         }
          627 }
          628 
          629 /* pass the body to sendmail encoding with base64 */
          630 /* */
          631 /*  the size of buf is very important to enc64.  Anything other than */
          632 /*  a multiple of 3 will cause enc64 to output a termination sequence. */
          633 /*  To ensure that a full buf corresponds to a multiple of complete lines, */
          634 /*  we make buf a multiple of 3*18 since that's how many enc64 sticks on */
          635 /*  a single line.  This avoids short lines in the output which is pleasing */
          636 /*  but not necessary. */
          637 /* */
          638 void
          639 body64(Biobuf *in, Biobuf *out)
          640 {
          641         uchar buf[3*18*54];
          642         char obuf[3*18*54*2];
          643         int m, n;
          644 
          645         Bprint(out, "\n");
          646         for(;;){
          647                 n = Bread(in, buf, sizeof(buf));
          648                 if(n < 0)
          649                         fatal("input error");
          650                 if(n == 0)
          651                         break;
          652                 m = enc64(obuf, sizeof(obuf), buf, n);
          653                 if((n=Bwrite(out, obuf, m)) < 0)
          654                         fatal("output error");
          655         }
          656         lastchar = '\n';
          657 }
          658 
          659 /* pass message to sendmail, make sure body starts with a newline */
          660 void
          661 copy(Biobuf *in, Biobuf *out)
          662 {
          663         char buf[4*1024];
          664         int n;
          665 
          666         for(;;){
          667                 n = Bread(in, buf, sizeof(buf));
          668                 if(n < 0)
          669                         fatal("input error");
          670                 if(n == 0)
          671                         break;
          672                 if(Bwrite(out, buf, n) < 0)
          673                         fatal("output error");
          674         }
          675 }
          676 
          677 void
          678 attachment(Attach *a, Biobuf *out)
          679 {
          680         Biobuf *f;
          681         char *p;
          682 
          683         f = emalloc(sizeof *f);
          684         Binit(f, a->fd, OREAD);
          685         /* if it's already mime encoded, just copy */
          686         if(strcmp(a->type, "mime") == 0){
          687                 copy(f, out);
          688                 Bterm(f);
          689                 free(f);
          690                 return;
          691         }
          692 
          693         /* if it's not already mime encoded ... */
          694         if(strcmp(a->type, "text/plain") != 0)
          695                 Bprint(out, "Content-Type: %s\n", a->type);
          696 
          697         if(a->inline){
          698                 Bprint(out, "Content-Disposition: inline\n");
          699         } else {
          700                 p = strrchr(a->path, '/');
          701                 if(p == nil)
          702                         p = a->path;
          703                 else
          704                         p++;
          705                 Bprint(out, "Content-Disposition: attachment; filename=%Z\n", p);
          706         }
          707 
          708         /* dump our local 'From ' line when passing along mail messages */
          709         if(strcmp(a->type, "message/rfc822") == 0){
          710                 p = Brdline(f, '\n');
          711                 if(strncmp(p, "From ", 5) != 0)
          712                         Bseek(f, 0, 0);
          713         }
          714         if(a->ctype->display){
          715                 body(f, out, strcmp(a->type, "text/plain") == 0);
          716         } else {
          717                 Bprint(out, "Content-Transfer-Encoding: base64\n");
          718                 body64(f, out);
          719         }
          720         Bterm(f);
          721         free(f);
          722 }
          723 
          724 char *ascwday[] =
          725 {
          726         "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
          727 };
          728 
          729 char *ascmon[] =
          730 {
          731         "Jan", "Feb", "Mar", "Apr", "May", "Jun",
          732         "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
          733 };
          734 
          735 int
          736 printdate(Biobuf *b)
          737 {
          738         Tm *tm;
          739         int tz;
          740 
          741         tm = localtime(time(0));
          742         tz = (tm->tzoff/3600)*100 + ((tm->tzoff/60)%60);
          743 
          744         return Bprint(b, "Date: %s, %d %s %d %2.2d:%2.2d:%2.2d %s%.4d\n",
          745                 ascwday[tm->wday], tm->mday, ascmon[tm->mon], 1900+tm->year,
          746                 tm->hour, tm->min, tm->sec, tz>=0?"+":"", tz);
          747 }
          748 
          749 int
          750 printfrom(Biobuf *b)
          751 {
          752         return Bprint(b, "From: %s\n", user);
          753 }
          754 
          755 int
          756 printto(Biobuf *b, Addr *a)
          757 {
          758         int i;
          759 
          760         if(Bprint(b, "To: %s", a->v) < 0)
          761                 return -1;
          762         i = 0;
          763         for(a = a->next; a != nil; a = a->next)
          764                 if(Bprint(b, "%s%s", ((i++ & 7) == 7)?",\n\t":", ", a->v) < 0)
          765                         return -1;
          766         if(Bprint(b, "\n") < 0)
          767                 return -1;
          768         return 0;
          769 }
          770 
          771 int
          772 printcc(Biobuf *b, Addr *a)
          773 {
          774         int i;
          775 
          776         if(a == nil)
          777                 return 0;
          778         if(Bprint(b, "CC: %s", a->v) < 0)
          779                 return -1;
          780         i = 0;
          781         for(a = a->next; a != nil; a = a->next)
          782                 if(Bprint(b, "%s%s", ((i++ & 7) == 7)?",\n\t":", ", a->v) < 0)
          783                         return -1;
          784         if(Bprint(b, "\n") < 0)
          785                 return -1;
          786         return 0;
          787 }
          788 
          789 int
          790 printsubject(Biobuf *b, char *subject)
          791 {
          792         return Bprint(b, "Subject: %s\n", subject);
          793 }
          794 
          795 int
          796 printinreplyto(Biobuf *out, char *dir)
          797 {
          798         String *s;
          799         char buf[256];
          800         int fd;
          801         int n;
          802 
          803         if(mountmail() < 0)
          804                 return -1;
          805         if(strncmp(dir, "Mail/", 5) != 0)
          806                 return -1;
          807         s = s_copy(dir+5);
          808         s_append(s, "/messageid");
          809         fd = fsopenfd(mailfs, s_to_c(s), OREAD);
          810         s_free(s);
          811         if(fd < 0)
          812                 return -1;
          813         n = readn(fd, buf, sizeof(buf)-1);
          814         close(fd);
          815         if(n <= 0)
          816                 return -1;
          817         buf[n] = 0;
          818         return Bprint(out, "In-Reply-To: %s\n", buf);
          819 }
          820 
          821 int
          822 mopen(char *file, int mode)
          823 {
          824         int fd;
          825 
          826         if((fd = open(file, mode)) >= 0)
          827                 return fd;
          828         if(strncmp(file, "Mail/", 5) == 0 && mountmail() >= 0 && (fd = fsopenfd(mailfs, file+5, mode)) >= 0)
          829                 return fd;
          830         return -1;
          831 }
          832 
          833 Attach*
          834 mkattach(char *file, char *type, int inline)
          835 {
          836         Ctype *c;
          837         Attach *a;
          838         char ftype[64];
          839         char *p;
          840         int fd, n, pfd[2], xfd[3];
          841 
          842         if(file == nil)
          843                 return nil;
          844         if((fd = mopen(file, OREAD)) < 0)
          845                 return nil;
          846         a = emalloc(sizeof(*a));
          847         a->fd = fd;
          848         a->path = file;
          849         a->next = nil;
          850         a->type = type;
          851         a->inline = inline;
          852         a->ctype = nil;
          853         if(type != nil){
          854                 for(c = ctype; ; c++)
          855                         if(strncmp(type, c->type, strlen(c->type)) == 0){
          856                                 a->ctype = c;
          857                                 break;
          858                         }
          859                 return a;
          860         }
          861 
          862         /* pick a type depending on extension */
          863         p = strchr(file, '.');
          864         if(p != nil)
          865                 p++;
          866 
          867         /* check the builtin extensions */
          868         if(p != nil){
          869                 for(c = ctype; c->ext != nil; c++)
          870                         if(strcmp(p, c->ext) == 0){
          871                                 a->type = c->type;
          872                                 a->ctype = c;
          873                                 return a;
          874                         }
          875         }
          876 
          877         /* try the mime types file */
          878         if(p != nil){
          879                 if(mimetypes == nil)
          880                         readmimetypes();
          881                 for(c = mimetypes; c != nil && c->ext != nil; c++)
          882                         if(strcmp(p, c->ext) == 0){
          883                                 a->type = c->type;
          884                                 a->ctype = c;
          885                                 return a;
          886                         }
          887         }
          888 
          889         /* run file to figure out the type */
          890         a->type = "application/octet-stream";                /* safest default */
          891         if(pipe(pfd) < 0)
          892                 return a;
          893 
          894         xfd[0] = mopen(file, OREAD);
          895         xfd[1] = pfd[0];
          896         xfd[2] = dup(2, -1);
          897         if((pid=threadspawnl(xfd, unsharp("#9/bin/file"), "file", "-m", nil)) < 0){
          898                 close(xfd[0]);
          899                 close(xfd[1]);
          900                 close(xfd[2]);
          901                 return a;
          902         }
          903         /* threadspawnl closed pfd[0] */
          904 
          905         n = readn(pfd[1], ftype, sizeof(ftype));
          906         if(n > 0){
          907                 ftype[n-1] = 0;
          908                 a->type = estrdup(ftype);
          909         }
          910         close(pfd[1]);
          911         procwait(pid);
          912 
          913         for(c = ctype; ; c++)
          914                 if(strncmp(a->type, c->type, strlen(c->type)) == 0){
          915                         a->ctype = c;
          916                         break;
          917                 }
          918 
          919         return a;
          920 }
          921 
          922 char*
          923 mkboundary(void)
          924 {
          925         char buf[32];
          926         int i;
          927 
          928         srand((time(0)<<16)|getpid());
          929         strcpy(buf, "upas-");
          930         for(i = 5; i < sizeof(buf)-1; i++)
          931                 buf[i] = 'a' + nrand(26);
          932         buf[i] = 0;
          933         return estrdup(buf);
          934 }
          935 
          936 /* copy types to two fd's */
          937 static void
          938 tee(int in, int out1, int out2)
          939 {
          940         char buf[8*1024];
          941         int n;
          942 
          943         for(;;){
          944                 n = read(in, buf, sizeof(buf));
          945                 if(n <= 0)
          946                         break;
          947                 if(write(out1, buf, n) < 0)
          948                         break;
          949                 if(write(out2, buf, n) < 0)
          950                         break;
          951         }
          952 }
          953 
          954 static void
          955 teeproc(void *v)
          956 {
          957         int *a;
          958 
          959         a = v;
          960         tee(a[0], a[1], a[2]);
          961         write(a[2], "\n", 1);
          962 }
          963 
          964 /* print the unix from line */
          965 int
          966 printunixfrom(int fd)
          967 {
          968         Tm *tm;
          969         int tz;
          970 
          971         tm = localtime(time(0));
          972         tz = (tm->tzoff/3600)*100 + ((tm->tzoff/60)%60);
          973 
          974         return fprint(fd, "From %s %s %s %d %2.2d:%2.2d:%2.2d %s%.4d %d\n",
          975                 user,
          976                 ascwday[tm->wday], ascmon[tm->mon], tm->mday,
          977                 tm->hour, tm->min, tm->sec, tz>=0?"+":"", tz, 1900+tm->year);
          978 }
          979 
          980 char *specialfile[] =
          981 {
          982         "pipeto",
          983         "pipefrom",
          984         "L.mbox",
          985         "forward",
          986         "names"
          987 };
          988 
          989 /* return 1 if this is a special file */
          990 static int
          991 special(String *s)
          992 {
          993         char *p;
          994         int i;
          995 
          996         p = strrchr(s_to_c(s), '/');
          997         if(p == nil)
          998                 p = s_to_c(s);
          999         else
         1000                 p++;
         1001         for(i = 0; i < nelem(specialfile); i++)
         1002                 if(strcmp(p, specialfile[i]) == 0)
         1003                         return 1;
         1004         return 0;
         1005 }
         1006 
         1007 /* open the folder using the recipients account name */
         1008 static int
         1009 openfolder(char *rcvr)
         1010 {
         1011         char *p;
         1012         int c;
         1013         String *file;
         1014         Dir *d;
         1015         int fd;
         1016         int scarey;
         1017 
         1018         file = s_new();
         1019         mboxpath("f", user, file, 0);
         1020 
         1021         /* if $mail/f exists, store there, otherwise in $mail */
         1022         d = dirstat(s_to_c(file));
         1023         if(d == nil || d->qid.type != QTDIR){
         1024                 scarey = 1;
         1025                 file->ptr -= 1;
         1026         } else {
         1027                 s_putc(file, '/');
         1028                 scarey = 0;
         1029         }
         1030         free(d);
         1031 
         1032         p = strrchr(rcvr, '!');
         1033         if(p != nil)
         1034                 rcvr = p+1;
         1035 
         1036         while(*rcvr && *rcvr != '@'){
         1037                 c = *rcvr++;
         1038                 if(c == '/')
         1039                         c = '_';
         1040                 s_putc(file, c);
         1041         }
         1042         s_terminate(file);
         1043 
         1044         if(scarey && special(file)){
         1045                 fprint(2, "%s: won't overwrite %s\n", argv0, s_to_c(file));
         1046                 s_free(file);
         1047                 return -1;
         1048         }
         1049 
         1050         fd = open(s_to_c(file), OWRITE);
         1051         if(fd < 0)
         1052                 fd = create(s_to_c(file), OWRITE, 0660);
         1053 
         1054         s_free(file);
         1055         return fd;
         1056 }
         1057 
         1058 /* start up sendmail and return an fd to talk to it with */
         1059 int
         1060 sendmail(Addr *to, Addr *cc, int *pid, char *rcvr)
         1061 {
         1062         char **av, **v;
         1063         int ac, fd, *targ;
         1064         int pfd[2], sfd, xfd[3];
         1065         String *cmd;
         1066         char *x;
         1067         Addr *a;
         1068 
         1069         fd = -1;
         1070         if(rcvr != nil)
         1071                 fd = openfolder(rcvr);
         1072 
         1073         ac = 0;
         1074         for(a = to; a != nil; a = a->next)
         1075                 ac++;
         1076         for(a = cc; a != nil; a = a->next)
         1077                 ac++;
         1078         v = av = emalloc(sizeof(char*)*(ac+20));
         1079         ac = 0;
         1080         v[ac++] = "sendmail";
         1081         if(xflag)
         1082                 v[ac++] = "-x";
         1083         if(rflag)
         1084                 v[ac++] = "-r";
         1085         if(lbflag)
         1086                 v[ac++] = "-#";
         1087         if(dflag)
         1088                 v[ac++] = "-d";
         1089         for(a = to; a != nil; a = a->next)
         1090                 v[ac++] = a->v;
         1091         for(a = cc; a != nil; a = a->next)
         1092                 v[ac++] = a->v;
         1093         v[ac] = 0;
         1094 
         1095         if(pipe(pfd) < 0)
         1096                 fatal("pipe: %r");
         1097 
         1098         xfd[0] = pfd[0];
         1099         xfd[1] = dup(1, -1);
         1100         xfd[2] = dup(2, -1);
         1101 
         1102         if(replymsg != nil)
         1103                 putenv("replymsg", replymsg);
         1104         cmd = mboxpath("pipefrom", login, s_new(), 0);
         1105 
         1106         if((*pid = threadspawn(xfd, x=s_to_c(cmd), av)) < 0
         1107         && (*pid = threadspawn(xfd, x="myupassend", av)) < 0
         1108         && (*pid = threadspawn(xfd, x=unsharp("#9/bin/upas/send"), av)) < 0)
         1109                 fatal("exec: %r");
         1110         /* threadspawn closed pfd[0] (== xfd[0]) */
         1111         sfd = pfd[1];
         1112 
         1113         if(rcvr != nil){
         1114                 if(pipe(pfd) < 0)
         1115                         fatal("pipe: %r");
         1116                 seek(fd, 0, 2);
         1117                 printunixfrom(fd);
         1118                 targ = emalloc(3*sizeof targ[0]);
         1119                 targ[0] = sfd;
         1120                 targ[1] = pfd[0];
         1121                 targ[2] = fd;
         1122                 proccreate(teeproc, targ, STACK);
         1123                 sfd = pfd[1];
         1124         }
         1125 
         1126         return sfd;
         1127 }
         1128 
         1129 /* start up pgp process and return an fd to talk to it with. */
         1130 /* its standard output will be the original fd, which goes to sendmail. */
         1131 int
         1132 pgpfilter(int *pid, int fd, int pgpflag)
         1133 {
         1134         char **av, **v;
         1135         int ac;
         1136         int pfd[2];
         1137 
         1138         v = av = emalloc(sizeof(char*)*8);
         1139         ac = 0;
         1140         v[ac++] = "pgp";
         1141         v[ac++] = "-fat";                /* operate as a filter, generate text */
         1142         if(pgpflag & PGPsign)
         1143                 v[ac++] = "-s";
         1144         if(pgpflag & PGPencrypt)
         1145                 v[ac++] = "-e";
         1146         v[ac] = 0;
         1147 
         1148         if(pipe(pfd) < 0)
         1149                 fatal("%r");
         1150         switch(*pid = fork()){
         1151         case -1:
         1152                 fatal("%r");
         1153                 break;
         1154         case 0:
         1155                 close(pfd[1]);
         1156                 dup(pfd[0], 0);
         1157                 close(pfd[0]);
         1158                 dup(fd, 1);
         1159                 close(fd);
         1160                 /* add newline to avoid confusing pgp output with 822 headers */
         1161                 write(1, "\n", 1);
         1162 
         1163                 exec("pgp", av);
         1164                 fatal("execing: %r");
         1165                 break;
         1166         default:
         1167                 close(pfd[0]);
         1168                 break;
         1169         }
         1170         close(fd);
         1171         return pfd[1];
         1172 }
         1173 
         1174 /* wait for sendmail and pgp to exit; exit here if either failed */
         1175 char*
         1176 waitforsubprocs(void)
         1177 {
         1178         Waitmsg *w;
         1179         char *err;
         1180 
         1181         err = nil;
         1182         if(pgppid >= 0 && (w=procwait(pgppid)) && w->msg[0])
         1183                 err = w->msg;
         1184         if(pid >= 0 && (w=procwait(pid)) && w->msg[0])
         1185                 err = w->msg;
         1186         return err;
         1187 }
         1188 
         1189 int
         1190 cistrncmp(char *a, char *b, int n)
         1191 {
         1192         while(n-- > 0){
         1193                 if(tolower(*a++) != tolower(*b++))
         1194                         return -1;
         1195         }
         1196         return 0;
         1197 }
         1198 
         1199 int
         1200 cistrcmp(char *a, char *b)
         1201 {
         1202         for(;;){
         1203                 if(tolower(*a) != tolower(*b++))
         1204                         return -1;
         1205                 if(*a++ == 0)
         1206                         break;
         1207         }
         1208         return 0;
         1209 }
         1210 
         1211 static uchar t64d[256];
         1212 static char t64e[64];
         1213 
         1214 static void
         1215 init64(void)
         1216 {
         1217         int c, i;
         1218 
         1219         memset(t64d, 255, 256);
         1220         memset(t64e, '=', 64);
         1221         i = 0;
         1222         for(c = 'A'; c <= 'Z'; c++){
         1223                 t64e[i] = c;
         1224                 t64d[c] = i++;
         1225         }
         1226         for(c = 'a'; c <= 'z'; c++){
         1227                 t64e[i] = c;
         1228                 t64d[c] = i++;
         1229         }
         1230         for(c = '0'; c <= '9'; c++){
         1231                 t64e[i] = c;
         1232                 t64d[c] = i++;
         1233         }
         1234         t64e[i] = '+';
         1235         t64d['+'] = i++;
         1236         t64e[i] = '/';
         1237         t64d['/'] = i;
         1238 }
         1239 
         1240 int
         1241 enc64(char *out, int lim, uchar *in, int n)
         1242 {
         1243         int i;
         1244         ulong b24;
         1245         char *start = out;
         1246         char *e = out + lim;
         1247 
         1248         if(t64e[0] == 0)
         1249                 init64();
         1250         for(i = 0; i < n/3; i++){
         1251                 b24 = (*in++)<<16;
         1252                 b24 |= (*in++)<<8;
         1253                 b24 |= *in++;
         1254                 if(out + 5 >= e)
         1255                         goto exhausted;
         1256                 *out++ = t64e[(b24>>18)];
         1257                 *out++ = t64e[(b24>>12)&0x3f];
         1258                 *out++ = t64e[(b24>>6)&0x3f];
         1259                 *out++ = t64e[(b24)&0x3f];
         1260                 if((i%18) == 17)
         1261                         *out++ = '\n';
         1262         }
         1263 
         1264         switch(n%3){
         1265         case 2:
         1266                 b24 = (*in++)<<16;
         1267                 b24 |= (*in)<<8;
         1268                 if(out + 4 >= e)
         1269                         goto exhausted;
         1270                 *out++ = t64e[(b24>>18)];
         1271                 *out++ = t64e[(b24>>12)&0x3f];
         1272                 *out++ = t64e[(b24>>6)&0x3f];
         1273                 break;
         1274         case 1:
         1275                 b24 = (*in)<<16;
         1276                 if(out + 4 >= e)
         1277                         goto exhausted;
         1278                 *out++ = t64e[(b24>>18)];
         1279                 *out++ = t64e[(b24>>12)&0x3f];
         1280                 *out++ = '=';
         1281                 break;
         1282         case 0:
         1283                 if((i%18) != 0)
         1284                         *out++ = '\n';
         1285                 *out = 0;
         1286                 return out - start;
         1287         }
         1288 exhausted:
         1289         *out++ = '=';
         1290         *out++ = '\n';
         1291         *out = 0;
         1292         return out - start;
         1293 }
         1294 
         1295 void
         1296 freealias(Alias *a)
         1297 {
         1298         freeaddrs(a->addr);
         1299         free(a);
         1300 }
         1301 
         1302 void
         1303 freealiases(Alias *a)
         1304 {
         1305         Alias *next;
         1306 
         1307         while(a != nil){
         1308                 next = a->next;
         1309                 freealias(a);
         1310                 a = next;
         1311         }
         1312 }
         1313 
         1314 /* */
         1315 /*  read alias file */
         1316 /* */
         1317 Alias*
         1318 readaliases(void)
         1319 {
         1320         Alias *a, **l, *first;
         1321         Addr *addr, **al;
         1322         String *file, *line, *token;
         1323         Sinstack *sp;
         1324 
         1325         first = nil;
         1326         file = s_new();
         1327         line = s_new();
         1328         token = s_new();
         1329 
         1330         /* open and get length */
         1331         mboxpath("names", login, file, 0);
         1332         sp = s_allocinstack(s_to_c(file));
         1333         if(sp == nil)
         1334                 goto out;
         1335 
         1336         l = &first;
         1337 
         1338         /* read a line at a time. */
         1339         while(s_rdinstack(sp, s_restart(line))!=nil) {
         1340                 s_restart(line);
         1341                 a = emalloc(sizeof(Alias));
         1342                 al = &a->addr;
         1343                 for(;;){
         1344                         if(s_parse(line, s_restart(token))==0)
         1345                                 break;
         1346                         addr = emalloc(sizeof(Addr));
         1347                         addr->v = strdup(s_to_c(token));
         1348                         addr->next = 0;
         1349                         *al = addr;
         1350                         al = &addr->next;
         1351                 }
         1352                 if(a->addr == nil || a->addr->next == nil){
         1353                         freealias(a);
         1354                         continue;
         1355                 }
         1356                 a->next = nil;
         1357                 *l = a;
         1358                 l = &a->next;
         1359         }
         1360         s_freeinstack(sp);
         1361 
         1362 out:
         1363         s_free(file);
         1364         s_free(line);
         1365         s_free(token);
         1366         return first;
         1367 }
         1368 
         1369 Addr*
         1370 newaddr(char *name)
         1371 {
         1372         Addr *a;
         1373 
         1374         a = emalloc(sizeof(*a));
         1375         a->next = nil;
         1376         a->v = estrdup(name);
         1377         if(a->v == nil)
         1378                 sysfatal("%r");
         1379         return a;
         1380 }
         1381 
         1382 /* */
         1383 /*  expand personal aliases since the names are meaningless in */
         1384 /*  other contexts */
         1385 /* */
         1386 Addr*
         1387 _expand(Addr *old, int *changedp)
         1388 {
         1389         Alias *al;
         1390         Addr *first, *next, **l, *a;
         1391 
         1392         *changedp = 0;
         1393         first = nil;
         1394         l = &first;
         1395         for(;old != nil; old = next){
         1396                 next = old->next;
         1397                 for(al = aliases; al != nil; al = al->next){
         1398                         if(strcmp(al->addr->v, old->v) == 0){
         1399                                 for(a = al->addr->next; a != nil; a = a->next){
         1400                                         *l = newaddr(a->v);
         1401                                         if(*l == nil)
         1402                                                 sysfatal("%r");
         1403                                         l = &(*l)->next;
         1404                                         *changedp = 1;
         1405                                 }
         1406                                 break;
         1407                         }
         1408                 }
         1409                 if(al != nil){
         1410                         freeaddr(old);
         1411                         continue;
         1412                 }
         1413                 *l = old;
         1414                 old->next = nil;
         1415                 l = &(*l)->next;
         1416         }
         1417         return first;
         1418 }
         1419 
         1420 Addr*
         1421 rexpand(Addr *old)
         1422 {
         1423         int i, changed;
         1424 
         1425         changed = 0;
         1426         for(i=0; i<32; i++){
         1427                 old = _expand(old, &changed);
         1428                 if(changed == 0)
         1429                         break;
         1430         }
         1431         return old;
         1432 }
         1433 
         1434 Addr*
         1435 unique(Addr *first)
         1436 {
         1437         Addr *a, **l, *x;
         1438 
         1439         for(a = first; a != nil; a = a->next){
         1440                 for(l = &a->next; *l != nil;){
         1441                         if(strcmp(a->v, (*l)->v) == 0){
         1442                                 x = *l;
         1443                                 *l = x->next;
         1444                                 freeaddr(x);
         1445                         } else
         1446                                 l = &(*l)->next;
         1447                 }
         1448         }
         1449         return first;
         1450 }
         1451 
         1452 Addr*
         1453 expand(int ac, char **av)
         1454 {
         1455         Addr *first, **l;
         1456         int i;
         1457 
         1458         first = nil;
         1459 
         1460         /* make a list of the starting addresses */
         1461         l = &first;
         1462         for(i = 0; i < ac; i++){
         1463                 *l = newaddr(av[i]);
         1464                 if(*l == nil)
         1465                         sysfatal("%r");
         1466                 l = &(*l)->next;
         1467         }
         1468 
         1469         /* recurse till we don't change any more */
         1470         return unique(rexpand(first));
         1471 }
         1472 
         1473 Addr*
         1474 concataddr(Addr *a, Addr *b)
         1475 {
         1476         Addr *oa;
         1477 
         1478         if(a == nil)
         1479                 return b;
         1480 
         1481         oa = a;
         1482         for(; a->next; a=a->next)
         1483                 ;
         1484         a->next = b;
         1485         return oa;
         1486 }
         1487 
         1488 void
         1489 freeaddr(Addr *ap)
         1490 {
         1491         free(ap->v);
         1492         free(ap);
         1493 }
         1494 
         1495 void
         1496 freeaddrs(Addr *ap)
         1497 {
         1498         Addr *next;
         1499 
         1500         for(; ap; ap=next) {
         1501                 next = ap->next;
         1502                 freeaddr(ap);
         1503         }
         1504 }
         1505 
         1506 String*
         1507 s_copyn(char *s, int n)
         1508 {
         1509         return s_nappend(s_reset(nil), s, n);
         1510 }
         1511 
         1512 /* fetch the next token from an RFC822 address string */
         1513 /* we assume the header is RFC822-conformant in that */
         1514 /* we recognize escaping anywhere even though it is only */
         1515 /* supposed to be in quoted-strings, domain-literals, and comments. */
         1516 /* */
         1517 /* i'd use yylex or yyparse here, but we need to preserve  */
         1518 /* things like comments, which i think it tosses away. */
         1519 /* */
         1520 /* we're not strictly RFC822 compliant.  we misparse such nonsense as */
         1521 /* */
         1522 /*        To: gre @ (Grace) plan9 . (Emlin) bell-labs.com */
         1523 /* */
         1524 /* make sure there's no whitespace in your addresses and  */
         1525 /* you'll be fine. */
         1526 /* */
         1527 enum {
         1528         Twhite,
         1529         Tcomment,
         1530         Twords,
         1531         Tcomma,
         1532         Tleftangle,
         1533         Trightangle,
         1534         Terror,
         1535         Tend
         1536 };
         1537 /*char *ty82[] = {"white", "comment", "words", "comma", "<", ">", "err", "end"}; */
         1538 #define ISWHITE(p) ((p)==' ' || (p)=='\t' || (p)=='\n' || (p)=='\r')
         1539 int
         1540 get822token(String **tok, char *p, char **pp)
         1541 {
         1542         char *op;
         1543         int type;
         1544         int quoting;
         1545 
         1546         op = p;
         1547         switch(*p){
         1548         case '\0':
         1549                 *tok = nil;
         1550                 *pp = nil;
         1551                 return Tend;
         1552 
         1553         case ' ':        /* get whitespace */
         1554         case '\t':
         1555         case '\n':
         1556         case '\r':
         1557                 type = Twhite;
         1558                 while(ISWHITE(*p))
         1559                         p++;
         1560                 break;
         1561 
         1562         case '(':        /* get comment */
         1563                 type = Tcomment;
         1564                 for(p++; *p && *p != ')'; p++)
         1565                         if(*p == '\\') {
         1566                                 if(*(p+1) == '\0') {
         1567                                         *tok = nil;
         1568                                         return Terror;
         1569                                 }
         1570                                 p++;
         1571                         }
         1572 
         1573                 if(*p != ')') {
         1574                         *tok = nil;
         1575                         return Terror;
         1576                 }
         1577                 p++;
         1578                 break;
         1579         case ',':
         1580                 type = Tcomma;
         1581                 p++;
         1582                 break;
         1583         case '<':
         1584                 type = Tleftangle;
         1585                 p++;
         1586                 break;
         1587         case '>':
         1588                 type = Trightangle;
         1589                 p++;
         1590                 break;
         1591         default:        /* bunch of letters, perhaps quoted strings tossed in */
         1592                 type = Twords;
         1593                 quoting = 0;
         1594                 for(; *p && (quoting || (!ISWHITE(*p) && *p != '>' && *p != '<' && *p != ',')); p++) {
         1595                         if(*p == '"')
         1596                                 quoting = !quoting;
         1597                         if(*p == '\\') {
         1598                                 if(*(p+1) == '\0') {
         1599                                         *tok = nil;
         1600                                         return Terror;
         1601                                 }
         1602                                 p++;
         1603                         }
         1604                 }
         1605                 break;
         1606         }
         1607 
         1608         if(pp)
         1609                 *pp = p;
         1610         *tok = s_copyn(op, p-op);
         1611         return type;
         1612 }
         1613 
         1614 /* expand local aliases in an RFC822 mail line */
         1615 /* add list of expanded addresses to to. */
         1616 Addr*
         1617 expandline(String **s, Addr *to)
         1618 {
         1619         Addr *na, *nto, *ap;
         1620         char *p;
         1621         int tok, inangle, hadangle, nword;
         1622         String *os, *ns, *stok, *lastword, *sinceword;
         1623 
         1624         os = s_copy(s_to_c(*s));
         1625         p = strchr(s_to_c(*s), ':');
         1626         assert(p != nil);
         1627         p++;
         1628 
         1629         ns = s_copyn(s_to_c(*s), p-s_to_c(*s));
         1630         stok = nil;
         1631         nto = nil;
         1632         /* */
         1633         /* the only valid mailbox namings are word */
         1634         /* and word* < addr > */
         1635         /* without comments this would be simple. */
         1636         /* we keep the following: */
         1637         /*        lastword - current guess at the address */
         1638         /*        sinceword - whitespace and comment seen since lastword */
         1639         /* */
         1640         lastword = s_new();
         1641         sinceword = s_new();
         1642         inangle = 0;
         1643         nword = 0;
         1644         hadangle = 0;
         1645         for(;;) {
         1646                 stok = nil;
         1647                 switch(tok = get822token(&stok, p, &p)){
         1648                 default:
         1649                         abort();
         1650                 case Tcomma:
         1651                 case Tend:
         1652                         if(inangle)
         1653                                 goto Error;
         1654                         if(nword != 1)
         1655                                 goto Error;
         1656                         na = rexpand(newaddr(s_to_c(lastword)));
         1657                         s_append(ns, na->v);
         1658                         s_append(ns, s_to_c(sinceword));
         1659                         for(ap=na->next; ap; ap=ap->next) {
         1660                                 s_append(ns, ", ");
         1661                                 s_append(ns, ap->v);
         1662                         }
         1663                         nto = concataddr(na, nto);
         1664                         if(tok == Tcomma){
         1665                                 s_append(ns, ",");
         1666                                 s_free(stok);
         1667                         }
         1668                         if(tok == Tend)
         1669                                 goto Break2;
         1670                         inangle = 0;
         1671                         nword = 0;
         1672                         hadangle = 0;
         1673                         s_reset(sinceword);
         1674                         s_reset(lastword);
         1675                         break;
         1676                 case Twhite:
         1677                 case Tcomment:
         1678                         s_append(sinceword, s_to_c(stok));
         1679                         s_free(stok);
         1680                         break;
         1681                 case Trightangle:
         1682                         if(!inangle)
         1683                                 goto Error;
         1684                         inangle = 0;
         1685                         hadangle = 1;
         1686                         s_append(sinceword, s_to_c(stok));
         1687                         s_free(stok);
         1688                         break;
         1689                 case Twords:
         1690                 case Tleftangle:
         1691                         if(hadangle)
         1692                                 goto Error;
         1693                         if(tok != Tleftangle && inangle && s_len(lastword))
         1694                                 goto Error;
         1695                         if(tok == Tleftangle) {
         1696                                 inangle = 1;
         1697                                 nword = 1;
         1698                         }
         1699                         s_append(ns, s_to_c(lastword));
         1700                         s_append(ns, s_to_c(sinceword));
         1701                         s_reset(sinceword);
         1702                         if(tok == Tleftangle) {
         1703                                 s_append(ns, "<");
         1704                                 s_reset(lastword);
         1705                         } else {
         1706                                 s_free(lastword);
         1707                                 lastword = stok;
         1708                         }
         1709                         if(!inangle)
         1710                                 nword++;
         1711                         break;
         1712                 case Terror:        /* give up, use old string, addrs */
         1713                 Error:
         1714                         ns = os;
         1715                         os = nil;
         1716                         freeaddrs(nto);
         1717                         nto = nil;
         1718                         werrstr("rfc822 syntax error");
         1719                         rfc822syntaxerror = 1;
         1720                         goto Break2;
         1721                 }
         1722         }
         1723 Break2:
         1724         s_free(*s);
         1725         s_free(os);
         1726         *s = ns;
         1727         nto = concataddr(nto, to);
         1728         return nto;
         1729 }
         1730 
         1731 void
         1732 Bdrain(Biobuf *b)
         1733 {
         1734         char buf[8192];
         1735 
         1736         while(Bread(b, buf, sizeof buf) > 0)
         1737                 ;
         1738 }
         1739 
         1740 void
         1741 readmimetypes(void)
         1742 {
         1743         Biobuf *b;
         1744         char *p;
         1745         char *f[6];
         1746         char type[256];
         1747         static int alloced, inuse;
         1748 
         1749         if(mimetypes == 0){
         1750                 alloced = 256;
         1751                 mimetypes = emalloc(alloced*sizeof(Ctype));
         1752                 mimetypes[0].ext = "";
         1753         }
         1754 
         1755         b = Bopen(unsharp("#9/lib/mimetype"), OREAD);
         1756         if(b == nil)
         1757                 return;
         1758         for(;;){
         1759                 p = Brdline(b, '\n');
         1760                 if(p == nil)
         1761                         break;
         1762                 p[Blinelen(b)-1] = 0;
         1763                 if(tokenize(p, f, 6) < 4)
         1764                         continue;
         1765                 if(strcmp(f[0], "-") == 0 || strcmp(f[1], "-") == 0 || strcmp(f[2], "-") == 0)
         1766                         continue;
         1767                 if(inuse + 1 >= alloced){
         1768                         alloced += 256;
         1769                         mimetypes = erealloc(mimetypes, alloced*sizeof(Ctype));
         1770                 }
         1771                 snprint(type, sizeof(type), "%s/%s", f[1], f[2]);
         1772                 mimetypes[inuse].type = estrdup(type);
         1773                 mimetypes[inuse].ext = estrdup(f[0]+1);
         1774                 mimetypes[inuse].display = !strcmp(type, "text/plain");
         1775                 inuse++;
         1776 
         1777                 /* always make sure there's a terminator */
         1778                 mimetypes[inuse].ext = 0;
         1779         }
         1780         Bterm(b);
         1781 }
         1782 
         1783 char*
         1784 estrdup(char *x)
         1785 {
         1786         x = strdup(x);
         1787         if(x == nil)
         1788                 fatal("memory");
         1789         return x;
         1790 }
         1791 
         1792 void*
         1793 emalloc(int n)
         1794 {
         1795         void *x;
         1796 
         1797         x = malloc(n);
         1798         if(x == nil)
         1799                 fatal("%r");
         1800         return x;
         1801 }
         1802 
         1803 void*
         1804 erealloc(void *x, int n)
         1805 {
         1806         x = realloc(x, n);
         1807         if(x == nil)
         1808                 fatal("%r");
         1809         return x;
         1810 }
         1811 
         1812 /* */
         1813 /* Formatter for %" */
         1814 /* Use double quotes to protect white space, frogs, \ and " */
         1815 /* */
         1816 enum
         1817 {
         1818         Qok = 0,
         1819         Qquote,
         1820         Qbackslash
         1821 };
         1822 
         1823 static int
         1824 needtoquote(Rune r)
         1825 {
         1826         if(r >= Runeself)
         1827                 return Qquote;
         1828         if(r <= ' ')
         1829                 return Qquote;
         1830         if(r=='\\' || r=='"')
         1831                 return Qbackslash;
         1832         return Qok;
         1833 }
         1834 
         1835 int
         1836 doublequote(Fmt *f)
         1837 {
         1838         char *s, *t;
         1839         int w, quotes;
         1840         Rune r;
         1841 
         1842         s = va_arg(f->args, char*);
         1843         if(s == nil || *s == '\0')
         1844                 return fmtstrcpy(f, "\"\"");
         1845 
         1846         quotes = 0;
         1847         for(t=s; *t; t+=w){
         1848                 w = chartorune(&r, t);
         1849                 quotes |= needtoquote(r);
         1850         }
         1851         if(quotes == 0)
         1852                 return fmtstrcpy(f, s);
         1853 
         1854         fmtrune(f, '"');
         1855         for(t=s; *t; t+=w){
         1856                 w = chartorune(&r, t);
         1857                 if(needtoquote(r) == Qbackslash)
         1858                         fmtrune(f, '\\');
         1859                 fmtrune(f, r);
         1860         }
         1861         return fmtrune(f, '"');
         1862 }
         1863 
         1864 int
         1865 mountmail(void)
         1866 {
         1867         if(mailfs != nil)
         1868                 return 0;
         1869         if((mailfs = nsmount("mail", nil)) == nil)
         1870                 return -1;
         1871         return 0;
         1872 }
         1873 
         1874 int
         1875 rfc2047fmt(Fmt *fmt)
         1876 {
         1877         char *s, *p;
         1878 
         1879         s = va_arg(fmt->args, char*);
         1880         if(s == nil)
         1881                 return fmtstrcpy(fmt, "");
         1882         for(p=s; *p; p++)
         1883                 if((uchar)*p >= 0x80)
         1884                         goto hard;
         1885         return fmtstrcpy(fmt, s);
         1886 
         1887 hard:
         1888         fmtprint(fmt, "=?utf-8?q?");
         1889         for(p=s; *p; p++){
         1890                 if(*p == ' ')
         1891                         fmtrune(fmt, '_');
         1892                 else if(*p == '_' || *p == '\t' || *p == '=' || *p == '?' || (uchar)*p >= 0x80)
         1893                         fmtprint(fmt, "=%.2uX", (uchar)*p);
         1894                 else
         1895                         fmtrune(fmt, (uchar)*p);
         1896         }
         1897         fmtprint(fmt, "?=");
         1898         return 0;
         1899 }
         1900 
         1901 char*
         1902 mksubject(char *line)
         1903 {
         1904         char *p, *q;
         1905         static char buf[1024];
         1906 
         1907         p = strchr(line, ':')+1;
         1908         while(*p == ' ')
         1909                 p++;
         1910         for(q=p; *q; q++)
         1911                 if((uchar)*q >= 0x80)
         1912                         goto hard;
         1913         return line;
         1914 
         1915 hard:
         1916         snprint(buf, sizeof buf, "Subject: %U", p);
         1917         return buf;
         1918 }