Blob


1 #include "common.h"
2 #include <ctype.h>
3 #include <plumb.h>
4 #include <9pclient.h>
5 #include <thread.h>
7 #define system nedsystem
8 #define rcmd nedrcmd
10 typedef struct Message Message;
11 typedef struct Ctype Ctype;
12 typedef struct Cmd Cmd;
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;
24 int interrupted;
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 };
50 Message top;
52 struct Ctype {
53 char *type;
54 char *ext;
55 int display;
56 char *plumbdest;
57 Ctype *next;
58 };
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 },
80 };
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*);
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 };
143 enum
145 NARG= 32
146 };
148 struct Cmd {
149 Message *msgs;
150 Message *(*f)(Cmd*, Message*);
151 int an;
152 char *av[NARG];
153 int delete;
154 };
156 Biobuf out;
157 int startedfs;
158 int reverse;
159 int longestfrom = 12;
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*);
187 CFsys *mailfs;
189 void
190 usage(void)
192 fprint(2, "usage: %s [-nr] [-f mailfile] [-s mailfile]\n", argv0);
193 fprint(2, " %s -c dir\n", argv0);
194 threadexitsall("usage");
197 void
198 catchnote(void *x, char *note)
200 USED(x);
202 if(strstr(note, "interrupt") != nil){
203 interrupted = 1;
204 noted(NCONT);
206 noted(NDFLT);
209 char *
210 plural(int n)
212 if (n == 1)
213 return "";
215 return "s";
218 void
219 threadmain(int argc, char **argv)
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;
230 Binit(&out, 1, OWRITE);
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;
262 user = getlog();
263 if(user == nil || *user == 0)
264 sysfatal("can't read user name");
266 if(cflag){
267 if(argc > 0)
268 creatembox(user, argv[0]);
269 else
270 creatembox(user, nil);
271 threadexitsall(0);
274 if(argc)
275 usage();
276 if((mailfs = nsmount(service, nil)) == nil)
277 sysfatal("cannot mount %s: %r", service);
279 switchmb(file, singleton);
281 top.path = s_copy(root);
283 for(cp = ctype; cp < ctype+nelem(ctype)-1; cp++)
284 cp->next = cp+1;
286 if(singleton != nil){
287 cur = dosingleton(&top, singleton);
288 if(cur == nil){
289 Bprint(&out, "no message\n");
290 exitfs(0);
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));
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, ": ");
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;
322 if(singleton != nil && cmd.f == icmd){
323 Bprint(&out, "!illegal command\n");
324 continue;
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);
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;
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);
357 if(doflush)
358 cur = flushdeleted(cur);
360 qcmd(nil, nil);
363 static char*
364 mkaddrs(char *t)
366 int i, nf, inquote;
367 char **f, *s;
368 Fmt fmt;
370 inquote = 0;
371 nf = 2;
372 for(s=t; *s; s++){
373 if(*s == '\'')
374 inquote = !inquote;
375 if(*s == ' ' && !inquote)
376 nf++;
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]); */
391 free(f);
392 return fmtstrflush(&fmt);
395 /* */
396 /* read the message info */
397 /* */
398 Message*
399 file2message(Message *parent, char *name)
401 Message *m;
402 String *path;
403 char *f[30], *s, *t;
404 int i, nf;
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;
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;
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;
453 return m;
456 void
457 freemessage(Message *m)
459 Message *nm, *next;
461 for(nm = m->child; nm != nil; nm = next){
462 next = nm->next;
463 freemessage(nm);
465 s_free(m->path);
466 s_free(m->info);
467 free(m);
470 /* */
471 /* read a directory into a list of messages */
472 /* */
473 int
474 dir2message(Message *parent, int reverse)
476 int i, n, highest, newmsgs;
477 CFid *fd;
479 Dir *d;
480 Message *first, *last, *m;
482 fd = fsopen(mailfs, s_to_c(parent->path), OREAD);
483 if(fd == nil)
484 return -1;
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;
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;
521 free(d);
522 fsclose(fd);
523 parent->child = first;
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;
535 return newmsgs;
538 /* */
539 /* point directly to a message */
540 /* */
541 Message*
542 dosingleton(Message *parent, char *path)
544 char *p, *np;
545 Message *m;
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;
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;
574 return m;
577 /* */
578 /* read a file into a string */
579 /* */
580 String*
581 file2string(String *dir, char *file)
583 String *s;
584 int n, m;
585 CFid *fd;
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;
594 for(;;){
595 n = s->end - s->ptr;
596 if(n == 0){
597 s_grow(s, 128);
598 continue;
600 m = fsread(fd, s->ptr, n);
601 if(m <= 0)
602 break;
603 s->ptr += m;
604 if(m < n)
605 break;
607 s_terminate(s);
608 fsclose(fd);
610 return s;
613 /* */
614 /* get the length of a file */
615 /* */
616 int
617 filelen(String *dir, char *file)
619 String *path;
620 Dir *d;
621 int rv;
623 path = extendpath(dir, file);
624 d = fsdirstat(mailfs, s_to_c(path));
625 if(d == nil){
626 s_free(path);
627 return -1;
629 s_free(path);
630 rv = d->length;
631 free(d);
632 return rv;
635 /* */
636 /* walk the path name an element */
637 /* */
638 String*
639 extendpath(String *dir, char *name)
641 String *path;
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, "/");
649 s_append(path, name);
650 return path;
653 int
654 cistrncmp(char *a, char *b, int n)
656 while(n-- > 0){
657 if(tolower(*a++) != tolower(*b++))
658 return -1;
660 return 0;
663 int
664 cistrcmp(char *a, char *b)
666 for(;;){
667 if(tolower(*a) != tolower(*b++))
668 return -1;
669 if(*a++ == 0)
670 break;
672 return 0;
675 char*
676 nosecs(char *t)
678 char *p;
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;
689 char *months[12] =
691 "jan", "feb", "mar", "apr", "may", "jun",
692 "jul", "aug", "sep", "oct", "nov", "dec"
693 };
695 int
696 month(char *m)
698 int i;
700 for(i = 0; i < 12; i++)
701 if(cistrcmp(m, months[i]) == 0)
702 return i+1;
703 return 1;
706 enum
708 Yearsecs= 365*24*60*60
709 };
711 void
712 cracktime(char *d, char *out, int len)
714 char in[64];
715 char *f[6];
716 int n;
717 Tm tm;
718 long now, then;
719 char *dtime;
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;
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 = "";
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);
760 Ctype*
761 findctype(Message *m)
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 };
769 for(cp = ctype; cp; cp = cp->next)
770 if(strncmp(cp->type, m->type, strlen(cp->type)) == 0)
771 return cp;
773 if(pipe(pfd) < 0)
774 return &bintype;
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;
798 if (*ftype=='\0' || (p = strchr(ftype, '/')) == nil)
799 return &bintype;
800 *p++ = 0;
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;
814 void
815 mkid(String *s, Message *m)
817 char buf[32];
819 if(m->parent != &top){
820 mkid(s, m->parent);
821 s_append(s, ".");
823 sprint(buf, "%d", m->id);
824 s_append(s, buf);
827 void
828 snprintheader(char *buf, int len, Message *m)
830 char timebuf[32];
831 String *id;
832 char *p, *q;;
834 /* create id */
835 id = s_new();
836 mkid(id, m);
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);
873 s_free(id);
876 char *spaces = " ";
878 void
879 snprintHeader(char *buf, int len, int indent, Message *m)
881 String *id;
882 char typeid[64];
883 char *p, *e;
885 /* create id */
886 id = s_new();
887 mkid(id, m);
889 e = buf + len;
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);
903 s_free(id);
906 char sstring[256];
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)
922 char *p, *np;
923 int c, n;
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;
935 if(*p == 0)
936 p = sstring;
937 else{
938 strncpy(sstring, p, sizeof(sstring));
939 sstring[sizeof(sstring)-1] = 0;
941 return regcomp(p);
944 char*
945 parseaddr(char **pp, Message *first, Message *cur, Message *unspec, Message **mp)
947 int n;
948 Message *m;
949 char *p;
950 Reprog *prog;
951 int c, sign;
952 char buf[256];
954 *mp = nil;
955 p = *pp;
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;
968 switch(*p){
969 default:
970 if(sign){
971 n = 1;
972 goto number;
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;
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;
1004 for(m = cur; m != nil && n > 0; n--)
1005 m = m->next;
1006 break;
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;
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;
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;
1040 break;
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)
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;
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);
1069 if(**pp == '+' || **pp == '-' || **pp == '/' || **pp == '%')
1070 return parseaddr(pp, first, *mp, *mp, mp);
1072 return nil;
1075 /* */
1076 /* search a message for a regular expression match */
1077 /* */
1078 int
1079 rawsearch(Message *m, Reprog *prog)
1081 char buf[4096+1];
1082 int i, rv;
1083 CFid *fd;
1084 String *path;
1086 path = extendpath(m->path, "raw");
1087 fd = fsopen(mailfs, s_to_c(path), OREAD);
1088 if(fd == nil)
1089 return 0;
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;
1103 if(i < sizeof(buf)-1)
1104 break;
1105 if(fsseek(fd, -128LL, 1) < 0)
1106 break;
1109 fsclose(fd);
1110 s_free(path);
1111 return rv;
1115 char*
1116 parsecmd(char *p, Cmd *cmd, Message *first, Message *cur)
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];
1126 cmd->delete = 0;
1127 l = &cmd->msgs;
1128 *l = nil;
1130 /* eat white space */
1131 while(*p == ' ')
1132 p++;
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;
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;
1156 /* global search ? */
1157 if(*p == 'g'){
1158 p++;
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;
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;
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;
1191 free(prog);
1193 } else {
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)
1207 err = parseaddr(&p, first, cur, last, &e);
1208 if(err != nil)
1209 return err;
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;
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;
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 = ' ';
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]++;
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;
1259 cmd->f = cmdtab[i].f;
1261 return nil;
1264 /* inefficient read from standard input */
1265 char*
1266 readline(char *prompt, char *line, int len)
1268 char *p, *e;
1269 int n;
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;
1283 if(n == 0)
1284 return nil;
1285 if(*p == '\n')
1286 break;
1288 *p = 0;
1289 return line;
1292 void
1293 messagecount(Message *m)
1295 int i;
1297 i = 0;
1298 for(; m != nil; m = m->next)
1299 i++;
1300 Bprint(&out, "%d message%s\n", i, plural(i));
1303 Message*
1304 aichcmd(Message *m, int indent)
1306 char hdr[256];
1308 if(m == &top)
1309 return nil;
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;
1318 Message*
1319 Hcmd(Cmd *x, Message *m)
1321 USED(x);
1323 if(m == &top)
1324 return nil;
1325 aichcmd(m, 0);
1326 return nil;
1329 Message*
1330 hcmd(Cmd *x, Message *m)
1332 char hdr[256];
1334 USED(x);
1335 if(m == &top)
1336 return nil;
1338 snprintheader(hdr, sizeof(hdr), m);
1339 Bprint(&out, "%s\n", hdr);
1340 return nil;
1343 Message*
1344 bcmd(Cmd *x, Message *m)
1346 int i;
1347 Message *om = m;
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;
1358 return om;
1361 Message*
1362 ncmd(Cmd *x, Message *m)
1364 USED(x);
1365 if(m == &top)
1366 return m->child;
1367 return m->next;
1370 int
1371 printpart(String *s, char *part)
1373 char buf[4096];
1374 int n, tot;
1375 CFid *fd;
1376 String *path;
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;
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;
1393 fsclose(fd);
1394 return tot;
1397 int
1398 printhtml(Message *m)
1400 Cmd c;
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;
1411 Message*
1412 Pcmd(Cmd *x, Message *m)
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;
1423 void
1424 compress(char *p)
1426 char *np;
1427 int last;
1429 last = ' ';
1430 for(np = p; *p; p++){
1431 if(*p != ' ' || last != ' '){
1432 last = *p;
1433 *np++ = last;
1436 *np = 0;
1439 Message*
1440 pcmd(Cmd *x, Message *m)
1442 Message *nm;
1443 Ctype *cp;
1444 String *s;
1445 char buf[128];
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;
1466 if(nm == nil)
1467 for(nm = m->child; nm != nil; nm = nm->next){
1468 cp = findctype(nm);
1469 if(cp->display)
1470 break;
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);
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));
1503 s_free(s);
1505 } else {
1506 hcmd(nil, m);
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);
1515 return m;
1518 void
1519 printpartindented(String *s, char *part, char *indent)
1521 int fd;
1522 char *p;
1523 String *path;
1524 Biobuf *b;
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;
1533 b = Bfdopen(fd, OREAD);
1534 if(b == 0){
1535 fprint(2, "out of memory\n");
1536 close(fd);
1537 return;
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;
1546 Bprint(&out, "\n");
1547 Bterm(b);
1550 Message*
1551 quotecmd(Cmd *x, Message *m)
1553 Message *nm;
1554 Ctype *cp;
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;
1571 if(nm == nil)
1572 for(nm = m->child; nm != nil; nm = nm->next){
1573 cp = findctype(nm);
1574 if(cp->display)
1575 break;
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);
1587 return m;
1590 /* really delete messages */
1591 Message*
1592 flushdeleted(Message *cur)
1594 Message *m, **l;
1595 char buf[1024], *p, *e, *msg;
1596 int deld, n;
1597 CFid *fd;
1598 int i;
1600 doflush = 0;
1601 deld = 0;
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);
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;
1619 /* don't return a pointer to a deleted message */
1620 if(m == cur)
1621 cur = m->next;
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");
1634 p = seprint(p, e, " %s", msg);
1635 n++;
1637 /* unchain and free */
1638 *l = m->next;
1639 if(m->next)
1640 m->next->prev = m->prev;
1641 freemessage(m);
1643 if(n)
1644 fswrite(fd, buf, p-buf);
1646 fsclose(fd);
1648 if(deld)
1649 Bprint(&out, "!%d message%s deleted\n", deld, plural(deld));
1651 /* renumber */
1652 i = 1;
1653 for(m = top.child; m != nil; m = m->next)
1654 m->id = natural ? m->fileno : i++;
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;
1664 return cur;
1667 Message*
1668 qcmd(Cmd *x, Message *m)
1670 USED(x);
1671 USED(m);
1673 flushdeleted(nil);
1675 if(didopen)
1676 closemb();
1677 Bflush(&out);
1679 exitfs(0);
1680 return nil; /* not reached */
1683 Message*
1684 ycmd(Cmd *x, Message *m)
1686 USED(x);
1688 doflush = 1;
1690 return icmd(nil, m);
1693 Message*
1694 xcmd(Cmd *x, Message *m)
1696 USED(x);
1697 USED(m);
1699 exitfs(0);
1700 return nil; /* not reached */
1703 Message*
1704 eqcmd(Cmd *x, Message *m)
1706 USED(x);
1708 if(m == &top)
1709 Bprint(&out, "0\n");
1710 else
1711 Bprint(&out, "%d\n", m->id);
1712 return nil;
1715 Message*
1716 dcmd(Cmd *x, Message *m)
1718 USED(x);
1720 if(m == &top){
1721 Bprint(&out, "!address\n");
1722 return nil;
1724 while(m->parent != &top)
1725 m = m->parent;
1726 m->deleted = 1;
1727 return m;
1730 Message*
1731 ucmd(Cmd *x, Message *m)
1733 USED(x);
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;
1746 Message*
1747 icmd(Cmd *x, Message *m)
1749 int n;
1750 char buf[1024];
1751 CFid *fd;
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);
1760 n = dir2message(&top, reverse);
1761 if(n > 0)
1762 Bprint(&out, "%d new message%s\n", n, plural(n));
1763 return m;
1766 Message*
1767 helpcmd(Cmd *x, Message *m)
1769 int i;
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;
1782 int
1783 tomailer(char **av)
1785 static char *marshal;
1786 Waitmsg *w;
1787 int pid, i;
1789 if(marshal == nil)
1790 marshal = unsharp("#9/bin/upas/marshal");
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]);
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;
1820 if(w->msg[0]){
1821 fprint(2, "mailer failed: %s\n", w->msg);
1822 free(w);
1823 return -1;
1825 free(w);
1826 Bprint(&out, "!\n");
1827 break;
1829 return 0;
1832 /* */
1833 /* like tokenize but obey "" quoting */
1834 /* */
1835 int
1836 tokenize822(char *str, char **args, int max)
1838 int na;
1839 int intok = 0, inquote = 0;
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;
1870 return 0; /* can't get here; silence compiler */
1873 Message*
1874 rcmd(Cmd *c, Message *m)
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;
1885 if(m == &top){
1886 Bprint(&out, "!address\n");
1887 return nil;
1890 addr = nil;
1891 for(nm = m; nm != &top; nm = nm->parent){
1892 if(*nm->replyto != 0){
1893 addr = nm->replyto;
1894 break;
1897 if(addr == nil){
1898 Bprint(&out, "!no reply address\n");
1899 return nil;
1902 if(nm == &top){
1903 print("!noone to reply to\n");
1904 return nil;
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;
1916 av[ai++] = "-R";
1917 rpath = rooted(s_clone(m->path));
1918 av[ai++] = s_to_c(rpath);
1920 if(strchr(c->av[0], 'f') != nil){
1921 fcmd(c, m);
1922 av[ai++] = "-F";
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);
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;
1947 Message*
1948 mcmd(Cmd *c, Message *m)
1950 char **av;
1951 int i, ai;
1952 String *path;
1954 if(m == &top){
1955 Bprint(&out, "!address\n");
1956 return nil;
1959 if(c->an < 2){
1960 fprint(2, "!usage: M list-of addresses\n");
1961 return nil;
1964 ai = 1;
1965 av = malloc(sizeof(char*)*(c->an + 8));
1967 av[ai++] = "-t";
1968 if(m->parent == &top)
1969 av[ai++] = "message/rfc822";
1970 else
1971 av[ai++] = "mime";
1973 av[ai++] = "-A";
1974 path = rooted(extendpath(m->path, "raw"));
1975 av[ai++] = s_to_c(path);
1977 if(strchr(c->av[0], 'M') == nil)
1978 av[ai++] = "-n";
1980 for(i = 1; i < c->an; i++)
1981 av[ai++] = c->av[i];
1982 av[ai] = 0;
1984 if(tomailer(av) < 0)
1985 m = nil;
1986 if(path != nil)
1987 s_free(path);
1988 free(av);
1989 return m;
1992 Message*
1993 acmd(Cmd *c, Message *m)
1995 char *av[128];
1996 int i, ai;
1997 String *from, *to, *cc, *path = nil, *subject = nil;
1999 if(m == &top){
2000 Bprint(&out, "!address\n");
2001 return nil;
2004 ai = 1;
2005 if(*m->subject){
2006 av[ai++] = "-s";
2007 subject = addrecolon(m->subject);
2008 av[ai++] = s_to_c(subject);
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);
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;
2038 String *
2039 relpath(char *path, String *to)
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);
2049 return to;
2052 int
2053 appendtofile(Message *m, char *part, char *base, int mbox)
2055 String *file, *h;
2056 int in, out, rv;
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;
2065 s_reset(file);
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));
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;
2090 if(mbox)
2091 seek(out, 0, 2);
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);
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);
2108 close(in);
2109 close(out);
2111 if(rv >= 0)
2112 print("!saved in %s\n", s_to_c(file));
2113 s_free(file);
2114 return rv;
2117 Message*
2118 scmd(Cmd *c, Message *m)
2120 char buf[256];
2121 CFid *fd;
2122 char *file, *msg;
2124 if(m == &top){
2125 Bprint(&out, "!address\n");
2126 return nil;
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;
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;
2157 fsclose(fd);
2159 m->stored = 1;
2160 return m;
2163 Message*
2164 wcmd(Cmd *c, Message *m)
2166 char *file;
2168 if(m == &top){
2169 Bprint(&out, "!address\n");
2170 return nil;
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;
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;
2193 if(appendtofile(m, "body", file, 0) < 0)
2194 return nil;
2195 m->stored = 1;
2196 return m;
2199 char *specialfile[] =
2201 "pipeto",
2202 "pipefrom",
2203 "L.mbox",
2204 "forward",
2205 "names"
2208 /* return 1 if this is a special file */
2209 static int
2210 special(String *s)
2212 char *p;
2213 int i;
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;
2226 /* open the folder using the recipients account name */
2227 static String*
2228 foldername(char *rcvr)
2230 char *p;
2231 int c;
2232 String *file;
2233 Dir *d;
2234 int scarey;
2236 file = s_new();
2237 mboxpath("f", user, file, 0);
2238 d = dirstat(s_to_c(file));
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;
2248 free(d);
2250 p = strrchr(rcvr, '!');
2251 if(p != nil)
2252 rcvr = p+1;
2254 while(*rcvr && *rcvr != '@'){
2255 c = *rcvr++;
2256 if(c == '/')
2257 c = '_';
2258 s_putc(file, c);
2260 s_terminate(file);
2262 if(scarey && special(file)){
2263 fprint(2, "!won't overwrite %s\n", s_to_c(file));
2264 s_free(file);
2265 return nil;
2268 return file;
2271 Message*
2272 fcmd(Cmd *c, Message *m)
2274 String *folder;
2276 if(c->an > 1){
2277 fprint(2, "!usage: f takes no arguments\n");
2278 return nil;
2281 if(m == &top){
2282 Bprint(&out, "!address\n");
2283 return nil;
2286 folder = foldername(m->from);
2287 if(folder == nil)
2288 return nil;
2290 if(appendtofile(m, "raw", s_to_c(folder), 1) < 0){
2291 s_free(folder);
2292 return nil;
2294 s_free(folder);
2296 m->stored = 1;
2297 return m;
2300 void
2301 system(char *cmd, char **av, int in)
2303 int pid;
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);
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;
2330 break;
2334 Message*
2335 bangcmd(Cmd *c, Message *m)
2337 char cmd[4*1024];
2338 char *p, *e;
2339 char *av[4];
2340 int i;
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;
2356 Message*
2357 xpipecmd(Cmd *c, Message *m, char *part)
2359 char cmd[128];
2360 char *p, *e;
2361 char *av[4];
2362 String *path;
2363 int i, fd;
2365 if(c->an < 2){
2366 Bprint(&out, "!usage: | cmd\n");
2367 return nil;
2370 if(m == &top){
2371 Bprint(&out, "!address\n");
2372 return nil;
2375 path = extendpath(m->path, part);
2376 fd = fsopenfd(mailfs, s_to_c(path), OREAD);
2377 s_free(path);
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);
2384 if(fd < 0){
2385 fprint(2, "!message disappeared\n");
2386 return nil;
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;
2403 Message*
2404 pipecmd(Cmd *c, Message *m)
2406 return xpipecmd(c, m, "body");
2409 Message*
2410 rpipecmd(Cmd *c, Message *m)
2412 return xpipecmd(c, m, "rawunix");
2415 void
2416 closemb(void)
2418 CFid *fd;
2420 fd = fsopen(mailfs, "ctl", OWRITE);
2421 if(fd == nil)
2422 sysfatal("can't open ctl: %r");
2424 /* close current mailbox */
2425 if(*mbname && strcmp(mbname, "mbox") != 0)
2426 fsprint(fd, "close %s", mbname);
2428 fsclose(fd);
2431 int
2432 switchmb(char *file, char *singleton)
2434 char *p;
2435 int n, fd;
2436 String *path;
2437 char buf[256];
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";
2448 /* close current mailbox */
2449 closemb();
2450 didopen = 1;
2452 fd = open("/mail/fs/ctl", ORDWR);
2453 if(fd < 0)
2454 sysfatal("can't open /mail/fs/ctl: %r");
2456 path = s_new();
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;
2466 s_append(path, buf);
2467 s_append(path, file+1);
2468 } else {
2469 mboxpath(file, user, path, 0);
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;
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));
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;
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;
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);
2520 snprint(root, sizeof root, "%s", mbname);
2521 rootlen = strlen(root);
2523 if(mbpath != nil)
2524 s_free(mbpath);
2525 mbpath = path;
2526 return 0;
2529 /* like tokenize but for into lines */
2530 int
2531 lineize(char *s, char **f, int n)
2533 int i;
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;
2542 return i;
2547 String*
2548 rooted(String *s)
2550 static char buf[256];
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);
2559 int
2560 plumb(Message *m, Ctype *cp)
2562 String *s;
2563 Plumbmsg *pm;
2564 static int fd = -2;
2566 if(cp->plumbdest == nil)
2567 return -1;
2569 if(fd < -1)
2570 fd = plumbopen("send", OWRITE);
2571 if(fd < 0)
2572 return -1;
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);
2586 pm->data = strdup(s_to_c(s));
2587 s_free(s);
2588 plumbsend(fd, pm);
2589 plumbfree(pm);
2590 return 0;
2593 void
2594 regerror(char *x)
2596 USED(x);
2599 String*
2600 addrecolon(char *s)
2602 String *str;
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;
2612 void
2613 exitfs(char *rv)
2615 threadexitsall(rv);