URI:
       tnedmail.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
       ---
       tnedmail.c (47272B)
       ---
            1 #include "common.h"
            2 #include <ctype.h>
            3 #include <plumb.h>
            4 #include <9pclient.h>
            5 #include <thread.h>
            6 
            7 #define system nedsystem
            8 #define rcmd nedrcmd
            9 
           10 typedef struct Message Message;
           11 typedef struct Ctype Ctype;
           12 typedef struct Cmd Cmd;
           13 
           14 char        root[Pathlen];
           15 char        mbname[Elemlen];
           16 int        rootlen;
           17 int        didopen;
           18 char        *user;
           19 char        wd[2048];
           20 String        *mbpath;
           21 int        natural;
           22 int        doflush;
           23 
           24 int interrupted;
           25 
           26 struct Message {
           27         Message        *next;
           28         Message        *prev;
           29         Message        *cmd;
           30         Message        *child;
           31         Message        *parent;
           32         String        *path;
           33         int        id;
           34         int        len;
           35         int        fileno;        /* number of directory */
           36         String        *info;
           37         char        *from;
           38         char        *to;
           39         char        *cc;
           40         char        *replyto;
           41         char        *date;
           42         char        *subject;
           43         char        *type;
           44         char        *disposition;
           45         char        *filename;
           46         char        deleted;
           47         char        stored;
           48 };
           49 
           50 Message top;
           51 
           52 struct Ctype {
           53         char        *type;
           54         char         *ext;
           55         int        display;
           56         char        *plumbdest;
           57         Ctype        *next;
           58 };
           59 
           60 Ctype ctype[] = {
           61         { "text/plain",                        "txt",        1,        0        },
           62         { "text/html",                        "htm",        1,        0        },
           63         { "text/html",                        "html",        1,        0        },
           64         { "text/tab-separated-values",        "tsv",        1,        0        },
           65         { "text/richtext",                "rtx",        1,        0        },
           66         { "text/rtf",                        "rtf",        1,        0        },
           67         { "text",                        "txt",        1,        0        },
           68         { "message/rfc822",                "msg",        0,        0        },
           69         { "message/delivery-status",        "txt",        1,        0        },
           70         { "image/bmp",                        "bmp",        0,        "image"        },
           71         { "image/jpeg",                        "jpg",        0,        "image"        },
           72         { "image/gif",                        "gif",        0,        "image"        },
           73         { "image/png",                        "png",        0,        "image"        },
           74         { "application/pdf",                "pdf",        0,        "postscript"        },
           75         { "application/postscript",        "ps",        0,        "postscript"        },
           76         { "application/",                0,        0,        0        },
           77         { "image/",                        0,        0,        0        },
           78         { "multipart/",                        "mul",        0,        0        },
           79 
           80 };
           81 
           82 Message*        acmd(Cmd*, Message*);
           83 Message*        bcmd(Cmd*, Message*);
           84 Message*        dcmd(Cmd*, Message*);
           85 Message*        eqcmd(Cmd*, Message*);
           86 Message*        hcmd(Cmd*, Message*);
           87 Message*        Hcmd(Cmd*, Message*);
           88 Message*        helpcmd(Cmd*, Message*);
           89 Message*        icmd(Cmd*, Message*);
           90 Message*        pcmd(Cmd*, Message*);
           91 Message*        qcmd(Cmd*, Message*);
           92 Message*        rcmd(Cmd*, Message*);
           93 Message*        scmd(Cmd*, Message*);
           94 Message*        ucmd(Cmd*, Message*);
           95 Message*        wcmd(Cmd*, Message*);
           96 Message*        xcmd(Cmd*, Message*);
           97 Message*        ycmd(Cmd*, Message*);
           98 Message*        pipecmd(Cmd*, Message*);
           99 Message*        rpipecmd(Cmd*, Message*);
          100 Message*        bangcmd(Cmd*, Message*);
          101 Message*        Pcmd(Cmd*, Message*);
          102 Message*        mcmd(Cmd*, Message*);
          103 Message*        fcmd(Cmd*, Message*);
          104 Message*        quotecmd(Cmd*, Message*);
          105 
          106 struct {
          107         char                *cmd;
          108         int                args;
          109         Message*        (*f)(Cmd*, Message*);
          110         char                *help;
          111 } cmdtab[] = {
          112         { "a",        1,        acmd,        "a        reply to sender and recipients" },
          113         { "A",        1,        acmd,        "A        reply to sender and recipients with copy" },
          114         { "b",        0,        bcmd,        "b        print the next 10 headers" },
          115         { "d",        0,        dcmd,        "d        mark for deletion" },
          116         { "f",        0,        fcmd,        "f        file message by from address" },
          117         { "h",        0,        hcmd,        "h        print elided message summary (,h for all)" },
          118         { "help", 0,        helpcmd, "help     print this info" },
          119         { "H",        0,        Hcmd,        "H        print message's MIME structure " },
          120         { "i",        0,        icmd,        "i        incorporate new mail" },
          121         { "m",        1,        mcmd,        "m addr   forward mail" },
          122         { "M",        1,        mcmd,        "M addr   forward mail with message" },
          123         { "p",        0,        pcmd,        "p        print the processed message" },
          124         { "P",        0,        Pcmd,        "P        print the raw message" },
          125         { "\"",        0,        quotecmd, "\"        print a quoted version of msg" },
          126         { "q",        0,        qcmd,        "q        exit and remove all deleted mail" },
          127         { "r",        1,        rcmd,        "r [addr] reply to sender plus any addrs specified" },
          128         { "rf",        1,        rcmd,        "rf [addr]file message and reply" },
          129         { "R",        1,        rcmd,        "R [addr] reply including copy of message" },
          130         { "Rf",        1,        rcmd,        "Rf [addr]file message and reply with copy" },
          131         { "s",        1,        scmd,        "s file   append raw message to file" },
          132         { "u",        0,        ucmd,        "u        remove deletion mark" },
          133         { "w",        1,        wcmd,        "w file   store message contents as file" },
          134         { "x",        0,        xcmd,        "x        exit without flushing deleted messages" },
          135         { "y",        0,        ycmd,        "y        synchronize with mail box" },
          136         { "=",        1,        eqcmd,        "=        print current message number" },
          137         { "|",        1,        pipecmd, "|cmd     pipe message body to a command" },
          138         { "||",        1,        rpipecmd, "||cmd     pipe raw message to a command" },
          139         { "!",        1,        bangcmd, "!cmd     run a command" },
          140         { nil,        0,        nil,         nil }
          141 };
          142 
          143 enum
          144 {
          145         NARG=        32
          146 };
          147 
          148 struct Cmd {
          149         Message        *msgs;
          150         Message        *(*f)(Cmd*, Message*);
          151         int        an;
          152         char        *av[NARG];
          153         int        delete;
          154 };
          155 
          156 Biobuf out;
          157 int startedfs;
          158 int reverse;
          159 int longestfrom = 12;
          160 
          161 String*                file2string(String*, char*);
          162 int                dir2message(Message*, int);
          163 int                filelen(String*, char*);
          164 String*                extendpath(String*, char*);
          165 void                snprintheader(char*, int, Message*);
          166 void                cracktime(char*, char*, int);
          167 int                cistrncmp(char*, char*, int);
          168 int                cistrcmp(char*, char*);
          169 Reprog*                parsesearch(char**);
          170 char*                parseaddr(char**, Message*, Message*, Message*, Message**);
          171 char*                parsecmd(char*, Cmd*, Message*, Message*);
          172 char*                readline(char*, char*, int);
          173 void                messagecount(Message*);
          174 void                system(char*, char**, int);
          175 void                mkid(String*, Message*);
          176 int                switchmb(char*, char*);
          177 void                closemb(void);
          178 int                lineize(char*, char**, int);
          179 int                rawsearch(Message*, Reprog*);
          180 Message*        dosingleton(Message*, char*);
          181 String*                rooted(String*);
          182 int                plumb(Message*, Ctype*);
          183 String*                addrecolon(char*);
          184 void                exitfs(char*);
          185 Message*        flushdeleted(Message*);
          186 
          187 CFsys *mailfs;
          188 
          189 void
          190 usage(void)
          191 {
          192         fprint(2, "usage: %s [-nr] [-f mailfile] [-s mailfile]\n", argv0);
          193         fprint(2, "       %s -c dir\n", argv0);
          194         threadexitsall("usage");
          195 }
          196 
          197 void
          198 catchnote(void *x, char *note)
          199 {
          200         USED(x);
          201 
          202         if(strstr(note, "interrupt") != nil){
          203                 interrupted = 1;
          204                 noted(NCONT);
          205         }
          206         noted(NDFLT);
          207 }
          208 
          209 char *
          210 plural(int n)
          211 {
          212         if (n == 1)
          213                 return "";
          214 
          215         return "s";
          216 }
          217 
          218 void
          219 threadmain(int argc, char **argv)
          220 {
          221         Message *cur, *m, *x;
          222         char cmdline[4*1024];
          223         Cmd cmd;
          224         Ctype *cp;
          225         char *err;
          226         int n, cflag;
          227         String *prompt;
          228         char *file, *singleton, *service;
          229 
          230         Binit(&out, 1, OWRITE);
          231 
          232         file = nil;
          233         singleton = nil;
          234         reverse = 1;
          235         cflag = 0;
          236         service = "mail";
          237         ARGBEGIN {
          238         case 'S':
          239                 service = EARGF(usage());
          240                 break;
          241         case 'c':
          242                 cflag = 1;
          243                 break;
          244         case 'f':
          245                 file = EARGF(usage());
          246                 break;
          247         case 's':
          248                 singleton = EARGF(usage());
          249                 break;
          250         case 'r':
          251                 reverse = 0;
          252                 break;
          253         case 'n':
          254                 natural = 1;
          255                 reverse = 0;
          256                 break;
          257         default:
          258                 usage();
          259                 break;
          260         } ARGEND;
          261 
          262         user = getlog();
          263         if(user == nil || *user == 0)
          264                 sysfatal("can't read user name");
          265 
          266         if(cflag){
          267                 if(argc > 0)
          268                         creatembox(user, argv[0]);
          269                 else
          270                         creatembox(user, nil);
          271                 threadexitsall(0);
          272         }
          273 
          274         if(argc)
          275                 usage();
          276         if((mailfs = nsmount(service, nil)) == nil)
          277                 sysfatal("cannot mount %s: %r", service);
          278 
          279         switchmb(file, singleton);
          280 
          281         top.path = s_copy(root);
          282 
          283         for(cp = ctype; cp < ctype+nelem(ctype)-1; cp++)
          284                 cp->next = cp+1;
          285 
          286         if(singleton != nil){
          287                 cur = dosingleton(&top, singleton);
          288                 if(cur == nil){
          289                         Bprint(&out, "no message\n");
          290                         exitfs(0);
          291                 }
          292                 pcmd(nil, cur);
          293         } else {
          294                 cur = &top;
          295                 n = dir2message(&top, reverse);
          296                 if(n < 0)
          297                         sysfatal("can't read %s", s_to_c(top.path));
          298                 Bprint(&out, "%d message%s\n", n, plural(n));
          299         }
          300 
          301 
          302         notify(catchnote);
          303         prompt = s_new();
          304         for(;;){
          305                 s_reset(prompt);
          306                 if(cur == &top)
          307                         s_append(prompt, ": ");
          308                 else {
          309                         mkid(prompt, cur);
          310                         s_append(prompt, ": ");
          311                 }
          312 
          313                 /* leave space at the end of cmd line in case parsecmd needs to */
          314                 /* add a space after a '|' or '!' */
          315                 if(readline(s_to_c(prompt), cmdline, sizeof(cmdline)-1) == nil)
          316                         break;
          317                 err = parsecmd(cmdline, &cmd, top.child, cur);
          318                 if(err != nil){
          319                         Bprint(&out, "!%s\n", err);
          320                         continue;
          321                 }
          322                 if(singleton != nil && cmd.f == icmd){
          323                         Bprint(&out, "!illegal command\n");
          324                         continue;
          325                 }
          326                 interrupted = 0;
          327                 if(cmd.msgs == nil || cmd.msgs == &top){
          328                         x = (*cmd.f)(&cmd, &top);
          329                         if(x != nil)
          330                                 cur = x;
          331                 } else for(m = cmd.msgs; m != nil; m = m->cmd){
          332                         x = m;
          333                         if(cmd.delete){
          334                                 dcmd(&cmd, x);
          335 
          336                                 /* dp acts differently than all other commands */
          337                                 /* since its an old lesk idiom that people love. */
          338                                 /* it deletes the current message, moves the current */
          339                                 /* pointer ahead one and prints. */
          340                                 if(cmd.f == pcmd){
          341                                         if(x->next == nil){
          342                                                 Bprint(&out, "!address\n");
          343                                                 cur = x;
          344                                                 break;
          345                                         } else
          346                                                 x = x->next;
          347                                 }
          348                         }
          349                         x = (*cmd.f)(&cmd, x);
          350                         if(x != nil)
          351                                 cur = x;
          352                         if(interrupted)
          353                                 break;
          354                         if(singleton != nil && (cmd.delete || cmd.f == dcmd))
          355                                 qcmd(nil, nil);
          356                 }
          357                 if(doflush)
          358                         cur = flushdeleted(cur);
          359         }
          360         qcmd(nil, nil);
          361 }
          362 
          363 static char*
          364 mkaddrs(char *t)
          365 {
          366         int i, nf, inquote;
          367         char **f, *s;
          368         Fmt fmt;
          369 
          370         inquote = 0;
          371         nf = 2;
          372         for(s=t; *s; s++){
          373                 if(*s == '\'')
          374                         inquote = !inquote;
          375                 if(*s == ' ' && !inquote)
          376                         nf++;
          377         }
          378         f = malloc(nf*sizeof f[0]);
          379         if(f == nil)
          380                 return nil;
          381         nf = tokenize(t, f, nf);
          382         fmtstrinit(&fmt);
          383         for(i=0; i+1<nf; i+=2){
          384                 if(i > 0)
          385                         fmtprint(&fmt, " ");
          386         /*        if(f[i][0] == 0 || strcmp(f[i], f[i+1]) == 0) */
          387                         fmtprint(&fmt, "%s", f[i+1]);
          388         /*        else */
          389         /*                fmtprint(&fmt, "%s <%s>", f[i], f[i+1]); */
          390         }
          391         free(f);
          392         return fmtstrflush(&fmt);
          393 }
          394 
          395 /* */
          396 /* read the message info */
          397 /* */
          398 Message*
          399 file2message(Message *parent, char *name)
          400 {
          401         Message *m;
          402         String *path;
          403         char *f[30], *s, *t;
          404         int i, nf;
          405 
          406         m = mallocz(sizeof(Message), 1);
          407         if(m == nil)
          408                 return nil;
          409         m->path = path = extendpath(parent->path, name);
          410         m->fileno = atoi(name);
          411         m->info = file2string(path, "info");
          412         m->from = "";
          413         m->to = "";
          414         m->cc = "";
          415         m->replyto = "";
          416         m->date = "";
          417         m->subject = "";
          418         m->type = "";
          419         m->disposition = "";
          420         m->filename = "";
          421         nf = lineize(s_to_c(m->info), f, nelem(f));
          422         for(i=0; i<nf; i++){
          423                 s = f[i];
          424                 t = strchr(f[i], ' ');
          425                 if(t == nil)
          426                         continue;
          427                 *t++ = 0;
          428 
          429                 if(strcmp(s, "from") == 0)
          430                         m->from = mkaddrs(t);
          431                 else if(strcmp(s, "to") == 0)
          432                         m->to = mkaddrs(t);
          433                 else if(strcmp(s, "cc") == 0)
          434                         m->cc = mkaddrs(t);
          435                 else if(strcmp(s, "replyto") == 0)
          436                         m->replyto = mkaddrs(t);
          437                 else if(strcmp(s, "unixdate") == 0 && (t=strchr(t, ' ')) != nil)
          438                         m->date = t;
          439                 else if(strcmp(s, "subject") == 0)
          440                         m->subject = t;
          441                 else if(strcmp(s, "type") == 0)
          442                         m->type = t;
          443                 else if(strcmp(s, "disposition") == 0)
          444                         m->disposition = t;
          445                 else if(strcmp(s, "filename") == 0)
          446                         m->filename = t;
          447         }
          448         m->len = filelen(path, "raw");
          449         if(strstr(m->type, "multipart") != nil || strcmp(m->type, "message/rfc822") == 0)
          450                 dir2message(m, 0);
          451         m->parent = parent;
          452 
          453         return m;
          454 }
          455 
          456 void
          457 freemessage(Message *m)
          458 {
          459         Message *nm, *next;
          460 
          461         for(nm = m->child; nm != nil; nm = next){
          462                 next = nm->next;
          463                 freemessage(nm);
          464         }
          465         s_free(m->path);
          466         s_free(m->info);
          467         free(m);
          468 }
          469 
          470 /* */
          471 /*  read a directory into a list of messages */
          472 /* */
          473 int
          474 dir2message(Message *parent, int reverse)
          475 {
          476         int i, n, highest, newmsgs;
          477         CFid *fd;
          478 
          479         Dir *d;
          480         Message *first, *last, *m;
          481 
          482         fd = fsopen(mailfs, s_to_c(parent->path), OREAD);
          483         if(fd == nil)
          484                 return -1;
          485 
          486         /* count current entries */
          487         first = parent->child;
          488         highest = newmsgs = 0;
          489         for(last = parent->child; last != nil && last->next != nil; last = last->next)
          490                 if(last->fileno > highest)
          491                         highest = last->fileno;
          492         if(last != nil)
          493                 if(last->fileno > highest)
          494                         highest = last->fileno;
          495 
          496         n = fsdirreadall(fd, &d);
          497         for(i = 0; i < n; i++){
          498                 if((d[i].qid.type & QTDIR) == 0)
          499                         continue;
          500                 if(atoi(d[i].name) <= highest)
          501                         continue;
          502                 m = file2message(parent, d[i].name);
          503                 /* fprint(2,"returned from file2message\n"); */
          504                 if(m == nil)
          505                         break;
          506                 newmsgs++;
          507                 if(reverse){
          508                         m->next = first;
          509                         if(first != nil)
          510                                 first->prev = m;
          511                         first = m;
          512                 } else {
          513                         if(first == nil)
          514                                 first = m;
          515                         else
          516                                 last->next = m;
          517                         m->prev = last;
          518                         last = m;
          519                 }
          520         }
          521         free(d);
          522         fsclose(fd);
          523         parent->child = first;
          524 
          525         /* renumber and file longest from */
          526         i = 1;
          527         longestfrom = 12;
          528         for(m = first; m != nil; m = m->next){
          529                 m->id = natural ? m->fileno : i++;
          530                 n = strlen(m->from);
          531                 if(n > longestfrom)
          532                         longestfrom = n;
          533         }
          534 
          535         return newmsgs;
          536 }
          537 
          538 /* */
          539 /*  point directly to a message */
          540 /* */
          541 Message*
          542 dosingleton(Message *parent, char *path)
          543 {
          544         char *p, *np;
          545         Message *m;
          546 
          547         /* walk down to message and read it */
          548         if(strlen(path) < rootlen)
          549                 return nil;
          550         if(path[rootlen] != '/')
          551                 return nil;
          552         p = path+rootlen+1;
          553         np = strchr(p, '/');
          554         if(np != nil)
          555                 *np = 0;
          556         m = file2message(parent, p);
          557         if(m == nil)
          558                 return nil;
          559         parent->child = m;
          560         m->id = 1;
          561 
          562         /* walk down to requested component */
          563         while(np != nil){
          564                 *np = '/';
          565                 np = strchr(np+1, '/');
          566                 if(np != nil)
          567                         *np = 0;
          568                 for(m = m->child; m != nil; m = m->next)
          569                         if(strcmp(path, s_to_c(m->path)) == 0)
          570                                 return m;
          571                 if(m == nil)
          572                         return nil;
          573         }
          574         return m;
          575 }
          576 
          577 /* */
          578 /*  read a file into a string */
          579 /* */
          580 String*
          581 file2string(String *dir, char *file)
          582 {
          583         String *s;
          584         int n, m;
          585         CFid *fd;
          586 
          587         s = extendpath(dir, file);
          588         fd = fsopen(mailfs, s_to_c(s), OREAD);
          589         s_grow(s, 512);                        /* avoid multiple reads on info files */
          590         s_reset(s);
          591         if(fd == nil)
          592                 return s;
          593 
          594         for(;;){
          595                 n = s->end - s->ptr;
          596                 if(n == 0){
          597                         s_grow(s, 128);
          598                         continue;
          599                 }
          600                 m = fsread(fd, s->ptr, n);
          601                 if(m <= 0)
          602                         break;
          603                 s->ptr += m;
          604                 if(m < n)
          605                         break;
          606         }
          607         s_terminate(s);
          608         fsclose(fd);
          609 
          610         return s;
          611 }
          612 
          613 /* */
          614 /*  get the length of a file */
          615 /* */
          616 int
          617 filelen(String *dir, char *file)
          618 {
          619         String *path;
          620         Dir *d;
          621         int rv;
          622 
          623         path = extendpath(dir, file);
          624         d = fsdirstat(mailfs, s_to_c(path));
          625         if(d == nil){
          626                 s_free(path);
          627                 return -1;
          628         }
          629         s_free(path);
          630         rv = d->length;
          631         free(d);
          632         return rv;
          633 }
          634 
          635 /* */
          636 /*  walk the path name an element */
          637 /* */
          638 String*
          639 extendpath(String *dir, char *name)
          640 {
          641         String *path;
          642 
          643         if(strcmp(s_to_c(dir), ".") == 0)
          644                 path = s_new();
          645         else {
          646                 path = s_copy(s_to_c(dir));
          647                 s_append(path, "/");
          648         }
          649         s_append(path, name);
          650         return path;
          651 }
          652 
          653 int
          654 cistrncmp(char *a, char *b, int n)
          655 {
          656         while(n-- > 0){
          657                 if(tolower(*a++) != tolower(*b++))
          658                         return -1;
          659         }
          660         return 0;
          661 }
          662 
          663 int
          664 cistrcmp(char *a, char *b)
          665 {
          666         for(;;){
          667                 if(tolower(*a) != tolower(*b++))
          668                         return -1;
          669                 if(*a++ == 0)
          670                         break;
          671         }
          672         return 0;
          673 }
          674 
          675 char*
          676 nosecs(char *t)
          677 {
          678         char *p;
          679 
          680         p = strchr(t, ':');
          681         if(p == nil)
          682                 return t;
          683         p = strchr(p+1, ':');
          684         if(p != nil)
          685                 *p = 0;
          686         return t;
          687 }
          688 
          689 char *months[12] =
          690 {
          691         "jan", "feb", "mar", "apr", "may", "jun",
          692         "jul", "aug", "sep", "oct", "nov", "dec"
          693 };
          694 
          695 int
          696 month(char *m)
          697 {
          698         int i;
          699 
          700         for(i = 0; i < 12; i++)
          701                 if(cistrcmp(m, months[i]) == 0)
          702                         return i+1;
          703         return 1;
          704 }
          705 
          706 enum
          707 {
          708         Yearsecs= 365*24*60*60
          709 };
          710 
          711 void
          712 cracktime(char *d, char *out, int len)
          713 {
          714         char in[64];
          715         char *f[6];
          716         int n;
          717         Tm tm;
          718         long now, then;
          719         char *dtime;
          720 
          721         *out = 0;
          722         if(d == nil)
          723                 return;
          724         strncpy(in, d, sizeof(in));
          725         in[sizeof(in)-1] = 0;
          726         n = getfields(in, f, 6, 1, " \t\r\n");
          727         if(n != 6){
          728                 /* unknown style */
          729                 snprint(out, 16, "%10.10s", d);
          730                 return;
          731         }
          732         now = time(0);
          733         memset(&tm, 0, sizeof tm);
          734         if(strchr(f[0], ',') != nil && strchr(f[4], ':') != nil){
          735                 /* 822 style */
          736                 tm.year = atoi(f[3])-1900;
          737                 tm.mon = month(f[2]);
          738                 tm.mday = atoi(f[1]);
          739                 dtime = nosecs(f[4]);
          740                 then = tm2sec(&tm);
          741         } else if(strchr(f[3], ':') != nil){
          742                 /* unix style */
          743                 tm.year = atoi(f[5])-1900;
          744                 tm.mon = month(f[1]);
          745                 tm.mday = atoi(f[2]);
          746                 dtime = nosecs(f[3]);
          747                 then = tm2sec(&tm);
          748         } else {
          749                 then = now;
          750                 tm = *localtime(now);
          751                 dtime = "";
          752         }
          753 
          754         if(now - then < Yearsecs/2)
          755                 snprint(out, len, "%2d/%2.2d %s", tm.mon, tm.mday, dtime);
          756         else
          757                 snprint(out, len, "%2d/%2.2d  %4d", tm.mon, tm.mday, tm.year+1900);
          758 }
          759 
          760 Ctype*
          761 findctype(Message *m)
          762 {
          763         char *p;
          764         char ftype[128];
          765         int n, pfd[2];
          766         Ctype *a, *cp;
          767         static Ctype bintype         = { "application/octet-stream", "bin", 0, 0 };
          768 
          769         for(cp = ctype; cp; cp = cp->next)
          770                 if(strncmp(cp->type, m->type, strlen(cp->type)) == 0)
          771                         return cp;
          772 
          773         if(pipe(pfd) < 0)
          774                 return &bintype;
          775 
          776         *ftype = 0;
          777         switch(fork()){
          778         case -1:
          779                 break;
          780         case 0:
          781                 close(pfd[1]);
          782                 close(0);
          783                 dup(pfd[0], 0);
          784                 close(1);
          785                 dup(pfd[0], 1);
          786                 execl(unsharp("#9/bin/file"), "file", "-m", s_to_c(extendpath(m->path, "body")), nil);
          787                 threadexits(0);
          788         default:
          789                 close(pfd[0]);
          790                 n = read(pfd[1], ftype, sizeof(ftype));
          791                 if(n > 0)
          792                         ftype[n] = 0;
          793                 close(pfd[1]);
          794                 waitpid();
          795                 break;
          796         }
          797 
          798         if (*ftype=='\0' || (p = strchr(ftype, '/')) == nil)
          799                 return &bintype;
          800         *p++ = 0;
          801 
          802         a = mallocz(sizeof(Ctype), 1);
          803         a->type = strdup(ftype);
          804         a->ext = strdup(p);
          805         a->display = 0;
          806         a->plumbdest = strdup(ftype);
          807         for(cp = ctype; cp->next; cp = cp->next)
          808                 continue;
          809         cp->next = a;
          810         a->next = nil;
          811         return a;
          812 }
          813 
          814 void
          815 mkid(String *s, Message *m)
          816 {
          817         char buf[32];
          818 
          819         if(m->parent != &top){
          820                 mkid(s, m->parent);
          821                 s_append(s, ".");
          822         }
          823         sprint(buf, "%d", m->id);
          824         s_append(s, buf);
          825 }
          826 
          827 void
          828 snprintheader(char *buf, int len, Message *m)
          829 {
          830         char timebuf[32];
          831         String *id;
          832         char *p, *q;;
          833 
          834         /* create id */
          835         id = s_new();
          836         mkid(id, m);
          837 
          838         if(*m->from == 0){
          839                 /* no from */
          840                 snprint(buf, len, "%-3s    %s %6d  %s",
          841                         s_to_c(id),
          842                         m->type,
          843                         m->len,
          844                         m->filename);
          845         } else if(*m->subject){
          846                 q = p = strdup(m->subject);
          847                 while(*p == ' ')
          848                         p++;
          849                 if(strlen(p) > 50)
          850                         p[50] = 0;
          851                 cracktime(m->date, timebuf, sizeof(timebuf));
          852                 snprint(buf, len, "%-3s %c%c%c %6d  %11.11s %-*.*s %s",
          853                         s_to_c(id),
          854                         m->child ? 'H' : ' ',
          855                         m->deleted ? 'd' : ' ',
          856                         m->stored ? 's' : ' ',
          857                         m->len,
          858                         timebuf,
          859                         longestfrom, longestfrom, m->from,
          860                         p);
          861                 free(q);
          862         } else {
          863                 cracktime(m->date, timebuf, sizeof(timebuf));
          864                 snprint(buf, len, "%-3s %c%c%c %6d  %11.11s %s",
          865                         s_to_c(id),
          866                         m->child ? 'H' : ' ',
          867                         m->deleted ? 'd' : ' ',
          868                         m->stored ? 's' : ' ',
          869                         m->len,
          870                         timebuf,
          871                         m->from);
          872         }
          873         s_free(id);
          874 }
          875 
          876 char *spaces = "                                                                    ";
          877 
          878 void
          879 snprintHeader(char *buf, int len, int indent, Message *m)
          880 {
          881         String *id;
          882         char typeid[64];
          883         char *p, *e;
          884 
          885         /* create id */
          886         id = s_new();
          887         mkid(id, m);
          888 
          889         e = buf + len;
          890 
          891         snprint(typeid, sizeof typeid, "%s    %s", s_to_c(id), m->type);
          892         if(indent < 6)
          893                 p = seprint(buf, e, "%-32s %-6d ", typeid, m->len);
          894         else
          895                 p = seprint(buf, e, "%-64s %-6d ", typeid, m->len);
          896         if(m->filename && *m->filename)
          897                 p = seprint(p, e, "(file,%s)", m->filename);
          898         if(m->from && *m->from)
          899                 p = seprint(p, e, "(from,%s)", m->from);
          900         if(m->subject && *m->subject)
          901                 seprint(p, e, "(subj,%s)", m->subject);
          902 
          903         s_free(id);
          904 }
          905 
          906 char sstring[256];
          907 
          908 /*        cmd := range cmd ' ' arg-list ;  */
          909 /*        range := address */
          910 /*                | address ',' address */
          911 /*                | 'g' search ; */
          912 /*        address := msgno */
          913 /*                | search ; */
          914 /*        msgno := number */
          915 /*                | number '/' msgno ; */
          916 /*        search := '/' string '/' */
          917 /*                | '%' string '%' ; */
          918 /* */
          919 Reprog*
          920 parsesearch(char **pp)
          921 {
          922         char *p, *np;
          923         int c, n;
          924 
          925         p = *pp;
          926         c = *p++;
          927         np = strchr(p, c);
          928         if(np != nil){
          929                 *np++ = 0;
          930                 *pp = np;
          931         } else {
          932                 n = strlen(p);
          933                 *pp = p + n;
          934         }
          935         if(*p == 0)
          936                 p = sstring;
          937         else{
          938                 strncpy(sstring, p, sizeof(sstring));
          939                 sstring[sizeof(sstring)-1] = 0;
          940         }
          941         return regcomp(p);
          942 }
          943 
          944 char*
          945 parseaddr(char **pp, Message *first, Message *cur, Message *unspec, Message **mp)
          946 {
          947         int n;
          948         Message *m;
          949         char *p;
          950         Reprog *prog;
          951         int c, sign;
          952         char buf[256];
          953 
          954         *mp = nil;
          955         p = *pp;
          956 
          957         if(*p == '+'){
          958                 sign = 1;
          959                 p++;
          960                 *pp = p;
          961         } else if(*p == '-'){
          962                 sign = -1;
          963                 p++;
          964                 *pp = p;
          965         } else
          966                 sign = 0;
          967 
          968         switch(*p){
          969         default:
          970                 if(sign){
          971                         n = 1;
          972                         goto number;
          973                 }
          974                 *mp = unspec;
          975                 break;
          976         case '0': case '1': case '2': case '3': case '4':
          977         case '5': case '6': case '7': case '8': case '9':
          978                 n = strtoul(p, pp, 10);
          979                 if(n == 0){
          980                         if(sign)
          981                                 *mp = cur;
          982                         else
          983                                 *mp = &top;
          984                         break;
          985                 }
          986         number:
          987                 m = nil;
          988                 switch(sign){
          989                 case 0:
          990                         for(m = first; m != nil; m = m->next)
          991                                 if(m->id == n)
          992                                         break;
          993                         break;
          994                 case -1:
          995                         if(cur != &top)
          996                                 for(m = cur; m != nil && n > 0; n--)
          997                                         m = m->prev;
          998                         break;
          999                 case 1:
         1000                         if(cur == &top){
         1001                                 n--;
         1002                                 cur = first;
         1003                         }
         1004                         for(m = cur; m != nil && n > 0; n--)
         1005                                 m = m->next;
         1006                         break;
         1007                 }
         1008                 if(m == nil)
         1009                         return "address";
         1010                 *mp = m;
         1011                 break;
         1012         case '%':
         1013         case '/':
         1014         case '?':
         1015                 c = *p;
         1016                 prog = parsesearch(pp);
         1017                 if(prog == nil)
         1018                         return "badly formed regular expression";
         1019                 m = nil;
         1020                 switch(c){
         1021                 case '%':
         1022                         for(m = cur == &top ? first : cur->next; m != nil; m = m->next){
         1023                                 if(rawsearch(m, prog))
         1024                                         break;
         1025                         }
         1026                         break;
         1027                 case '/':
         1028                         for(m = cur == &top ? first : cur->next; m != nil; m = m->next){
         1029                                 snprintheader(buf, sizeof(buf), m);
         1030                                 if(regexec(prog, buf, nil, 0))
         1031                                         break;
         1032                         }
         1033                         break;
         1034                 case '?':
         1035                         for(m = cur == &top ? nil : cur->prev; m != nil; m = m->prev){
         1036                                 snprintheader(buf, sizeof(buf), m);
         1037                                 if(regexec(prog, buf, nil, 0))
         1038                                         break;
         1039                         }
         1040                         break;
         1041                 }
         1042                 if(m == nil)
         1043                         return "search";
         1044                 *mp = m;
         1045                 free(prog);
         1046                 break;
         1047         case '$':
         1048                 for(m = first; m != nil && m->next != nil; m = m->next)
         1049                         ;
         1050                 *mp = m;
         1051                 *pp = p+1;
         1052                 break;
         1053         case '.':
         1054                 *mp = cur;
         1055                 *pp = p+1;
         1056                 break;
         1057         case ',':
         1058                 *mp = first;
         1059                 *pp = p;
         1060                 break;
         1061         }
         1062 
         1063         if(*mp != nil && **pp == '.'){
         1064                 (*pp)++;
         1065                 if((*mp)->child == nil)
         1066                         return "no sub parts";
         1067                 return parseaddr(pp, (*mp)->child, (*mp)->child, (*mp)->child, mp);
         1068         }
         1069         if(**pp == '+' || **pp == '-' || **pp == '/' || **pp == '%')
         1070                 return parseaddr(pp, first, *mp, *mp, mp);
         1071 
         1072         return nil;
         1073 }
         1074 
         1075 /* */
         1076 /*  search a message for a regular expression match */
         1077 /* */
         1078 int
         1079 rawsearch(Message *m, Reprog *prog)
         1080 {
         1081         char buf[4096+1];
         1082         int i, rv;
         1083         CFid *fd;
         1084         String *path;
         1085 
         1086         path = extendpath(m->path, "raw");
         1087         fd = fsopen(mailfs, s_to_c(path), OREAD);
         1088         if(fd == nil)
         1089                 return 0;
         1090 
         1091         /* march through raw message 4096 bytes at a time */
         1092         /* with a 128 byte overlap to chain the re search. */
         1093         rv = 0;
         1094         for(;;){
         1095                 i = fsread(fd, buf, sizeof(buf)-1);
         1096                 if(i <= 0)
         1097                         break;
         1098                 buf[i] = 0;
         1099                 if(regexec(prog, buf, nil, 0)){
         1100                         rv = 1;
         1101                         break;
         1102                 }
         1103                 if(i < sizeof(buf)-1)
         1104                         break;
         1105                 if(fsseek(fd, -128LL, 1) < 0)
         1106                         break;
         1107         }
         1108 
         1109         fsclose(fd);
         1110         s_free(path);
         1111         return rv;
         1112 }
         1113 
         1114 
         1115 char*
         1116 parsecmd(char *p, Cmd *cmd, Message *first, Message *cur)
         1117 {
         1118         Reprog *prog;
         1119         Message *m, *s, *e, **l, *last;
         1120         char buf[256];
         1121         char *err;
         1122         int i, c;
         1123         char *q;
         1124         static char errbuf[Errlen];
         1125 
         1126         cmd->delete = 0;
         1127         l = &cmd->msgs;
         1128         *l = nil;
         1129 
         1130         /* eat white space */
         1131         while(*p == ' ')
         1132                 p++;
         1133 
         1134         /* null command is a special case (advance and print) */
         1135         if(*p == 0){
         1136                 if(cur == &top){
         1137                         /* special case */
         1138                         m = first;
         1139                 } else {
         1140                         /* walk to the next message even if we have to go up */
         1141                         m = cur->next;
         1142                         while(m == nil && cur->parent != nil){
         1143                                 cur = cur->parent;
         1144                                 m = cur->next;
         1145                         }
         1146                 }
         1147                 if(m == nil)
         1148                         return "address";
         1149                 *l = m;
         1150                 m->cmd = nil;
         1151                 cmd->an = 0;
         1152                 cmd->f = pcmd;
         1153                 return nil;
         1154         }
         1155 
         1156         /* global search ? */
         1157         if(*p == 'g'){
         1158                 p++;
         1159 
         1160                 /* no search string means all messages */
         1161                 if(*p != '/' && *p != '%'){
         1162                         for(m = first; m != nil; m = m->next){
         1163                                 *l = m;
         1164                                 l = &m->cmd;
         1165                                 *l = nil;
         1166                         }
         1167                 } else {
         1168                         /* mark all messages matching this search string */
         1169                         c = *p;
         1170                         prog = parsesearch(&p);
         1171                         if(prog == nil)
         1172                                 return "badly formed regular expression";
         1173                         if(c == '%'){
         1174                                 for(m = first; m != nil; m = m->next){
         1175                                         if(rawsearch(m, prog)){
         1176                                                 *l = m;
         1177                                                 l = &m->cmd;
         1178                                                 *l = nil;
         1179                                         }
         1180                                 }
         1181                         } else {
         1182                                 for(m = first; m != nil; m = m->next){
         1183                                         snprintheader(buf, sizeof(buf), m);
         1184                                         if(regexec(prog, buf, nil, 0)){
         1185                                                 *l = m;
         1186                                                 l = &m->cmd;
         1187                                                 *l = nil;
         1188                                         }
         1189                                 }
         1190                         }
         1191                         free(prog);
         1192                 }
         1193         } else {
         1194 
         1195                 /* parse an address */
         1196                 s = e = nil;
         1197                 err = parseaddr(&p, first, cur, cur, &s);
         1198                 if(err != nil)
         1199                         return err;
         1200                 if(*p == ','){
         1201                         /* this is an address range */
         1202                         if(s == &top)
         1203                                 s = first;
         1204                         p++;
         1205                         for(last = s; last != nil && last->next != nil; last = last->next)
         1206                                 ;
         1207                         err = parseaddr(&p, first, cur, last, &e);
         1208                         if(err != nil)
         1209                                 return err;
         1210 
         1211                         /* select all messages in the range */
         1212                         for(; s != nil; s = s->next){
         1213                                 *l = s;
         1214                                 l = &s->cmd;
         1215                                 *l = nil;
         1216                                 if(s == e)
         1217                                         break;
         1218                         }
         1219                         if(s == nil)
         1220                                 return "null address range";
         1221                 } else {
         1222                         /* single address */
         1223                         if(s != &top){
         1224                                 *l = s;
         1225                                 s->cmd = nil;
         1226                         }
         1227                 }
         1228         }
         1229 
         1230         /* insert a space after '!'s and '|'s */
         1231         for(q = p; *q; q++)
         1232                 if(*q != '!' && *q != '|')
         1233                         break;
         1234         if(q != p && *q != ' '){
         1235                 memmove(q+1, q, strlen(q)+1);
         1236                 *q = ' ';
         1237         }
         1238 
         1239         cmd->an = getfields(p, cmd->av, nelem(cmd->av) - 1, 1, " \t\r\n");
         1240         if(cmd->an == 0 || *cmd->av[0] == 0)
         1241                 cmd->f = pcmd;
         1242         else {
         1243                 /* hack to allow all messages to start with 'd' */
         1244                 if(*(cmd->av[0]) == 'd' && *(cmd->av[0]+1) != 0){
         1245                         cmd->delete = 1;
         1246                         cmd->av[0]++;
         1247                 }
         1248 
         1249                 /* search command table */
         1250                 for(i = 0; cmdtab[i].cmd != nil; i++)
         1251                         if(strcmp(cmd->av[0], cmdtab[i].cmd) == 0)
         1252                                 break;
         1253                 if(cmdtab[i].cmd == nil)
         1254                         return "illegal command";
         1255                 if(cmdtab[i].args == 0 && cmd->an > 1){
         1256                         snprint(errbuf, sizeof(errbuf), "%s doesn't take an argument", cmdtab[i].cmd);
         1257                         return errbuf;
         1258                 }
         1259                 cmd->f = cmdtab[i].f;
         1260         }
         1261         return nil;
         1262 }
         1263 
         1264 /* inefficient read from standard input */
         1265 char*
         1266 readline(char *prompt, char *line, int len)
         1267 {
         1268         char *p, *e;
         1269         int n;
         1270 
         1271 retry:
         1272         interrupted = 0;
         1273         Bprint(&out, "%s", prompt);
         1274         Bflush(&out);
         1275         e = line + len;
         1276         for(p = line; p < e; p++){
         1277                 n = read(0, p, 1);
         1278                 if(n < 0){
         1279                         if(interrupted)
         1280                                 goto retry;
         1281                         return nil;
         1282                 }
         1283                 if(n == 0)
         1284                         return nil;
         1285                 if(*p == '\n')
         1286                         break;
         1287         }
         1288         *p = 0;
         1289         return line;
         1290 }
         1291 
         1292 void
         1293 messagecount(Message *m)
         1294 {
         1295         int i;
         1296 
         1297         i = 0;
         1298         for(; m != nil; m = m->next)
         1299                 i++;
         1300         Bprint(&out, "%d message%s\n", i, plural(i));
         1301 }
         1302 
         1303 Message*
         1304 aichcmd(Message *m, int indent)
         1305 {
         1306         char        hdr[256];
         1307 
         1308         if(m == &top)
         1309                 return nil;
         1310 
         1311         snprintHeader(hdr, sizeof(hdr), indent, m);
         1312         Bprint(&out, "%s\n", hdr);
         1313         for(m = m->child; m != nil; m = m->next)
         1314                 aichcmd(m, indent+1);
         1315         return nil;
         1316 }
         1317 
         1318 Message*
         1319 Hcmd(Cmd *x, Message *m)
         1320 {
         1321         USED(x);
         1322 
         1323         if(m == &top)
         1324                 return nil;
         1325         aichcmd(m, 0);
         1326         return nil;
         1327 }
         1328 
         1329 Message*
         1330 hcmd(Cmd *x, Message *m)
         1331 {
         1332         char        hdr[256];
         1333 
         1334         USED(x);
         1335         if(m == &top)
         1336                 return nil;
         1337 
         1338         snprintheader(hdr, sizeof(hdr), m);
         1339         Bprint(&out, "%s\n", hdr);
         1340         return nil;
         1341 }
         1342 
         1343 Message*
         1344 bcmd(Cmd *x, Message *m)
         1345 {
         1346         int i;
         1347         Message *om = m;
         1348 
         1349         USED(x);
         1350         if(m == &top)
         1351                 m = top.child;
         1352         for(i = 0; i < 10 && m != nil; i++){
         1353                 hcmd(nil, m);
         1354                 om = m;
         1355                 m = m->next;
         1356         }
         1357 
         1358         return om;
         1359 }
         1360 
         1361 Message*
         1362 ncmd(Cmd *x, Message *m)
         1363 {
         1364         USED(x);
         1365         if(m == &top)
         1366                 return m->child;
         1367         return m->next;
         1368 }
         1369 
         1370 int
         1371 printpart(String *s, char *part)
         1372 {
         1373         char buf[4096];
         1374         int n, tot;
         1375         CFid *fd;
         1376         String *path;
         1377 
         1378         path = extendpath(s, part);
         1379         fd = fsopen(mailfs, s_to_c(path), OREAD);
         1380         s_free(path);
         1381         if(fd == nil){
         1382                 fprint(2, "!message dissappeared\n");
         1383                 return 0;
         1384         }
         1385         tot = 0;
         1386         while((n = fsread(fd, buf, sizeof(buf))) > 0){
         1387                 if(interrupted)
         1388                         break;
         1389                 if(Bwrite(&out, buf, n) <= 0)
         1390                         break;
         1391                 tot += n;
         1392         }
         1393         fsclose(fd);
         1394         return tot;
         1395 }
         1396 
         1397 int
         1398 printhtml(Message *m)
         1399 {
         1400         Cmd c;
         1401 
         1402         c.an = 3;
         1403         c.av[1] = "htmlfmt";
         1404         c.av[2] = "-l 40 -cutf-8";
         1405         Bprint(&out, "!%s\n", c.av[1]);
         1406         Bflush(&out);
         1407         pipecmd(&c, m);
         1408         return 0;
         1409 }
         1410 
         1411 Message*
         1412 Pcmd(Cmd *x, Message *m)
         1413 {
         1414         USED(x);
         1415         if(m == &top)
         1416                 return &top;
         1417         if(m->parent == &top)
         1418                 printpart(m->path, "unixheader");
         1419         printpart(m->path, "raw");
         1420         return m;
         1421 }
         1422 
         1423 void
         1424 compress(char *p)
         1425 {
         1426         char *np;
         1427         int last;
         1428 
         1429         last = ' ';
         1430         for(np = p; *p; p++){
         1431                 if(*p != ' ' || last != ' '){
         1432                         last = *p;
         1433                         *np++ = last;
         1434                 }
         1435         }
         1436         *np = 0;
         1437 }
         1438 
         1439 Message*
         1440 pcmd(Cmd *x, Message *m)
         1441 {
         1442         Message *nm;
         1443         Ctype *cp;
         1444         String *s;
         1445         char buf[128];
         1446 
         1447         USED(x);
         1448         if(m == &top)
         1449                 return &top;
         1450         if(m->parent == &top)
         1451                 printpart(m->path, "unixheader");
         1452         if(printpart(m->path, "header") > 0)
         1453                 Bprint(&out, "\n");
         1454         cp = findctype(m);
         1455         if(cp->display){
         1456                 if(strcmp(m->type, "text/html") == 0)
         1457                         printhtml(m);
         1458                 else
         1459                         printpart(m->path, "body");
         1460         } else if(strcmp(m->type, "multipart/alternative") == 0){
         1461                 for(nm = m->child; nm != nil; nm = nm->next){
         1462                         cp = findctype(nm);
         1463                         if(cp->ext != nil && strncmp(cp->ext, "txt", 3) == 0)
         1464                                 break;
         1465                 }
         1466                 if(nm == nil)
         1467                         for(nm = m->child; nm != nil; nm = nm->next){
         1468                                 cp = findctype(nm);
         1469                                 if(cp->display)
         1470                                         break;
         1471                         }
         1472                 if(nm != nil)
         1473                         pcmd(nil, nm);
         1474                 else
         1475                         hcmd(nil, m);
         1476         } else if(strncmp(m->type, "multipart/", 10) == 0){
         1477                 nm = m->child;
         1478                 if(nm != nil){
         1479                         /* always print first part */
         1480                         pcmd(nil, nm);
         1481 
         1482                         for(nm = nm->next; nm != nil; nm = nm->next){
         1483                                 s = rooted(s_clone(nm->path));
         1484                                 cp = findctype(nm);
         1485                                 snprintHeader(buf, sizeof buf, -1, nm);
         1486                                 compress(buf);
         1487                                 if(strcmp(nm->disposition, "inline") == 0){
         1488                                         if(cp->ext != nil)
         1489                                                 Bprint(&out, "\n--- %s %s/body.%s\n\n",
         1490                                                         buf, s_to_c(s), cp->ext);
         1491                                         else
         1492                                                 Bprint(&out, "\n--- %s %s/body\n\n",
         1493                                                         buf, s_to_c(s));
         1494                                         pcmd(nil, nm);
         1495                                 } else {
         1496                                         if(cp->ext != nil)
         1497                                                 Bprint(&out, "\n!--- %s %s/body.%s\n",
         1498                                                         buf, s_to_c(s), cp->ext);
         1499                                         else
         1500                                                 Bprint(&out, "\n!--- %s %s/body\n",
         1501                                                         buf, s_to_c(s));
         1502                                 }
         1503                                 s_free(s);
         1504                         }
         1505                 } else {
         1506                         hcmd(nil, m);
         1507                 }
         1508         } else if(strcmp(m->type, "message/rfc822") == 0){
         1509                 pcmd(nil, m->child);
         1510         } else if(plumb(m, cp) >= 0)
         1511                 Bprint(&out, "\n!--- using plumber to display message of type %s\n", m->type);
         1512         else
         1513                 Bprint(&out, "\n!--- cannot display messages of type %s\n", m->type);
         1514 
         1515         return m;
         1516 }
         1517 
         1518 void
         1519 printpartindented(String *s, char *part, char *indent)
         1520 {
         1521         int fd;
         1522         char *p;
         1523         String *path;
         1524         Biobuf *b;
         1525 
         1526         path = extendpath(s, part);
         1527         fd = fsopenfd(mailfs, s_to_c(path), OREAD);
         1528         s_free(path);
         1529         if(fd < 0){
         1530                 fprint(2, "!message disappeared\n");
         1531                 return;
         1532         }
         1533         b = Bfdopen(fd, OREAD);
         1534         if(b == 0){
         1535                 fprint(2, "out of memory\n");
         1536                 close(fd);
         1537                 return;
         1538         }
         1539         while((p = Brdline(b, '\n')) != nil){
         1540                 if(interrupted)
         1541                         break;
         1542                 p[Blinelen(b)-1] = 0;
         1543                 if(Bprint(&out, "%s%s\n", indent, p) < 0)
         1544                         break;
         1545         }
         1546         Bprint(&out, "\n");
         1547         Bterm(b);
         1548 }
         1549 
         1550 Message*
         1551 quotecmd(Cmd *x, Message *m)
         1552 {
         1553         Message *nm;
         1554         Ctype *cp;
         1555 
         1556         USED(x);
         1557         if(m == &top)
         1558                 return &top;
         1559         Bprint(&out, "\n");
         1560         if(m->from != nil && *m->from)
         1561                 Bprint(&out, "On %s, %s wrote:\n", m->date, m->from);
         1562         cp = findctype(m);
         1563         if(cp->display){
         1564                 printpartindented(m->path, "body", "> ");
         1565         } else if(strcmp(m->type, "multipart/alternative") == 0){
         1566                 for(nm = m->child; nm != nil; nm = nm->next){
         1567                         cp = findctype(nm);
         1568                         if(cp->ext != nil && strncmp(cp->ext, "txt", 3) == 0)
         1569                                 break;
         1570                 }
         1571                 if(nm == nil)
         1572                         for(nm = m->child; nm != nil; nm = nm->next){
         1573                                 cp = findctype(nm);
         1574                                 if(cp->display)
         1575                                         break;
         1576                         }
         1577                 if(nm != nil)
         1578                         quotecmd(nil, nm);
         1579         } else if(strncmp(m->type, "multipart/", 10) == 0){
         1580                 nm = m->child;
         1581                 if(nm != nil){
         1582                         cp = findctype(nm);
         1583                         if(cp->display || strncmp(m->type, "multipart/", 10) == 0)
         1584                                 quotecmd(nil, nm);
         1585                 }
         1586         }
         1587         return m;
         1588 }
         1589 
         1590 /* really delete messages */
         1591 Message*
         1592 flushdeleted(Message *cur)
         1593 {
         1594         Message *m, **l;
         1595         char buf[1024], *p, *e, *msg;
         1596         int deld, n;
         1597         CFid *fd;
         1598         int i;
         1599 
         1600         doflush = 0;
         1601         deld = 0;
         1602 
         1603         snprint(buf, sizeof buf, "%s/ctl", mbname);
         1604         fd = fsopen(mailfs, buf, OWRITE);
         1605         if(fd == nil){
         1606                 fprint(2, "!can't delete mail, opening %s: %r\n", buf);
         1607                 exitfs(0);
         1608         }
         1609         e = &buf[sizeof(buf)];
         1610         p = seprint(buf, e, "delete");
         1611         n = 0;
         1612         for(l = &top.child; *l != nil;){
         1613                 m = *l;
         1614                 if(!m->deleted){
         1615                         l = &(*l)->next;
         1616                         continue;
         1617                 }
         1618 
         1619                 /* don't return a pointer to a deleted message */
         1620                 if(m == cur)
         1621                         cur = m->next;
         1622 
         1623                 deld++;
         1624                 msg = strrchr(s_to_c(m->path), '/');
         1625                 if(msg == nil)
         1626                         msg = s_to_c(m->path);
         1627                 else
         1628                         msg++;
         1629                 if(e-p < 10){
         1630                         fswrite(fd, buf, p-buf);
         1631                         n = 0;
         1632                         p = seprint(buf, e, "delete");
         1633                 }
         1634                 p = seprint(p, e, " %s", msg);
         1635                 n++;
         1636 
         1637                 /* unchain and free */
         1638                 *l = m->next;
         1639                 if(m->next)
         1640                         m->next->prev = m->prev;
         1641                 freemessage(m);
         1642         }
         1643         if(n)
         1644                 fswrite(fd, buf, p-buf);
         1645 
         1646         fsclose(fd);
         1647 
         1648         if(deld)
         1649                 Bprint(&out, "!%d message%s deleted\n", deld, plural(deld));
         1650 
         1651         /* renumber */
         1652         i = 1;
         1653         for(m = top.child; m != nil; m = m->next)
         1654                 m->id = natural ? m->fileno : i++;
         1655 
         1656         /* if we're out of messages, go back to first */
         1657         /* if no first, return the fake first */
         1658         if(cur == nil){
         1659                 if(top.child)
         1660                         return top.child;
         1661                 else
         1662                         return &top;
         1663         }
         1664         return cur;
         1665 }
         1666 
         1667 Message*
         1668 qcmd(Cmd *x, Message *m)
         1669 {
         1670         USED(x);
         1671         USED(m);
         1672 
         1673         flushdeleted(nil);
         1674 
         1675         if(didopen)
         1676                 closemb();
         1677         Bflush(&out);
         1678 
         1679         exitfs(0);
         1680         return nil;        /* not reached */
         1681 }
         1682 
         1683 Message*
         1684 ycmd(Cmd *x, Message *m)
         1685 {
         1686         USED(x);
         1687 
         1688         doflush = 1;
         1689 
         1690         return icmd(nil, m);
         1691 }
         1692 
         1693 Message*
         1694 xcmd(Cmd *x, Message *m)
         1695 {
         1696         USED(x);
         1697         USED(m);
         1698 
         1699         exitfs(0);
         1700         return nil;        /* not reached */
         1701 }
         1702 
         1703 Message*
         1704 eqcmd(Cmd *x, Message *m)
         1705 {
         1706         USED(x);
         1707 
         1708         if(m == &top)
         1709                 Bprint(&out, "0\n");
         1710         else
         1711                 Bprint(&out, "%d\n", m->id);
         1712         return nil;
         1713 }
         1714 
         1715 Message*
         1716 dcmd(Cmd *x, Message *m)
         1717 {
         1718         USED(x);
         1719 
         1720         if(m == &top){
         1721                 Bprint(&out, "!address\n");
         1722                 return nil;
         1723         }
         1724         while(m->parent != &top)
         1725                 m = m->parent;
         1726         m->deleted = 1;
         1727         return m;
         1728 }
         1729 
         1730 Message*
         1731 ucmd(Cmd *x, Message *m)
         1732 {
         1733         USED(x);
         1734 
         1735         if(m == &top)
         1736                 return nil;
         1737         while(m->parent != &top)
         1738                 m = m->parent;
         1739         if(m->deleted < 0)
         1740                 Bprint(&out, "!can't undelete, already flushed\n");
         1741         m->deleted = 0;
         1742         return m;
         1743 }
         1744 
         1745 
         1746 Message*
         1747 icmd(Cmd *x, Message *m)
         1748 {
         1749         int n;
         1750         char buf[1024];
         1751         CFid *fd;
         1752 
         1753         USED(x);
         1754         snprint(buf, sizeof buf, "%s/ctl", mbname);
         1755         fd = fsopen(mailfs, buf, OWRITE);
         1756         if(fd){
         1757                 fswrite(fd, "refresh", 7);
         1758                 fsclose(fd);
         1759         }
         1760         n = dir2message(&top, reverse);
         1761         if(n > 0)
         1762                 Bprint(&out, "%d new message%s\n", n, plural(n));
         1763         return m;
         1764 }
         1765 
         1766 Message*
         1767 helpcmd(Cmd *x, Message *m)
         1768 {
         1769         int i;
         1770 
         1771         USED(x);
         1772         Bprint(&out, "Commands are of the form [<range>] <command> [args]\n");
         1773         Bprint(&out, "<range> := <addr> | <addr>','<addr>| 'g'<search>\n");
         1774         Bprint(&out, "<addr> := '.' | '$' | '^' | <number> | <search> | <addr>'+'<addr> | <addr>'-'<addr>\n");
         1775         Bprint(&out, "<search> := '/'<regexp>'/' | '?'<regexp>'?' | '%%'<regexp>'%%'\n");
         1776         Bprint(&out, "<command> :=\n");
         1777         for(i = 0; cmdtab[i].cmd != nil; i++)
         1778                 Bprint(&out, "%s\n", cmdtab[i].help);
         1779         return m;
         1780 }
         1781 
         1782 int
         1783 tomailer(char **av)
         1784 {
         1785         static char *marshal;
         1786         Waitmsg *w;
         1787         int pid, i;
         1788 
         1789         if(marshal == nil)
         1790                 marshal = unsharp("#9/bin/upas/marshal");
         1791 
         1792         /* start the mailer and get out of the way */
         1793         switch(pid = fork()){
         1794         case -1:
         1795                 fprint(2, "can't fork: %r\n");
         1796                 return -1;
         1797         case 0:
         1798                 Bprint(&out, "!%s", marshal);
         1799                 for(i = 1; av[i]; i++){
         1800                         if(strchr(av[i], ' ') != nil)
         1801                                 Bprint(&out, " '%s'", av[i]);
         1802                         else
         1803                                 Bprint(&out, " %s", av[i]);
         1804                 }
         1805                 Bprint(&out, "\n");
         1806                 Bflush(&out);
         1807                 av[0] = "marshal";
         1808                 chdir(wd);
         1809                 exec(marshal, av);
         1810                 fprint(2, "couldn't exec %s\n", marshal);
         1811                 threadexits(0);
         1812         default:
         1813                 w = wait();
         1814                 if(w == nil){
         1815                         if(interrupted)
         1816                                 postnote(PNPROC, pid, "die");
         1817                         waitpid();
         1818                         return -1;
         1819                 }
         1820                 if(w->msg[0]){
         1821                         fprint(2, "mailer failed: %s\n", w->msg);
         1822                         free(w);
         1823                         return -1;
         1824                 }
         1825                 free(w);
         1826                 Bprint(&out, "!\n");
         1827                 break;
         1828         }
         1829         return 0;
         1830 }
         1831 
         1832 /* */
         1833 /* like tokenize but obey "" quoting */
         1834 /* */
         1835 int
         1836 tokenize822(char *str, char **args, int max)
         1837 {
         1838         int na;
         1839         int intok = 0, inquote = 0;
         1840 
         1841         if(max <= 0)
         1842                 return 0;
         1843         for(na=0; ;str++)
         1844                 switch(*str) {
         1845                 case ' ':
         1846                 case '\t':
         1847                         if(inquote)
         1848                                 goto Default;
         1849                         /* fall through */
         1850                 case '\n':
         1851                         *str = 0;
         1852                         if(!intok)
         1853                                 continue;
         1854                         intok = 0;
         1855                         if(na < max)
         1856                                 continue;
         1857                         /* fall through */
         1858                 case 0:
         1859                         return na;
         1860                 case '"':
         1861                         inquote ^= 1;
         1862                         /* fall through */
         1863                 Default:
         1864                 default:
         1865                         if(intok)
         1866                                 continue;
         1867                         args[na++] = str;
         1868                         intok = 1;
         1869                 }
         1870         return 0;        /* can't get here; silence compiler */
         1871 }
         1872 
         1873 Message*
         1874 rcmd(Cmd *c, Message *m)
         1875 {
         1876         char *av[128];
         1877         int i, ai = 1;
         1878         Message *nm;
         1879         char *addr;
         1880         String *path = nil;
         1881         String *rpath;
         1882         String *subject = nil;
         1883         String *from;
         1884 
         1885         if(m == &top){
         1886                 Bprint(&out, "!address\n");
         1887                 return nil;
         1888         }
         1889 
         1890         addr = nil;
         1891         for(nm = m; nm != &top; nm = nm->parent){
         1892                  if(*nm->replyto != 0){
         1893                         addr = nm->replyto;
         1894                         break;
         1895                 }
         1896         }
         1897         if(addr == nil){
         1898                 Bprint(&out, "!no reply address\n");
         1899                 return nil;
         1900         }
         1901 
         1902         if(nm == &top){
         1903                 print("!noone to reply to\n");
         1904                 return nil;
         1905         }
         1906 
         1907         for(nm = m; nm != &top; nm = nm->parent){
         1908                 if(*nm->subject){
         1909                         av[ai++] = "-s";
         1910                         subject = addrecolon(nm->subject);
         1911                         av[ai++] = s_to_c(subject);;
         1912                         break;
         1913                 }
         1914         }
         1915 
         1916         av[ai++] = "-R";
         1917         rpath = rooted(s_clone(m->path));
         1918         av[ai++] = s_to_c(rpath);
         1919 
         1920         if(strchr(c->av[0], 'f') != nil){
         1921                 fcmd(c, m);
         1922                 av[ai++] = "-F";
         1923         }
         1924 
         1925         if(strchr(c->av[0], 'R') != nil){
         1926                 av[ai++] = "-t";
         1927                 av[ai++] = "message/rfc822";
         1928                 av[ai++] = "-A";
         1929                 path = rooted(extendpath(m->path, "raw"));
         1930                 av[ai++] = s_to_c(path);
         1931         }
         1932 
         1933         for(i = 1; i < c->an && ai < nelem(av)-1; i++)
         1934                 av[ai++] = c->av[i];
         1935         from = s_copy(addr);
         1936         ai += tokenize822(s_to_c(from), &av[ai], nelem(av) - ai);
         1937         av[ai] = 0;
         1938         if(tomailer(av) < 0)
         1939                 m = nil;
         1940         s_free(path);
         1941         s_free(rpath);
         1942         s_free(subject);
         1943         s_free(from);
         1944         return m;
         1945 }
         1946 
         1947 Message*
         1948 mcmd(Cmd *c, Message *m)
         1949 {
         1950         char **av;
         1951         int i, ai;
         1952         String *path;
         1953 
         1954         if(m == &top){
         1955                 Bprint(&out, "!address\n");
         1956                 return nil;
         1957         }
         1958 
         1959         if(c->an < 2){
         1960                 fprint(2, "!usage: M list-of addresses\n");
         1961                 return nil;
         1962         }
         1963 
         1964         ai = 1;
         1965         av = malloc(sizeof(char*)*(c->an + 8));
         1966 
         1967         av[ai++] = "-t";
         1968         if(m->parent == &top)
         1969                 av[ai++] = "message/rfc822";
         1970         else
         1971                 av[ai++] = "mime";
         1972 
         1973         av[ai++] = "-A";
         1974         path = rooted(extendpath(m->path, "raw"));
         1975         av[ai++] = s_to_c(path);
         1976 
         1977         if(strchr(c->av[0], 'M') == nil)
         1978                 av[ai++] = "-n";
         1979 
         1980         for(i = 1; i < c->an; i++)
         1981                 av[ai++] = c->av[i];
         1982         av[ai] = 0;
         1983 
         1984         if(tomailer(av) < 0)
         1985                 m = nil;
         1986         if(path != nil)
         1987                 s_free(path);
         1988         free(av);
         1989         return m;
         1990 }
         1991 
         1992 Message*
         1993 acmd(Cmd *c, Message *m)
         1994 {
         1995         char *av[128];
         1996         int i, ai;
         1997         String *from, *to, *cc, *path = nil, *subject = nil;
         1998 
         1999         if(m == &top){
         2000                 Bprint(&out, "!address\n");
         2001                 return nil;
         2002         }
         2003 
         2004         ai = 1;
         2005         if(*m->subject){
         2006                 av[ai++] = "-s";
         2007                 subject = addrecolon(m->subject);
         2008                 av[ai++] = s_to_c(subject);
         2009         }
         2010 
         2011         if(strchr(c->av[0], 'A') != nil){
         2012                 av[ai++] = "-t";
         2013                 av[ai++] = "message/rfc822";
         2014                 av[ai++] = "-A";
         2015                 path = rooted(extendpath(m->path, "raw"));
         2016                 av[ai++] = s_to_c(path);
         2017         }
         2018 
         2019         for(i = 1; i < c->an && ai < nelem(av)-1; i++)
         2020                 av[ai++] = c->av[i];
         2021         from = s_copy(m->from);
         2022         ai += tokenize822(s_to_c(from), &av[ai], nelem(av) - ai);
         2023         to = s_copy(m->to);
         2024         ai += tokenize822(s_to_c(to), &av[ai], nelem(av) - ai);
         2025         cc = s_copy(m->cc);
         2026         ai += tokenize822(s_to_c(cc), &av[ai], nelem(av) - ai);
         2027         av[ai] = 0;
         2028         if(tomailer(av) < 0)
         2029                 return nil;
         2030         s_free(from);
         2031         s_free(to);
         2032         s_free(cc);
         2033         s_free(subject);
         2034         s_free(path);
         2035         return m;
         2036 }
         2037 
         2038 String *
         2039 relpath(char *path, String *to)
         2040 {
         2041         if (*path=='/' || strncmp(path, "./", 2) == 0
         2042                               || strncmp(path, "../", 3) == 0) {
         2043                 to = s_append(to, path);
         2044         } else if(mbpath) {
         2045                 to = s_append(to, s_to_c(mbpath));
         2046                 to->ptr = strrchr(to->base, '/')+1;
         2047                 s_append(to, path);
         2048         }
         2049         return to;
         2050 }
         2051 
         2052 int
         2053 appendtofile(Message *m, char *part, char *base, int mbox)
         2054 {
         2055         String *file, *h;
         2056         int in, out, rv;
         2057 
         2058         file = extendpath(m->path, part);
         2059         in = open(s_to_c(file), OREAD);
         2060         if(in < 0){
         2061                 fprint(2, "!message disappeared\n");
         2062                 return -1;
         2063         }
         2064 
         2065         s_reset(file);
         2066 
         2067         relpath(base, file);
         2068         if(sysisdir(s_to_c(file))){
         2069                 s_append(file, "/");
         2070                 if(m->filename && strchr(m->filename, '/') == nil)
         2071                         s_append(file, m->filename);
         2072                 else {
         2073                         s_append(file, "att.XXXXXXXXXXX");
         2074                         mktemp(s_to_c(file));
         2075                 }
         2076         }
         2077         if(mbox)
         2078                 out = open(s_to_c(file), OWRITE);
         2079         else
         2080                 out = open(s_to_c(file), OWRITE|OTRUNC);
         2081         if(out < 0){
         2082                 out = create(s_to_c(file), OWRITE, 0666);
         2083                 if(out < 0){
         2084                         fprint(2, "!can't open %s: %r\n", s_to_c(file));
         2085                         close(in);
         2086                         s_free(file);
         2087                         return -1;
         2088                 }
         2089         }
         2090         if(mbox)
         2091                 seek(out, 0, 2);
         2092 
         2093         /* put on a 'From ' line */
         2094         if(mbox){
         2095                 while(m->parent != &top)
         2096                         m = m->parent;
         2097                 h = file2string(m->path, "unixheader");
         2098                 fprint(out, "%s", s_to_c(h));
         2099                 s_free(h);
         2100         }
         2101 
         2102         /* copy the message escaping what we have to ad adding newlines if we have to */
         2103         if(mbox)
         2104                 rv = appendfiletombox(in, out);
         2105         else
         2106                 rv = appendfiletofile(in, out);
         2107 
         2108         close(in);
         2109         close(out);
         2110 
         2111         if(rv >= 0)
         2112                 print("!saved in %s\n", s_to_c(file));
         2113         s_free(file);
         2114         return rv;
         2115 }
         2116 
         2117 Message*
         2118 scmd(Cmd *c, Message *m)
         2119 {
         2120         char buf[256];
         2121         CFid *fd;
         2122         char *file, *msg;
         2123 
         2124         if(m == &top){
         2125                 Bprint(&out, "!address\n");
         2126                 return nil;
         2127         }
         2128 
         2129         switch(c->an){
         2130         case 1:
         2131                 file = "stored";
         2132                 break;
         2133         case 2:
         2134                 file = c->av[1];
         2135                 break;
         2136         default:
         2137                 fprint(2, "!usage: s filename\n");
         2138                 return nil;
         2139         }
         2140 
         2141         if(file[0] == '/' || (file[0]=='.' && file[1]=='/')){
         2142                 if(appendtofile(m, "raw", file, 1) < 0)
         2143                         return nil;
         2144         }else{
         2145                 snprint(buf, sizeof buf, "%s/ctl", mbname);
         2146                 if((fd = fsopen(mailfs, buf, OWRITE)) == nil)
         2147                         return nil;
         2148                 msg = strrchr(s_to_c(m->path), '/');
         2149                 if(msg == nil)
         2150                         msg = s_to_c(m->path);
         2151                 else
         2152                         msg++;
         2153                 if(fsprint(fd, "save %s %s", file, msg) < 0){
         2154                         fsclose(fd);
         2155                         return nil;
         2156                 }
         2157                 fsclose(fd);
         2158         }
         2159         m->stored = 1;
         2160         return m;
         2161 }
         2162 
         2163 Message*
         2164 wcmd(Cmd *c, Message *m)
         2165 {
         2166         char *file;
         2167 
         2168         if(m == &top){
         2169                 Bprint(&out, "!address\n");
         2170                 return nil;
         2171         }
         2172 
         2173         switch(c->an){
         2174         case 2:
         2175                 file = c->av[1];
         2176                 break;
         2177         case 1:
         2178                 if(*m->filename == 0){
         2179                         fprint(2, "!usage: w filename\n");
         2180                         return nil;
         2181                 }
         2182                 file = strrchr(m->filename, '/');
         2183                 if(file != nil)
         2184                         file++;
         2185                 else
         2186                         file = m->filename;
         2187                 break;
         2188         default:
         2189                 fprint(2, "!usage: w filename\n");
         2190                 return nil;
         2191         }
         2192 
         2193         if(appendtofile(m, "body", file, 0) < 0)
         2194                 return nil;
         2195         m->stored = 1;
         2196         return m;
         2197 }
         2198 
         2199 char *specialfile[] =
         2200 {
         2201         "pipeto",
         2202         "pipefrom",
         2203         "L.mbox",
         2204         "forward",
         2205         "names"
         2206 };
         2207 
         2208 /* return 1 if this is a special file */
         2209 static int
         2210 special(String *s)
         2211 {
         2212         char *p;
         2213         int i;
         2214 
         2215         p = strrchr(s_to_c(s), '/');
         2216         if(p == nil)
         2217                 p = s_to_c(s);
         2218         else
         2219                 p++;
         2220         for(i = 0; i < nelem(specialfile); i++)
         2221                 if(strcmp(p, specialfile[i]) == 0)
         2222                         return 1;
         2223         return 0;
         2224 }
         2225 
         2226 /* open the folder using the recipients account name */
         2227 static String*
         2228 foldername(char *rcvr)
         2229 {
         2230         char *p;
         2231         int c;
         2232         String *file;
         2233         Dir *d;
         2234         int scarey;
         2235 
         2236         file = s_new();
         2237         mboxpath("f", user, file, 0);
         2238         d = dirstat(s_to_c(file));
         2239 
         2240         /* if $mail/f exists, store there, otherwise in $mail */
         2241         s_restart(file);
         2242         if(d && d->qid.type == QTDIR){
         2243                 scarey = 0;
         2244                 s_append(file, "f/");
         2245         } else {
         2246                 scarey = 1;
         2247         }
         2248         free(d);
         2249 
         2250         p = strrchr(rcvr, '!');
         2251         if(p != nil)
         2252                 rcvr = p+1;
         2253 
         2254         while(*rcvr && *rcvr != '@'){
         2255                 c = *rcvr++;
         2256                 if(c == '/')
         2257                         c = '_';
         2258                 s_putc(file, c);
         2259         }
         2260         s_terminate(file);
         2261 
         2262         if(scarey && special(file)){
         2263                 fprint(2, "!won't overwrite %s\n", s_to_c(file));
         2264                 s_free(file);
         2265                 return nil;
         2266         }
         2267 
         2268         return file;
         2269 }
         2270 
         2271 Message*
         2272 fcmd(Cmd *c, Message *m)
         2273 {
         2274         String *folder;
         2275 
         2276         if(c->an > 1){
         2277                 fprint(2, "!usage: f takes no arguments\n");
         2278                 return nil;
         2279         }
         2280 
         2281         if(m == &top){
         2282                 Bprint(&out, "!address\n");
         2283                 return nil;
         2284         }
         2285 
         2286         folder = foldername(m->from);
         2287         if(folder == nil)
         2288                 return nil;
         2289 
         2290         if(appendtofile(m, "raw", s_to_c(folder), 1) < 0){
         2291                 s_free(folder);
         2292                 return nil;
         2293         }
         2294         s_free(folder);
         2295 
         2296         m->stored = 1;
         2297         return m;
         2298 }
         2299 
         2300 void
         2301 system(char *cmd, char **av, int in)
         2302 {
         2303         int pid;
         2304 
         2305         switch(pid=fork()){
         2306         case -1:
         2307                 return;
         2308         case 0:
         2309                 if(strcmp(cmd, "rc") == 0)
         2310                         cmd = unsharp("#9/bin/rc");
         2311                 if(in >= 0){
         2312                         close(0);
         2313                         dup(in, 0);
         2314                         close(in);
         2315                 }
         2316                 if(wd[0] != 0)
         2317                         chdir(wd);
         2318                 exec(cmd, av);
         2319                 fprint(2, "!couldn't exec %s\n", cmd);
         2320                 threadexits(0);
         2321         default:
         2322                 if(in >= 0)
         2323                         close(in);
         2324                 while(waitpid() < 0){
         2325                         if(!interrupted)
         2326                                 break;
         2327                         postnote(PNPROC, pid, "die");
         2328                         continue;
         2329                 }
         2330                 break;
         2331         }
         2332 }
         2333 
         2334 Message*
         2335 bangcmd(Cmd *c, Message *m)
         2336 {
         2337         char cmd[4*1024];
         2338         char *p, *e;
         2339         char *av[4];
         2340         int i;
         2341 
         2342         cmd[0] = 0;
         2343         p = cmd;
         2344         e = cmd+sizeof(cmd);
         2345         for(i = 1; i < c->an; i++)
         2346                 p = seprint(p, e, "%s ", c->av[i]);
         2347         av[0] = "rc";
         2348         av[1] = "-c";
         2349         av[2] = cmd;
         2350         av[3] = 0;
         2351         system("rc", av, -1);
         2352         Bprint(&out, "!\n");
         2353         return m;
         2354 }
         2355 
         2356 Message*
         2357 xpipecmd(Cmd *c, Message *m, char *part)
         2358 {
         2359         char cmd[128];
         2360         char *p, *e;
         2361         char *av[4];
         2362         String *path;
         2363         int i, fd;
         2364 
         2365         if(c->an < 2){
         2366                 Bprint(&out, "!usage: | cmd\n");
         2367                 return nil;
         2368         }
         2369 
         2370         if(m == &top){
         2371                 Bprint(&out, "!address\n");
         2372                 return nil;
         2373         }
         2374 
         2375         path = extendpath(m->path, part);
         2376         fd = fsopenfd(mailfs, s_to_c(path), OREAD);
         2377         s_free(path);
         2378 
         2379         if(fd < 0){        /* compatibility with older upas/fs */
         2380                 path = extendpath(m->path, "raw");
         2381                 fd = fsopenfd(mailfs, s_to_c(path), OREAD);
         2382                 s_free(path);
         2383         }
         2384         if(fd < 0){
         2385                 fprint(2, "!message disappeared\n");
         2386                 return nil;
         2387         }
         2388 
         2389         p = cmd;
         2390         e = cmd+sizeof(cmd);
         2391         cmd[0] = 0;
         2392         for(i = 1; i < c->an; i++)
         2393                 p = seprint(p, e, "%s ", c->av[i]);
         2394         av[0] = "rc";
         2395         av[1] = "-c";
         2396         av[2] = cmd;
         2397         av[3] = 0;
         2398         system("rc", av, fd);        /* system closes fd */
         2399         Bprint(&out, "!\n");
         2400         return m;
         2401 }
         2402 
         2403 Message*
         2404 pipecmd(Cmd *c, Message *m)
         2405 {
         2406         return xpipecmd(c, m, "body");
         2407 }
         2408 
         2409 Message*
         2410 rpipecmd(Cmd *c, Message *m)
         2411 {
         2412         return xpipecmd(c, m, "rawunix");
         2413 }
         2414 
         2415 void
         2416 closemb(void)
         2417 {
         2418         CFid *fd;
         2419 
         2420         fd = fsopen(mailfs, "ctl", OWRITE);
         2421         if(fd == nil)
         2422                 sysfatal("can't open ctl: %r");
         2423 
         2424         /* close current mailbox */
         2425         if(*mbname && strcmp(mbname, "mbox") != 0)
         2426                 fsprint(fd, "close %s", mbname);
         2427 
         2428         fsclose(fd);
         2429 }
         2430 
         2431 int
         2432 switchmb(char *file, char *singleton)
         2433 {
         2434         char *p;
         2435         int n, fd;
         2436         String *path;
         2437         char buf[256];
         2438 
         2439         /* if the user didn't say anything and there */
         2440         /* is an mbox mounted already, use that one */
         2441         /* so that the upas/fs -fdefault default is honored. */
         2442         if(0 && (file || (singleton && fsaccess(mailfs, singleton, 0) < 0))){
         2443         /* XXX all wrong */
         2444                 fprint(2, "file=%s singleton=%s\n", file, singleton);
         2445                 if(file == nil)
         2446                         file = "mbox";
         2447 
         2448                 /* close current mailbox */
         2449                 closemb();
         2450                 didopen = 1;
         2451 
         2452                 fd = open("/mail/fs/ctl", ORDWR);
         2453                 if(fd < 0)
         2454                         sysfatal("can't open /mail/fs/ctl: %r");
         2455 
         2456                 path = s_new();
         2457 
         2458                 /* get an absolute path to the mail box */
         2459                 if(strncmp(file, "./", 2) == 0){
         2460                         /* resolve path here since upas/fs doesn't know */
         2461                         /* our working directory */
         2462                         if(getwd(buf, sizeof(buf)-strlen(file)) == nil){
         2463                                 fprint(2, "!can't get working directory: %s\n", buf);
         2464                                 return -1;
         2465                         }
         2466                         s_append(path, buf);
         2467                         s_append(path, file+1);
         2468                 } else {
         2469                         mboxpath(file, user, path, 0);
         2470                 }
         2471 
         2472                 /* make up a handle to use when talking to fs */
         2473                 p = strrchr(file, '/');
         2474                 if(p == nil){
         2475                         /* if its in the mailbox directory, just use the name */
         2476                         strncpy(mbname, file, sizeof(mbname));
         2477                         mbname[sizeof(mbname)-1] = 0;
         2478                 } else {
         2479                         /* make up a mailbox name */
         2480                         p = strrchr(s_to_c(path), '/');
         2481                         p++;
         2482                         if(*p == 0){
         2483                                 fprint(2, "!bad mbox name");
         2484                                 return -1;
         2485                         }
         2486                         strncpy(mbname, p, sizeof(mbname));
         2487                         mbname[sizeof(mbname)-1] = 0;
         2488                         n = strlen(mbname);
         2489                         if(n > Elemlen-12)
         2490                                 n = Elemlen-12;
         2491                         sprint(mbname+n, "%ld", time(0));
         2492                 }
         2493 
         2494                 if(fprint(fd, "open %s %s", s_to_c(path), mbname) < 0){
         2495                         fprint(2, "!can't 'open %s %s': %r\n", file, mbname);
         2496                         s_free(path);
         2497                         return -1;
         2498                 }
         2499                 close(fd);
         2500         }else
         2501         if (singleton && fsaccess(mailfs, singleton, 0)==0){
         2502                 if ((p = strchr(singleton, '/')) == nil){
         2503                         fprint(2, "!bad mbox name");
         2504                         return -1;
         2505                 }
         2506                 n = p-singleton;
         2507                 strncpy(mbname, singleton, n);
         2508                 mbname[n+1] = 0;
         2509                 path = s_reset(nil);
         2510                 mboxpath(mbname, user, path, 0);
         2511         }else{
         2512                 if(file)
         2513                         strecpy(mbname, mbname+sizeof mbname, file);
         2514                 else
         2515                         strcpy(mbname, "mbox");
         2516                 path = s_reset(nil);
         2517                 mboxpath(mbname, user, path, 0);
         2518         }
         2519 
         2520         snprint(root, sizeof root, "%s", mbname);
         2521         rootlen = strlen(root);
         2522 
         2523         if(mbpath != nil)
         2524                 s_free(mbpath);
         2525         mbpath = path;
         2526         return 0;
         2527 }
         2528 
         2529 /* like tokenize but for into lines */
         2530 int
         2531 lineize(char *s, char **f, int n)
         2532 {
         2533         int i;
         2534 
         2535         for(i = 0; *s && i < n; i++){
         2536                 f[i] = s;
         2537                 s = strchr(s, '\n');
         2538                 if(s == nil)
         2539                         break;
         2540                 *s++ = 0;
         2541         }
         2542         return i;
         2543 }
         2544 
         2545 
         2546 
         2547 String*
         2548 rooted(String *s)
         2549 {
         2550         static char buf[256];
         2551 
         2552         if(strcmp(root, ".") != 0)
         2553                 return s;
         2554         snprint(buf, sizeof(buf), "/mail/fs/%s/%s", mbname, s_to_c(s));
         2555         s_free(s);
         2556         return s_copy(buf);
         2557 }
         2558 
         2559 int
         2560 plumb(Message *m, Ctype *cp)
         2561 {
         2562         String *s;
         2563         Plumbmsg *pm;
         2564         static int fd = -2;
         2565 
         2566         if(cp->plumbdest == nil)
         2567                 return -1;
         2568 
         2569         if(fd < -1)
         2570                 fd = plumbopen("send", OWRITE);
         2571         if(fd < 0)
         2572                 return -1;
         2573 
         2574         pm = mallocz(sizeof(Plumbmsg), 1);
         2575         pm->src = strdup("mail");
         2576         if(*cp->plumbdest)
         2577                 pm->dst = strdup(cp->plumbdest);
         2578         pm->wdir = nil;
         2579         pm->type = strdup("text");
         2580         pm->ndata = -1;
         2581         s = rooted(extendpath(m->path, "body"));
         2582         if(cp->ext != nil){
         2583                 s_append(s, ".");
         2584                 s_append(s, cp->ext);
         2585         }
         2586         pm->data = strdup(s_to_c(s));
         2587         s_free(s);
         2588         plumbsend(fd, pm);
         2589         plumbfree(pm);
         2590         return 0;
         2591 }
         2592 
         2593 void
         2594 regerror(char *x)
         2595 {
         2596         USED(x);
         2597 }
         2598 
         2599 String*
         2600 addrecolon(char *s)
         2601 {
         2602         String *str;
         2603 
         2604         if(cistrncmp(s, "re:", 3) != 0){
         2605                 str = s_copy("Re: ");
         2606                 s_append(str, s);
         2607         } else
         2608                 str = s_copy(s);
         2609         return str;
         2610 }
         2611 
         2612 void
         2613 exitfs(char *rv)
         2614 {
         2615         threadexitsall(rv);
         2616 }