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;
230 Binit(&out, 1, OWRITE);
232 file = nil;
233 singleton = nil;
234 reverse = 1;
235 cflag = 0;
236 ARGBEGIN {
237 case 'c':
238 cflag = 1;
239 break;
240 case 'f':
241 file = EARGF(usage());
242 break;
243 case 's':
244 singleton = EARGF(usage());
245 break;
246 case 'r':
247 reverse = 0;
248 break;
249 case 'n':
250 natural = 1;
251 reverse = 0;
252 break;
253 default:
254 usage();
255 break;
256 } ARGEND;
258 user = getlog();
259 if(user == nil || *user == 0)
260 sysfatal("can't read user name");
262 if(cflag){
263 if(argc > 0)
264 creatembox(user, argv[0]);
265 else
266 creatembox(user, nil);
267 threadexitsall(0);
270 if(argc)
271 usage();
272 if((mailfs = nsmount("mail", nil)) == nil)
273 sysfatal("cannot mount mail: %r");
275 switchmb(file, singleton);
277 top.path = s_copy(root);
279 for(cp = ctype; cp < ctype+nelem(ctype)-1; cp++)
280 cp->next = cp+1;
282 if(singleton != nil){
283 cur = dosingleton(&top, singleton);
284 if(cur == nil){
285 Bprint(&out, "no message\n");
286 exitfs(0);
288 pcmd(nil, cur);
289 } else {
290 cur = &top;
291 n = dir2message(&top, reverse);
292 if(n < 0)
293 sysfatal("can't read %s", s_to_c(top.path));
294 Bprint(&out, "%d message%s\n", n, plural(n));
298 notify(catchnote);
299 prompt = s_new();
300 for(;;){
301 s_reset(prompt);
302 if(cur == &top)
303 s_append(prompt, ": ");
304 else {
305 mkid(prompt, cur);
306 s_append(prompt, ": ");
309 /* leave space at the end of cmd line in case parsecmd needs to */
310 /* add a space after a '|' or '!' */
311 if(readline(s_to_c(prompt), cmdline, sizeof(cmdline)-1) == nil)
312 break;
313 err = parsecmd(cmdline, &cmd, top.child, cur);
314 if(err != nil){
315 Bprint(&out, "!%s\n", err);
316 continue;
318 if(singleton != nil && cmd.f == icmd){
319 Bprint(&out, "!illegal command\n");
320 continue;
322 interrupted = 0;
323 if(cmd.msgs == nil || cmd.msgs == &top){
324 x = (*cmd.f)(&cmd, &top);
325 if(x != nil)
326 cur = x;
327 } else for(m = cmd.msgs; m != nil; m = m->cmd){
328 x = m;
329 if(cmd.delete){
330 dcmd(&cmd, x);
332 /* dp acts differently than all other commands */
333 /* since its an old lesk idiom that people love. */
334 /* it deletes the current message, moves the current */
335 /* pointer ahead one and prints. */
336 if(cmd.f == pcmd){
337 if(x->next == nil){
338 Bprint(&out, "!address\n");
339 cur = x;
340 break;
341 } else
342 x = x->next;
345 x = (*cmd.f)(&cmd, x);
346 if(x != nil)
347 cur = x;
348 if(interrupted)
349 break;
350 if(singleton != nil && (cmd.delete || cmd.f == dcmd))
351 qcmd(nil, nil);
353 if(doflush)
354 cur = flushdeleted(cur);
356 qcmd(nil, nil);
359 static char*
360 mkaddrs(char *t)
362 int i, nf, inquote;
363 char **f, *s;
364 Fmt fmt;
366 inquote = 0;
367 nf = 2;
368 for(s=t; *s; s++){
369 if(*s == '\'')
370 inquote = !inquote;
371 if(*s == ' ' && !inquote)
372 nf++;
374 f = malloc(nf*sizeof f[0]);
375 if(f == nil)
376 return nil;
377 nf = tokenize(t, f, nf);
378 fmtstrinit(&fmt);
379 for(i=0; i+1<nf; i+=2){
380 if(i > 0)
381 fmtprint(&fmt, " ");
382 /* if(f[i][0] == 0 || strcmp(f[i], f[i+1]) == 0) */
383 fmtprint(&fmt, "%s", f[i+1]);
384 /* else */
385 /* fmtprint(&fmt, "%s <%s>", f[i], f[i+1]); */
387 free(f);
388 return fmtstrflush(&fmt);
391 /* */
392 /* read the message info */
393 /* */
394 Message*
395 file2message(Message *parent, char *name)
397 Message *m;
398 String *path;
399 char *f[30], *s, *t;
400 int i, nf;
402 m = mallocz(sizeof(Message), 1);
403 if(m == nil)
404 return nil;
405 m->path = path = extendpath(parent->path, name);
406 m->fileno = atoi(name);
407 m->info = file2string(path, "info");
408 m->from = "";
409 m->to = "";
410 m->cc = "";
411 m->replyto = "";
412 m->date = "";
413 m->subject = "";
414 m->type = "";
415 m->disposition = "";
416 m->filename = "";
417 nf = lineize(s_to_c(m->info), f, nelem(f));
418 for(i=0; i<nf; i++){
419 s = f[i];
420 t = strchr(f[i], ' ');
421 if(t == nil)
422 continue;
423 *t++ = 0;
425 if(strcmp(s, "from") == 0)
426 m->from = mkaddrs(t);
427 else if(strcmp(s, "to") == 0)
428 m->to = mkaddrs(t);
429 else if(strcmp(s, "cc") == 0)
430 m->cc = mkaddrs(t);
431 else if(strcmp(s, "replyto") == 0)
432 m->replyto = mkaddrs(t);
433 else if(strcmp(s, "unixdate") == 0 && (t=strchr(t, ' ')) != nil)
434 m->date = t;
435 else if(strcmp(s, "subject") == 0)
436 m->subject = t;
437 else if(strcmp(s, "type") == 0)
438 m->type = t;
439 else if(strcmp(s, "disposition") == 0)
440 m->disposition = t;
441 else if(strcmp(s, "filename") == 0)
442 m->filename = t;
444 m->len = filelen(path, "raw");
445 if(strstr(m->type, "multipart") != nil || strcmp(m->type, "message/rfc822") == 0)
446 dir2message(m, 0);
447 m->parent = parent;
449 return m;
452 void
453 freemessage(Message *m)
455 Message *nm, *next;
457 for(nm = m->child; nm != nil; nm = next){
458 next = nm->next;
459 freemessage(nm);
461 s_free(m->path);
462 s_free(m->info);
463 free(m);
466 /* */
467 /* read a directory into a list of messages */
468 /* */
469 int
470 dir2message(Message *parent, int reverse)
472 int i, n, highest, newmsgs;
473 CFid *fd;
475 Dir *d;
476 Message *first, *last, *m;
478 fd = fsopen(mailfs, s_to_c(parent->path), OREAD);
479 if(fd == nil)
480 return -1;
482 /* count current entries */
483 first = parent->child;
484 highest = newmsgs = 0;
485 for(last = parent->child; last != nil && last->next != nil; last = last->next)
486 if(last->fileno > highest)
487 highest = last->fileno;
488 if(last != nil)
489 if(last->fileno > highest)
490 highest = last->fileno;
492 n = fsdirreadall(fd, &d);
493 for(i = 0; i < n; i++){
494 if((d[i].qid.type & QTDIR) == 0)
495 continue;
496 if(atoi(d[i].name) <= highest)
497 continue;
498 m = file2message(parent, d[i].name);
499 /* fprint(2,"returned from file2message\n"); */
500 if(m == nil)
501 break;
502 newmsgs++;
503 if(reverse){
504 m->next = first;
505 if(first != nil)
506 first->prev = m;
507 first = m;
508 } else {
509 if(first == nil)
510 first = m;
511 else
512 last->next = m;
513 m->prev = last;
514 last = m;
517 free(d);
518 fsclose(fd);
519 parent->child = first;
521 /* renumber and file longest from */
522 i = 1;
523 longestfrom = 12;
524 for(m = first; m != nil; m = m->next){
525 m->id = natural ? m->fileno : i++;
526 n = strlen(m->from);
527 if(n > longestfrom)
528 longestfrom = n;
531 return newmsgs;
534 /* */
535 /* point directly to a message */
536 /* */
537 Message*
538 dosingleton(Message *parent, char *path)
540 char *p, *np;
541 Message *m;
543 /* walk down to message and read it */
544 if(strlen(path) < rootlen)
545 return nil;
546 if(path[rootlen] != '/')
547 return nil;
548 p = path+rootlen+1;
549 np = strchr(p, '/');
550 if(np != nil)
551 *np = 0;
552 m = file2message(parent, p);
553 if(m == nil)
554 return nil;
555 parent->child = m;
556 m->id = 1;
558 /* walk down to requested component */
559 while(np != nil){
560 *np = '/';
561 np = strchr(np+1, '/');
562 if(np != nil)
563 *np = 0;
564 for(m = m->child; m != nil; m = m->next)
565 if(strcmp(path, s_to_c(m->path)) == 0)
566 return m;
567 if(m == nil)
568 return nil;
570 return m;
573 /* */
574 /* read a file into a string */
575 /* */
576 String*
577 file2string(String *dir, char *file)
579 String *s;
580 int n, m;
581 CFid *fd;
583 s = extendpath(dir, file);
584 fd = fsopen(mailfs, s_to_c(s), OREAD);
585 s_grow(s, 512); /* avoid multiple reads on info files */
586 s_reset(s);
587 if(fd == nil)
588 return s;
590 for(;;){
591 n = s->end - s->ptr;
592 if(n == 0){
593 s_grow(s, 128);
594 continue;
596 m = fsread(fd, s->ptr, n);
597 if(m <= 0)
598 break;
599 s->ptr += m;
600 if(m < n)
601 break;
603 s_terminate(s);
604 fsclose(fd);
606 return s;
609 /* */
610 /* get the length of a file */
611 /* */
612 int
613 filelen(String *dir, char *file)
615 String *path;
616 Dir *d;
617 int rv;
619 path = extendpath(dir, file);
620 d = fsdirstat(mailfs, s_to_c(path));
621 if(d == nil){
622 s_free(path);
623 return -1;
625 s_free(path);
626 rv = d->length;
627 free(d);
628 return rv;
631 /* */
632 /* walk the path name an element */
633 /* */
634 String*
635 extendpath(String *dir, char *name)
637 String *path;
639 if(strcmp(s_to_c(dir), ".") == 0)
640 path = s_new();
641 else {
642 path = s_copy(s_to_c(dir));
643 s_append(path, "/");
645 s_append(path, name);
646 return path;
649 int
650 cistrncmp(char *a, char *b, int n)
652 while(n-- > 0){
653 if(tolower(*a++) != tolower(*b++))
654 return -1;
656 return 0;
659 int
660 cistrcmp(char *a, char *b)
662 for(;;){
663 if(tolower(*a) != tolower(*b++))
664 return -1;
665 if(*a++ == 0)
666 break;
668 return 0;
671 char*
672 nosecs(char *t)
674 char *p;
676 p = strchr(t, ':');
677 if(p == nil)
678 return t;
679 p = strchr(p+1, ':');
680 if(p != nil)
681 *p = 0;
682 return t;
685 char *months[12] =
687 "jan", "feb", "mar", "apr", "may", "jun",
688 "jul", "aug", "sep", "oct", "nov", "dec"
689 };
691 int
692 month(char *m)
694 int i;
696 for(i = 0; i < 12; i++)
697 if(cistrcmp(m, months[i]) == 0)
698 return i+1;
699 return 1;
702 enum
704 Yearsecs= 365*24*60*60
705 };
707 void
708 cracktime(char *d, char *out, int len)
710 char in[64];
711 char *f[6];
712 int n;
713 Tm tm;
714 long now, then;
715 char *dtime;
717 *out = 0;
718 if(d == nil)
719 return;
720 strncpy(in, d, sizeof(in));
721 in[sizeof(in)-1] = 0;
722 n = getfields(in, f, 6, 1, " \t\r\n");
723 if(n != 6){
724 /* unknown style */
725 snprint(out, 16, "%10.10s", d);
726 return;
728 now = time(0);
729 memset(&tm, 0, sizeof tm);
730 if(strchr(f[0], ',') != nil && strchr(f[4], ':') != nil){
731 /* 822 style */
732 tm.year = atoi(f[3])-1900;
733 tm.mon = month(f[2]);
734 tm.mday = atoi(f[1]);
735 dtime = nosecs(f[4]);
736 then = tm2sec(&tm);
737 } else if(strchr(f[3], ':') != nil){
738 /* unix style */
739 tm.year = atoi(f[5])-1900;
740 tm.mon = month(f[1]);
741 tm.mday = atoi(f[2]);
742 dtime = nosecs(f[3]);
743 then = tm2sec(&tm);
744 } else {
745 then = now;
746 tm = *localtime(now);
747 dtime = "";
750 if(now - then < Yearsecs/2)
751 snprint(out, len, "%2d/%2.2d %s", tm.mon, tm.mday, dtime);
752 else
753 snprint(out, len, "%2d/%2.2d %4d", tm.mon, tm.mday, tm.year+1900);
756 Ctype*
757 findctype(Message *m)
759 char *p;
760 char ftype[128];
761 int n, pfd[2];
762 Ctype *a, *cp;
763 static Ctype bintype = { "application/octet-stream", "bin", 0, 0 };
765 for(cp = ctype; cp; cp = cp->next)
766 if(strncmp(cp->type, m->type, strlen(cp->type)) == 0)
767 return cp;
769 if(pipe(pfd) < 0)
770 return &bintype;
772 *ftype = 0;
773 switch(fork()){
774 case -1:
775 break;
776 case 0:
777 close(pfd[1]);
778 close(0);
779 dup(pfd[0], 0);
780 close(1);
781 dup(pfd[0], 1);
782 execl(unsharp("#9/bin/file"), "file", "-m", s_to_c(extendpath(m->path, "body")), nil);
783 threadexits(0);
784 default:
785 close(pfd[0]);
786 n = read(pfd[1], ftype, sizeof(ftype));
787 if(n > 0)
788 ftype[n] = 0;
789 close(pfd[1]);
790 waitpid();
791 break;
794 if (*ftype=='\0' || (p = strchr(ftype, '/')) == nil)
795 return &bintype;
796 *p++ = 0;
798 a = mallocz(sizeof(Ctype), 1);
799 a->type = strdup(ftype);
800 a->ext = strdup(p);
801 a->display = 0;
802 a->plumbdest = strdup(ftype);
803 for(cp = ctype; cp->next; cp = cp->next)
804 continue;
805 cp->next = a;
806 a->next = nil;
807 return a;
810 void
811 mkid(String *s, Message *m)
813 char buf[32];
815 if(m->parent != &top){
816 mkid(s, m->parent);
817 s_append(s, ".");
819 sprint(buf, "%d", m->id);
820 s_append(s, buf);
823 void
824 snprintheader(char *buf, int len, Message *m)
826 char timebuf[32];
827 String *id;
828 char *p, *q;;
830 /* create id */
831 id = s_new();
832 mkid(id, m);
834 if(*m->from == 0){
835 /* no from */
836 snprint(buf, len, "%-3s %s %6d %s",
837 s_to_c(id),
838 m->type,
839 m->len,
840 m->filename);
841 } else if(*m->subject){
842 q = p = strdup(m->subject);
843 while(*p == ' ')
844 p++;
845 if(strlen(p) > 50)
846 p[50] = 0;
847 cracktime(m->date, timebuf, sizeof(timebuf));
848 snprint(buf, len, "%-3s %c%c%c %6d %11.11s %-*.*s %s",
849 s_to_c(id),
850 m->child ? 'H' : ' ',
851 m->deleted ? 'd' : ' ',
852 m->stored ? 's' : ' ',
853 m->len,
854 timebuf,
855 longestfrom, longestfrom, m->from,
856 p);
857 free(q);
858 } else {
859 cracktime(m->date, timebuf, sizeof(timebuf));
860 snprint(buf, len, "%-3s %c%c%c %6d %11.11s %s",
861 s_to_c(id),
862 m->child ? 'H' : ' ',
863 m->deleted ? 'd' : ' ',
864 m->stored ? 's' : ' ',
865 m->len,
866 timebuf,
867 m->from);
869 s_free(id);
872 char *spaces = " ";
874 void
875 snprintHeader(char *buf, int len, int indent, Message *m)
877 String *id;
878 char typeid[64];
879 char *p, *e;
881 /* create id */
882 id = s_new();
883 mkid(id, m);
885 e = buf + len;
887 snprint(typeid, sizeof typeid, "%s %s", s_to_c(id), m->type);
888 if(indent < 6)
889 p = seprint(buf, e, "%-32s %-6d ", typeid, m->len);
890 else
891 p = seprint(buf, e, "%-64s %-6d ", typeid, m->len);
892 if(m->filename && *m->filename)
893 p = seprint(p, e, "(file,%s)", m->filename);
894 if(m->from && *m->from)
895 p = seprint(p, e, "(from,%s)", m->from);
896 if(m->subject && *m->subject)
897 seprint(p, e, "(subj,%s)", m->subject);
899 s_free(id);
902 char sstring[256];
904 /* cmd := range cmd ' ' arg-list ; */
905 /* range := address */
906 /* | address ',' address */
907 /* | 'g' search ; */
908 /* address := msgno */
909 /* | search ; */
910 /* msgno := number */
911 /* | number '/' msgno ; */
912 /* search := '/' string '/' */
913 /* | '%' string '%' ; */
914 /* */
915 Reprog*
916 parsesearch(char **pp)
918 char *p, *np;
919 int c, n;
921 p = *pp;
922 c = *p++;
923 np = strchr(p, c);
924 if(np != nil){
925 *np++ = 0;
926 *pp = np;
927 } else {
928 n = strlen(p);
929 *pp = p + n;
931 if(*p == 0)
932 p = sstring;
933 else{
934 strncpy(sstring, p, sizeof(sstring));
935 sstring[sizeof(sstring)-1] = 0;
937 return regcomp(p);
940 char*
941 parseaddr(char **pp, Message *first, Message *cur, Message *unspec, Message **mp)
943 int n;
944 Message *m;
945 char *p;
946 Reprog *prog;
947 int c, sign;
948 char buf[256];
950 *mp = nil;
951 p = *pp;
953 if(*p == '+'){
954 sign = 1;
955 p++;
956 *pp = p;
957 } else if(*p == '-'){
958 sign = -1;
959 p++;
960 *pp = p;
961 } else
962 sign = 0;
964 switch(*p){
965 default:
966 if(sign){
967 n = 1;
968 goto number;
970 *mp = unspec;
971 break;
972 case '0': case '1': case '2': case '3': case '4':
973 case '5': case '6': case '7': case '8': case '9':
974 n = strtoul(p, pp, 10);
975 if(n == 0){
976 if(sign)
977 *mp = cur;
978 else
979 *mp = &top;
980 break;
982 number:
983 m = nil;
984 switch(sign){
985 case 0:
986 for(m = first; m != nil; m = m->next)
987 if(m->id == n)
988 break;
989 break;
990 case -1:
991 if(cur != &top)
992 for(m = cur; m != nil && n > 0; n--)
993 m = m->prev;
994 break;
995 case 1:
996 if(cur == &top){
997 n--;
998 cur = first;
1000 for(m = cur; m != nil && n > 0; n--)
1001 m = m->next;
1002 break;
1004 if(m == nil)
1005 return "address";
1006 *mp = m;
1007 break;
1008 case '%':
1009 case '/':
1010 case '?':
1011 c = *p;
1012 prog = parsesearch(pp);
1013 if(prog == nil)
1014 return "badly formed regular expression";
1015 m = nil;
1016 switch(c){
1017 case '%':
1018 for(m = cur == &top ? first : cur->next; m != nil; m = m->next){
1019 if(rawsearch(m, prog))
1020 break;
1022 break;
1023 case '/':
1024 for(m = cur == &top ? first : cur->next; m != nil; m = m->next){
1025 snprintheader(buf, sizeof(buf), m);
1026 if(regexec(prog, buf, nil, 0))
1027 break;
1029 break;
1030 case '?':
1031 for(m = cur == &top ? nil : cur->prev; m != nil; m = m->prev){
1032 snprintheader(buf, sizeof(buf), m);
1033 if(regexec(prog, buf, nil, 0))
1034 break;
1036 break;
1038 if(m == nil)
1039 return "search";
1040 *mp = m;
1041 free(prog);
1042 break;
1043 case '$':
1044 for(m = first; m != nil && m->next != nil; m = m->next)
1046 *mp = m;
1047 *pp = p+1;
1048 break;
1049 case '.':
1050 *mp = cur;
1051 *pp = p+1;
1052 break;
1053 case ',':
1054 *mp = first;
1055 *pp = p;
1056 break;
1059 if(*mp != nil && **pp == '.'){
1060 (*pp)++;
1061 if((*mp)->child == nil)
1062 return "no sub parts";
1063 return parseaddr(pp, (*mp)->child, (*mp)->child, (*mp)->child, mp);
1065 if(**pp == '+' || **pp == '-' || **pp == '/' || **pp == '%')
1066 return parseaddr(pp, first, *mp, *mp, mp);
1068 return nil;
1071 /* */
1072 /* search a message for a regular expression match */
1073 /* */
1074 int
1075 rawsearch(Message *m, Reprog *prog)
1077 char buf[4096+1];
1078 int i, rv;
1079 CFid *fd;
1080 String *path;
1082 path = extendpath(m->path, "raw");
1083 fd = fsopen(mailfs, s_to_c(path), OREAD);
1084 if(fd == nil)
1085 return 0;
1087 /* march through raw message 4096 bytes at a time */
1088 /* with a 128 byte overlap to chain the re search. */
1089 rv = 0;
1090 for(;;){
1091 i = fsread(fd, buf, sizeof(buf)-1);
1092 if(i <= 0)
1093 break;
1094 buf[i] = 0;
1095 if(regexec(prog, buf, nil, 0)){
1096 rv = 1;
1097 break;
1099 if(i < sizeof(buf)-1)
1100 break;
1101 if(fsseek(fd, -128LL, 1) < 0)
1102 break;
1105 fsclose(fd);
1106 s_free(path);
1107 return rv;
1111 char*
1112 parsecmd(char *p, Cmd *cmd, Message *first, Message *cur)
1114 Reprog *prog;
1115 Message *m, *s, *e, **l, *last;
1116 char buf[256];
1117 char *err;
1118 int i, c;
1119 char *q;
1120 static char errbuf[Errlen];
1122 cmd->delete = 0;
1123 l = &cmd->msgs;
1124 *l = nil;
1126 /* eat white space */
1127 while(*p == ' ')
1128 p++;
1130 /* null command is a special case (advance and print) */
1131 if(*p == 0){
1132 if(cur == &top){
1133 /* special case */
1134 m = first;
1135 } else {
1136 /* walk to the next message even if we have to go up */
1137 m = cur->next;
1138 while(m == nil && cur->parent != nil){
1139 cur = cur->parent;
1140 m = cur->next;
1143 if(m == nil)
1144 return "address";
1145 *l = m;
1146 m->cmd = nil;
1147 cmd->an = 0;
1148 cmd->f = pcmd;
1149 return nil;
1152 /* global search ? */
1153 if(*p == 'g'){
1154 p++;
1156 /* no search string means all messages */
1157 if(*p != '/' && *p != '%'){
1158 for(m = first; m != nil; m = m->next){
1159 *l = m;
1160 l = &m->cmd;
1161 *l = nil;
1163 } else {
1164 /* mark all messages matching this search string */
1165 c = *p;
1166 prog = parsesearch(&p);
1167 if(prog == nil)
1168 return "badly formed regular expression";
1169 if(c == '%'){
1170 for(m = first; m != nil; m = m->next){
1171 if(rawsearch(m, prog)){
1172 *l = m;
1173 l = &m->cmd;
1174 *l = nil;
1177 } else {
1178 for(m = first; m != nil; m = m->next){
1179 snprintheader(buf, sizeof(buf), m);
1180 if(regexec(prog, buf, nil, 0)){
1181 *l = m;
1182 l = &m->cmd;
1183 *l = nil;
1187 free(prog);
1189 } else {
1191 /* parse an address */
1192 s = e = nil;
1193 err = parseaddr(&p, first, cur, cur, &s);
1194 if(err != nil)
1195 return err;
1196 if(*p == ','){
1197 /* this is an address range */
1198 if(s == &top)
1199 s = first;
1200 p++;
1201 for(last = s; last != nil && last->next != nil; last = last->next)
1203 err = parseaddr(&p, first, cur, last, &e);
1204 if(err != nil)
1205 return err;
1207 /* select all messages in the range */
1208 for(; s != nil; s = s->next){
1209 *l = s;
1210 l = &s->cmd;
1211 *l = nil;
1212 if(s == e)
1213 break;
1215 if(s == nil)
1216 return "null address range";
1217 } else {
1218 /* single address */
1219 if(s != &top){
1220 *l = s;
1221 s->cmd = nil;
1226 /* insert a space after '!'s and '|'s */
1227 for(q = p; *q; q++)
1228 if(*q != '!' && *q != '|')
1229 break;
1230 if(q != p && *q != ' '){
1231 memmove(q+1, q, strlen(q)+1);
1232 *q = ' ';
1235 cmd->an = getfields(p, cmd->av, nelem(cmd->av) - 1, 1, " \t\r\n");
1236 if(cmd->an == 0 || *cmd->av[0] == 0)
1237 cmd->f = pcmd;
1238 else {
1239 /* hack to allow all messages to start with 'd' */
1240 if(*(cmd->av[0]) == 'd' && *(cmd->av[0]+1) != 0){
1241 cmd->delete = 1;
1242 cmd->av[0]++;
1245 /* search command table */
1246 for(i = 0; cmdtab[i].cmd != nil; i++)
1247 if(strcmp(cmd->av[0], cmdtab[i].cmd) == 0)
1248 break;
1249 if(cmdtab[i].cmd == nil)
1250 return "illegal command";
1251 if(cmdtab[i].args == 0 && cmd->an > 1){
1252 snprint(errbuf, sizeof(errbuf), "%s doesn't take an argument", cmdtab[i].cmd);
1253 return errbuf;
1255 cmd->f = cmdtab[i].f;
1257 return nil;
1260 /* inefficient read from standard input */
1261 char*
1262 readline(char *prompt, char *line, int len)
1264 char *p, *e;
1265 int n;
1267 retry:
1268 interrupted = 0;
1269 Bprint(&out, "%s", prompt);
1270 Bflush(&out);
1271 e = line + len;
1272 for(p = line; p < e; p++){
1273 n = read(0, p, 1);
1274 if(n < 0){
1275 if(interrupted)
1276 goto retry;
1277 return nil;
1279 if(n == 0)
1280 return nil;
1281 if(*p == '\n')
1282 break;
1284 *p = 0;
1285 return line;
1288 void
1289 messagecount(Message *m)
1291 int i;
1293 i = 0;
1294 for(; m != nil; m = m->next)
1295 i++;
1296 Bprint(&out, "%d message%s\n", i, plural(i));
1299 Message*
1300 aichcmd(Message *m, int indent)
1302 char hdr[256];
1304 if(m == &top)
1305 return nil;
1307 snprintHeader(hdr, sizeof(hdr), indent, m);
1308 Bprint(&out, "%s\n", hdr);
1309 for(m = m->child; m != nil; m = m->next)
1310 aichcmd(m, indent+1);
1311 return nil;
1314 Message*
1315 Hcmd(Cmd *x, Message *m)
1317 USED(x);
1319 if(m == &top)
1320 return nil;
1321 aichcmd(m, 0);
1322 return nil;
1325 Message*
1326 hcmd(Cmd *x, Message *m)
1328 char hdr[256];
1330 USED(x);
1331 if(m == &top)
1332 return nil;
1334 snprintheader(hdr, sizeof(hdr), m);
1335 Bprint(&out, "%s\n", hdr);
1336 return nil;
1339 Message*
1340 bcmd(Cmd *x, Message *m)
1342 int i;
1343 Message *om = m;
1345 USED(x);
1346 if(m == &top)
1347 m = top.child;
1348 for(i = 0; i < 10 && m != nil; i++){
1349 hcmd(nil, m);
1350 om = m;
1351 m = m->next;
1354 return om;
1357 Message*
1358 ncmd(Cmd *x, Message *m)
1360 USED(x);
1361 if(m == &top)
1362 return m->child;
1363 return m->next;
1366 int
1367 printpart(String *s, char *part)
1369 char buf[4096];
1370 int n, tot;
1371 CFid *fd;
1372 String *path;
1374 path = extendpath(s, part);
1375 fd = fsopen(mailfs, s_to_c(path), OREAD);
1376 s_free(path);
1377 if(fd == nil){
1378 fprint(2, "!message dissappeared\n");
1379 return 0;
1381 tot = 0;
1382 while((n = fsread(fd, buf, sizeof(buf))) > 0){
1383 if(interrupted)
1384 break;
1385 if(Bwrite(&out, buf, n) <= 0)
1386 break;
1387 tot += n;
1389 fsclose(fd);
1390 return tot;
1393 int
1394 printhtml(Message *m)
1396 Cmd c;
1398 c.an = 3;
1399 c.av[1] = "htmlfmt";
1400 c.av[2] = "-l 40 -cutf-8";
1401 Bprint(&out, "!%s\n", c.av[1]);
1402 Bflush(&out);
1403 pipecmd(&c, m);
1404 return 0;
1407 Message*
1408 Pcmd(Cmd *x, Message *m)
1410 USED(x);
1411 if(m == &top)
1412 return &top;
1413 if(m->parent == &top)
1414 printpart(m->path, "unixheader");
1415 printpart(m->path, "raw");
1416 return m;
1419 void
1420 compress(char *p)
1422 char *np;
1423 int last;
1425 last = ' ';
1426 for(np = p; *p; p++){
1427 if(*p != ' ' || last != ' '){
1428 last = *p;
1429 *np++ = last;
1432 *np = 0;
1435 Message*
1436 pcmd(Cmd *x, Message *m)
1438 Message *nm;
1439 Ctype *cp;
1440 String *s;
1441 char buf[128];
1443 USED(x);
1444 if(m == &top)
1445 return &top;
1446 if(m->parent == &top)
1447 printpart(m->path, "unixheader");
1448 if(printpart(m->path, "header") > 0)
1449 Bprint(&out, "\n");
1450 cp = findctype(m);
1451 if(cp->display){
1452 if(strcmp(m->type, "text/html") == 0)
1453 printhtml(m);
1454 else
1455 printpart(m->path, "body");
1456 } else if(strcmp(m->type, "multipart/alternative") == 0){
1457 for(nm = m->child; nm != nil; nm = nm->next){
1458 cp = findctype(nm);
1459 if(cp->ext != nil && strncmp(cp->ext, "txt", 3) == 0)
1460 break;
1462 if(nm == nil)
1463 for(nm = m->child; nm != nil; nm = nm->next){
1464 cp = findctype(nm);
1465 if(cp->display)
1466 break;
1468 if(nm != nil)
1469 pcmd(nil, nm);
1470 else
1471 hcmd(nil, m);
1472 } else if(strncmp(m->type, "multipart/", 10) == 0){
1473 nm = m->child;
1474 if(nm != nil){
1475 /* always print first part */
1476 pcmd(nil, nm);
1478 for(nm = nm->next; nm != nil; nm = nm->next){
1479 s = rooted(s_clone(nm->path));
1480 cp = findctype(nm);
1481 snprintHeader(buf, sizeof buf, -1, nm);
1482 compress(buf);
1483 if(strcmp(nm->disposition, "inline") == 0){
1484 if(cp->ext != nil)
1485 Bprint(&out, "\n--- %s %s/body.%s\n\n",
1486 buf, s_to_c(s), cp->ext);
1487 else
1488 Bprint(&out, "\n--- %s %s/body\n\n",
1489 buf, s_to_c(s));
1490 pcmd(nil, nm);
1491 } else {
1492 if(cp->ext != nil)
1493 Bprint(&out, "\n!--- %s %s/body.%s\n",
1494 buf, s_to_c(s), cp->ext);
1495 else
1496 Bprint(&out, "\n!--- %s %s/body\n",
1497 buf, s_to_c(s));
1499 s_free(s);
1501 } else {
1502 hcmd(nil, m);
1504 } else if(strcmp(m->type, "message/rfc822") == 0){
1505 pcmd(nil, m->child);
1506 } else if(plumb(m, cp) >= 0)
1507 Bprint(&out, "\n!--- using plumber to display message of type %s\n", m->type);
1508 else
1509 Bprint(&out, "\n!--- cannot display messages of type %s\n", m->type);
1511 return m;
1514 void
1515 printpartindented(String *s, char *part, char *indent)
1517 int fd;
1518 char *p;
1519 String *path;
1520 Biobuf *b;
1522 path = extendpath(s, part);
1523 fd = fsopenfd(mailfs, s_to_c(path), OREAD);
1524 s_free(path);
1525 if(fd < 0){
1526 fprint(2, "!message disappeared\n");
1527 return;
1529 b = Bfdopen(fd, OREAD);
1530 if(b == 0){
1531 fprint(2, "out of memory\n");
1532 close(fd);
1533 return;
1535 while((p = Brdline(b, '\n')) != nil){
1536 if(interrupted)
1537 break;
1538 p[Blinelen(b)-1] = 0;
1539 if(Bprint(&out, "%s%s\n", indent, p) < 0)
1540 break;
1542 Bprint(&out, "\n");
1543 Bterm(b);
1546 Message*
1547 quotecmd(Cmd *x, Message *m)
1549 Message *nm;
1550 Ctype *cp;
1552 USED(x);
1553 if(m == &top)
1554 return &top;
1555 Bprint(&out, "\n");
1556 if(m->from != nil && *m->from)
1557 Bprint(&out, "On %s, %s wrote:\n", m->date, m->from);
1558 cp = findctype(m);
1559 if(cp->display){
1560 printpartindented(m->path, "body", "> ");
1561 } else if(strcmp(m->type, "multipart/alternative") == 0){
1562 for(nm = m->child; nm != nil; nm = nm->next){
1563 cp = findctype(nm);
1564 if(cp->ext != nil && strncmp(cp->ext, "txt", 3) == 0)
1565 break;
1567 if(nm == nil)
1568 for(nm = m->child; nm != nil; nm = nm->next){
1569 cp = findctype(nm);
1570 if(cp->display)
1571 break;
1573 if(nm != nil)
1574 quotecmd(nil, nm);
1575 } else if(strncmp(m->type, "multipart/", 10) == 0){
1576 nm = m->child;
1577 if(nm != nil){
1578 cp = findctype(nm);
1579 if(cp->display || strncmp(m->type, "multipart/", 10) == 0)
1580 quotecmd(nil, nm);
1583 return m;
1586 /* really delete messages */
1587 Message*
1588 flushdeleted(Message *cur)
1590 Message *m, **l;
1591 char buf[1024], *p, *e, *msg;
1592 int deld, n;
1593 CFid *fd;
1594 int i;
1596 doflush = 0;
1597 deld = 0;
1599 snprint(buf, sizeof buf, "%s/ctl", mbname);
1600 fd = fsopen(mailfs, buf, OWRITE);
1601 if(fd == nil){
1602 fprint(2, "!can't delete mail, opening %s: %r\n", buf);
1603 exitfs(0);
1605 e = &buf[sizeof(buf)];
1606 p = seprint(buf, e, "delete");
1607 n = 0;
1608 for(l = &top.child; *l != nil;){
1609 m = *l;
1610 if(!m->deleted){
1611 l = &(*l)->next;
1612 continue;
1615 /* don't return a pointer to a deleted message */
1616 if(m == cur)
1617 cur = m->next;
1619 deld++;
1620 msg = strrchr(s_to_c(m->path), '/');
1621 if(msg == nil)
1622 msg = s_to_c(m->path);
1623 else
1624 msg++;
1625 if(e-p < 10){
1626 fswrite(fd, buf, p-buf);
1627 n = 0;
1628 p = seprint(buf, e, "delete");
1630 p = seprint(p, e, " %s", msg);
1631 n++;
1633 /* unchain and free */
1634 *l = m->next;
1635 if(m->next)
1636 m->next->prev = m->prev;
1637 freemessage(m);
1639 if(n)
1640 fswrite(fd, buf, p-buf);
1642 fsclose(fd);
1644 if(deld)
1645 Bprint(&out, "!%d message%s deleted\n", deld, plural(deld));
1647 /* renumber */
1648 i = 1;
1649 for(m = top.child; m != nil; m = m->next)
1650 m->id = natural ? m->fileno : i++;
1652 /* if we're out of messages, go back to first */
1653 /* if no first, return the fake first */
1654 if(cur == nil){
1655 if(top.child)
1656 return top.child;
1657 else
1658 return &top;
1660 return cur;
1663 Message*
1664 qcmd(Cmd *x, Message *m)
1666 USED(x);
1667 USED(m);
1669 flushdeleted(nil);
1671 if(didopen)
1672 closemb();
1673 Bflush(&out);
1675 exitfs(0);
1676 return nil; /* not reached */
1679 Message*
1680 ycmd(Cmd *x, Message *m)
1682 USED(x);
1684 doflush = 1;
1686 return icmd(nil, m);
1689 Message*
1690 xcmd(Cmd *x, Message *m)
1692 USED(x);
1693 USED(m);
1695 exitfs(0);
1696 return nil; /* not reached */
1699 Message*
1700 eqcmd(Cmd *x, Message *m)
1702 USED(x);
1704 if(m == &top)
1705 Bprint(&out, "0\n");
1706 else
1707 Bprint(&out, "%d\n", m->id);
1708 return nil;
1711 Message*
1712 dcmd(Cmd *x, Message *m)
1714 USED(x);
1716 if(m == &top){
1717 Bprint(&out, "!address\n");
1718 return nil;
1720 while(m->parent != &top)
1721 m = m->parent;
1722 m->deleted = 1;
1723 return m;
1726 Message*
1727 ucmd(Cmd *x, Message *m)
1729 USED(x);
1731 if(m == &top)
1732 return nil;
1733 while(m->parent != &top)
1734 m = m->parent;
1735 if(m->deleted < 0)
1736 Bprint(&out, "!can't undelete, already flushed\n");
1737 m->deleted = 0;
1738 return m;
1742 Message*
1743 icmd(Cmd *x, Message *m)
1745 int n;
1746 char buf[1024];
1747 CFid *fd;
1749 USED(x);
1750 snprint(buf, sizeof buf, "%s/ctl", mbname);
1751 fd = fsopen(mailfs, buf, OWRITE);
1752 if(fd){
1753 fswrite(fd, "refresh", 7);
1754 fsclose(fd);
1756 n = dir2message(&top, reverse);
1757 if(n > 0)
1758 Bprint(&out, "%d new message%s\n", n, plural(n));
1759 return m;
1762 Message*
1763 helpcmd(Cmd *x, Message *m)
1765 int i;
1767 USED(x);
1768 Bprint(&out, "Commands are of the form [<range>] <command> [args]\n");
1769 Bprint(&out, "<range> := <addr> | <addr>','<addr>| 'g'<search>\n");
1770 Bprint(&out, "<addr> := '.' | '$' | '^' | <number> | <search> | <addr>'+'<addr> | <addr>'-'<addr>\n");
1771 Bprint(&out, "<search> := '/'<regexp>'/' | '?'<regexp>'?' | '%%'<regexp>'%%'\n");
1772 Bprint(&out, "<command> :=\n");
1773 for(i = 0; cmdtab[i].cmd != nil; i++)
1774 Bprint(&out, "%s\n", cmdtab[i].help);
1775 return m;
1778 int
1779 tomailer(char **av)
1781 static char *marshal;
1782 Waitmsg *w;
1783 int pid, i;
1785 if(marshal == nil)
1786 marshal = unsharp("#9/bin/upas/marshal");
1788 /* start the mailer and get out of the way */
1789 switch(pid = fork()){
1790 case -1:
1791 fprint(2, "can't fork: %r\n");
1792 return -1;
1793 case 0:
1794 Bprint(&out, "!%s", marshal);
1795 for(i = 1; av[i]; i++){
1796 if(strchr(av[i], ' ') != nil)
1797 Bprint(&out, " '%s'", av[i]);
1798 else
1799 Bprint(&out, " %s", av[i]);
1801 Bprint(&out, "\n");
1802 Bflush(&out);
1803 av[0] = "marshal";
1804 chdir(wd);
1805 exec(marshal, av);
1806 fprint(2, "couldn't exec %s\n", marshal);
1807 threadexits(0);
1808 default:
1809 w = wait();
1810 if(w == nil){
1811 if(interrupted)
1812 postnote(PNPROC, pid, "die");
1813 waitpid();
1814 return -1;
1816 if(w->msg[0]){
1817 fprint(2, "mailer failed: %s\n", w->msg);
1818 free(w);
1819 return -1;
1821 free(w);
1822 Bprint(&out, "!\n");
1823 break;
1825 return 0;
1828 /* */
1829 /* like tokenize but obey "" quoting */
1830 /* */
1831 int
1832 tokenize822(char *str, char **args, int max)
1834 int na;
1835 int intok = 0, inquote = 0;
1837 if(max <= 0)
1838 return 0;
1839 for(na=0; ;str++)
1840 switch(*str) {
1841 case ' ':
1842 case '\t':
1843 if(inquote)
1844 goto Default;
1845 /* fall through */
1846 case '\n':
1847 *str = 0;
1848 if(!intok)
1849 continue;
1850 intok = 0;
1851 if(na < max)
1852 continue;
1853 /* fall through */
1854 case 0:
1855 return na;
1856 case '"':
1857 inquote ^= 1;
1858 /* fall through */
1859 Default:
1860 default:
1861 if(intok)
1862 continue;
1863 args[na++] = str;
1864 intok = 1;
1866 return 0; /* can't get here; silence compiler */
1869 Message*
1870 rcmd(Cmd *c, Message *m)
1872 char *av[128];
1873 int i, ai = 1;
1874 Message *nm;
1875 char *addr;
1876 String *path = nil;
1877 String *rpath;
1878 String *subject = nil;
1879 String *from;
1881 if(m == &top){
1882 Bprint(&out, "!address\n");
1883 return nil;
1886 addr = nil;
1887 for(nm = m; nm != &top; nm = nm->parent){
1888 if(*nm->replyto != 0){
1889 addr = nm->replyto;
1890 break;
1893 if(addr == nil){
1894 Bprint(&out, "!no reply address\n");
1895 return nil;
1898 if(nm == &top){
1899 print("!noone to reply to\n");
1900 return nil;
1903 for(nm = m; nm != &top; nm = nm->parent){
1904 if(*nm->subject){
1905 av[ai++] = "-s";
1906 subject = addrecolon(nm->subject);
1907 av[ai++] = s_to_c(subject);;
1908 break;
1912 av[ai++] = "-R";
1913 rpath = rooted(s_clone(m->path));
1914 av[ai++] = s_to_c(rpath);
1916 if(strchr(c->av[0], 'f') != nil){
1917 fcmd(c, m);
1918 av[ai++] = "-F";
1921 if(strchr(c->av[0], 'R') != nil){
1922 av[ai++] = "-t";
1923 av[ai++] = "message/rfc822";
1924 av[ai++] = "-A";
1925 path = rooted(extendpath(m->path, "raw"));
1926 av[ai++] = s_to_c(path);
1929 for(i = 1; i < c->an && ai < nelem(av)-1; i++)
1930 av[ai++] = c->av[i];
1931 from = s_copy(addr);
1932 ai += tokenize822(s_to_c(from), &av[ai], nelem(av) - ai);
1933 av[ai] = 0;
1934 if(tomailer(av) < 0)
1935 m = nil;
1936 s_free(path);
1937 s_free(rpath);
1938 s_free(subject);
1939 s_free(from);
1940 return m;
1943 Message*
1944 mcmd(Cmd *c, Message *m)
1946 char **av;
1947 int i, ai;
1948 String *path;
1950 if(m == &top){
1951 Bprint(&out, "!address\n");
1952 return nil;
1955 if(c->an < 2){
1956 fprint(2, "!usage: M list-of addresses\n");
1957 return nil;
1960 ai = 1;
1961 av = malloc(sizeof(char*)*(c->an + 8));
1963 av[ai++] = "-t";
1964 if(m->parent == &top)
1965 av[ai++] = "message/rfc822";
1966 else
1967 av[ai++] = "mime";
1969 av[ai++] = "-A";
1970 path = rooted(extendpath(m->path, "raw"));
1971 av[ai++] = s_to_c(path);
1973 if(strchr(c->av[0], 'M') == nil)
1974 av[ai++] = "-n";
1976 for(i = 1; i < c->an; i++)
1977 av[ai++] = c->av[i];
1978 av[ai] = 0;
1980 if(tomailer(av) < 0)
1981 m = nil;
1982 if(path != nil)
1983 s_free(path);
1984 free(av);
1985 return m;
1988 Message*
1989 acmd(Cmd *c, Message *m)
1991 char *av[128];
1992 int i, ai;
1993 String *from, *to, *cc, *path = nil, *subject = nil;
1995 if(m == &top){
1996 Bprint(&out, "!address\n");
1997 return nil;
2000 ai = 1;
2001 if(*m->subject){
2002 av[ai++] = "-s";
2003 subject = addrecolon(m->subject);
2004 av[ai++] = s_to_c(subject);
2007 if(strchr(c->av[0], 'A') != nil){
2008 av[ai++] = "-t";
2009 av[ai++] = "message/rfc822";
2010 av[ai++] = "-A";
2011 path = rooted(extendpath(m->path, "raw"));
2012 av[ai++] = s_to_c(path);
2015 for(i = 1; i < c->an && ai < nelem(av)-1; i++)
2016 av[ai++] = c->av[i];
2017 from = s_copy(m->from);
2018 ai += tokenize822(s_to_c(from), &av[ai], nelem(av) - ai);
2019 to = s_copy(m->to);
2020 ai += tokenize822(s_to_c(to), &av[ai], nelem(av) - ai);
2021 cc = s_copy(m->cc);
2022 ai += tokenize822(s_to_c(cc), &av[ai], nelem(av) - ai);
2023 av[ai] = 0;
2024 if(tomailer(av) < 0)
2025 return nil;
2026 s_free(from);
2027 s_free(to);
2028 s_free(cc);
2029 s_free(subject);
2030 s_free(path);
2031 return m;
2034 String *
2035 relpath(char *path, String *to)
2037 if (*path=='/' || strncmp(path, "./", 2) == 0
2038 || strncmp(path, "../", 3) == 0) {
2039 to = s_append(to, path);
2040 } else if(mbpath) {
2041 to = s_append(to, s_to_c(mbpath));
2042 to->ptr = strrchr(to->base, '/')+1;
2043 s_append(to, path);
2045 return to;
2048 int
2049 appendtofile(Message *m, char *part, char *base, int mbox)
2051 String *file, *h;
2052 int in, out, rv;
2054 file = extendpath(m->path, part);
2055 in = open(s_to_c(file), OREAD);
2056 if(in < 0){
2057 fprint(2, "!message disappeared\n");
2058 return -1;
2061 s_reset(file);
2063 relpath(base, file);
2064 if(sysisdir(s_to_c(file))){
2065 s_append(file, "/");
2066 if(m->filename && strchr(m->filename, '/') == nil)
2067 s_append(file, m->filename);
2068 else {
2069 s_append(file, "att.XXXXXXXXXXX");
2070 mktemp(s_to_c(file));
2073 if(mbox)
2074 out = open(s_to_c(file), OWRITE);
2075 else
2076 out = open(s_to_c(file), OWRITE|OTRUNC);
2077 if(out < 0){
2078 out = create(s_to_c(file), OWRITE, 0666);
2079 if(out < 0){
2080 fprint(2, "!can't open %s: %r\n", s_to_c(file));
2081 close(in);
2082 s_free(file);
2083 return -1;
2086 if(mbox)
2087 seek(out, 0, 2);
2089 /* put on a 'From ' line */
2090 if(mbox){
2091 while(m->parent != &top)
2092 m = m->parent;
2093 h = file2string(m->path, "unixheader");
2094 fprint(out, "%s", s_to_c(h));
2095 s_free(h);
2098 /* copy the message escaping what we have to ad adding newlines if we have to */
2099 if(mbox)
2100 rv = appendfiletombox(in, out);
2101 else
2102 rv = appendfiletofile(in, out);
2104 close(in);
2105 close(out);
2107 if(rv >= 0)
2108 print("!saved in %s\n", s_to_c(file));
2109 s_free(file);
2110 return rv;
2113 Message*
2114 scmd(Cmd *c, Message *m)
2116 char buf[256];
2117 CFid *fd;
2118 char *file, *msg;
2120 if(m == &top){
2121 Bprint(&out, "!address\n");
2122 return nil;
2125 switch(c->an){
2126 case 1:
2127 file = "stored";
2128 break;
2129 case 2:
2130 file = c->av[1];
2131 break;
2132 default:
2133 fprint(2, "!usage: s filename\n");
2134 return nil;
2137 if(file[0] == '/' || (file[0]=='.' && file[1]=='/')){
2138 if(appendtofile(m, "raw", file, 1) < 0)
2139 return nil;
2140 }else{
2141 snprint(buf, sizeof buf, "%s/ctl", mbname);
2142 if((fd = fsopen(mailfs, buf, OWRITE)) == nil)
2143 return nil;
2144 msg = strrchr(s_to_c(m->path), '/');
2145 if(msg == nil)
2146 msg = s_to_c(m->path);
2147 else
2148 msg++;
2149 if(fsprint(fd, "save %s %s", file, msg) < 0){
2150 fsclose(fd);
2151 return nil;
2153 fsclose(fd);
2155 m->stored = 1;
2156 return m;
2159 Message*
2160 wcmd(Cmd *c, Message *m)
2162 char *file;
2164 if(m == &top){
2165 Bprint(&out, "!address\n");
2166 return nil;
2169 switch(c->an){
2170 case 2:
2171 file = c->av[1];
2172 break;
2173 case 1:
2174 if(*m->filename == 0){
2175 fprint(2, "!usage: w filename\n");
2176 return nil;
2178 file = strrchr(m->filename, '/');
2179 if(file != nil)
2180 file++;
2181 else
2182 file = m->filename;
2183 break;
2184 default:
2185 fprint(2, "!usage: w filename\n");
2186 return nil;
2189 if(appendtofile(m, "body", file, 0) < 0)
2190 return nil;
2191 m->stored = 1;
2192 return m;
2195 char *specialfile[] =
2197 "pipeto",
2198 "pipefrom",
2199 "L.mbox",
2200 "forward",
2201 "names"
2204 /* return 1 if this is a special file */
2205 static int
2206 special(String *s)
2208 char *p;
2209 int i;
2211 p = strrchr(s_to_c(s), '/');
2212 if(p == nil)
2213 p = s_to_c(s);
2214 else
2215 p++;
2216 for(i = 0; i < nelem(specialfile); i++)
2217 if(strcmp(p, specialfile[i]) == 0)
2218 return 1;
2219 return 0;
2222 /* open the folder using the recipients account name */
2223 static String*
2224 foldername(char *rcvr)
2226 char *p;
2227 int c;
2228 String *file;
2229 Dir *d;
2230 int scarey;
2232 file = s_new();
2233 mboxpath("f", user, file, 0);
2234 d = dirstat(s_to_c(file));
2236 /* if $mail/f exists, store there, otherwise in $mail */
2237 s_restart(file);
2238 if(d && d->qid.type == QTDIR){
2239 scarey = 0;
2240 s_append(file, "f/");
2241 } else {
2242 scarey = 1;
2244 free(d);
2246 p = strrchr(rcvr, '!');
2247 if(p != nil)
2248 rcvr = p+1;
2250 while(*rcvr && *rcvr != '@'){
2251 c = *rcvr++;
2252 if(c == '/')
2253 c = '_';
2254 s_putc(file, c);
2256 s_terminate(file);
2258 if(scarey && special(file)){
2259 fprint(2, "!won't overwrite %s\n", s_to_c(file));
2260 s_free(file);
2261 return nil;
2264 return file;
2267 Message*
2268 fcmd(Cmd *c, Message *m)
2270 String *folder;
2272 if(c->an > 1){
2273 fprint(2, "!usage: f takes no arguments\n");
2274 return nil;
2277 if(m == &top){
2278 Bprint(&out, "!address\n");
2279 return nil;
2282 folder = foldername(m->from);
2283 if(folder == nil)
2284 return nil;
2286 if(appendtofile(m, "raw", s_to_c(folder), 1) < 0){
2287 s_free(folder);
2288 return nil;
2290 s_free(folder);
2292 m->stored = 1;
2293 return m;
2296 void
2297 system(char *cmd, char **av, int in)
2299 int pid;
2301 switch(pid=fork()){
2302 case -1:
2303 return;
2304 case 0:
2305 if(strcmp(cmd, "rc") == 0)
2306 cmd = unsharp("#9/bin/rc");
2307 if(in >= 0){
2308 close(0);
2309 dup(in, 0);
2310 close(in);
2312 if(wd[0] != 0)
2313 chdir(wd);
2314 exec(cmd, av);
2315 fprint(2, "!couldn't exec %s\n", cmd);
2316 threadexits(0);
2317 default:
2318 if(in >= 0)
2319 close(in);
2320 while(waitpid() < 0){
2321 if(!interrupted)
2322 break;
2323 postnote(PNPROC, pid, "die");
2324 continue;
2326 break;
2330 Message*
2331 bangcmd(Cmd *c, Message *m)
2333 char cmd[4*1024];
2334 char *p, *e;
2335 char *av[4];
2336 int i;
2338 cmd[0] = 0;
2339 p = cmd;
2340 e = cmd+sizeof(cmd);
2341 for(i = 1; i < c->an; i++)
2342 p = seprint(p, e, "%s ", c->av[i]);
2343 av[0] = "rc";
2344 av[1] = "-c";
2345 av[2] = cmd;
2346 av[3] = 0;
2347 system("rc", av, -1);
2348 Bprint(&out, "!\n");
2349 return m;
2352 Message*
2353 xpipecmd(Cmd *c, Message *m, char *part)
2355 char cmd[128];
2356 char *p, *e;
2357 char *av[4];
2358 String *path;
2359 int i, fd;
2361 if(c->an < 2){
2362 Bprint(&out, "!usage: | cmd\n");
2363 return nil;
2366 if(m == &top){
2367 Bprint(&out, "!address\n");
2368 return nil;
2371 path = extendpath(m->path, part);
2372 fd = fsopenfd(mailfs, s_to_c(path), OREAD);
2373 s_free(path);
2375 if(fd < 0){ /* compatibility with older upas/fs */
2376 path = extendpath(m->path, "raw");
2377 fd = fsopenfd(mailfs, s_to_c(path), OREAD);
2378 s_free(path);
2380 if(fd < 0){
2381 fprint(2, "!message disappeared\n");
2382 return nil;
2385 p = cmd;
2386 e = cmd+sizeof(cmd);
2387 cmd[0] = 0;
2388 for(i = 1; i < c->an; i++)
2389 p = seprint(p, e, "%s ", c->av[i]);
2390 av[0] = "rc";
2391 av[1] = "-c";
2392 av[2] = cmd;
2393 av[3] = 0;
2394 system("rc", av, fd); /* system closes fd */
2395 Bprint(&out, "!\n");
2396 return m;
2399 Message*
2400 pipecmd(Cmd *c, Message *m)
2402 return xpipecmd(c, m, "body");
2405 Message*
2406 rpipecmd(Cmd *c, Message *m)
2408 return xpipecmd(c, m, "rawunix");
2411 void
2412 closemb(void)
2414 CFid *fd;
2416 fd = fsopen(mailfs, "ctl", OWRITE);
2417 if(fd == nil)
2418 sysfatal("can't open ctl: %r");
2420 /* close current mailbox */
2421 if(*mbname && strcmp(mbname, "mbox") != 0)
2422 fsprint(fd, "close %s", mbname);
2424 fsclose(fd);
2427 int
2428 switchmb(char *file, char *singleton)
2430 char *p;
2431 int n, fd;
2432 String *path;
2433 char buf[256];
2435 /* if the user didn't say anything and there */
2436 /* is an mbox mounted already, use that one */
2437 /* so that the upas/fs -fdefault default is honored. */
2438 if(0 && (file || (singleton && fsaccess(mailfs, singleton, 0) < 0))){
2439 /* XXX all wrong */
2440 fprint(2, "file=%s singleton=%s\n", file, singleton);
2441 if(file == nil)
2442 file = "mbox";
2444 /* close current mailbox */
2445 closemb();
2446 didopen = 1;
2448 fd = open("/mail/fs/ctl", ORDWR);
2449 if(fd < 0)
2450 sysfatal("can't open /mail/fs/ctl: %r");
2452 path = s_new();
2454 /* get an absolute path to the mail box */
2455 if(strncmp(file, "./", 2) == 0){
2456 /* resolve path here since upas/fs doesn't know */
2457 /* our working directory */
2458 if(getwd(buf, sizeof(buf)-strlen(file)) == nil){
2459 fprint(2, "!can't get working directory: %s\n", buf);
2460 return -1;
2462 s_append(path, buf);
2463 s_append(path, file+1);
2464 } else {
2465 mboxpath(file, user, path, 0);
2468 /* make up a handle to use when talking to fs */
2469 p = strrchr(file, '/');
2470 if(p == nil){
2471 /* if its in the mailbox directory, just use the name */
2472 strncpy(mbname, file, sizeof(mbname));
2473 mbname[sizeof(mbname)-1] = 0;
2474 } else {
2475 /* make up a mailbox name */
2476 p = strrchr(s_to_c(path), '/');
2477 p++;
2478 if(*p == 0){
2479 fprint(2, "!bad mbox name");
2480 return -1;
2482 strncpy(mbname, p, sizeof(mbname));
2483 mbname[sizeof(mbname)-1] = 0;
2484 n = strlen(mbname);
2485 if(n > Elemlen-12)
2486 n = Elemlen-12;
2487 sprint(mbname+n, "%ld", time(0));
2490 if(fprint(fd, "open %s %s", s_to_c(path), mbname) < 0){
2491 fprint(2, "!can't 'open %s %s': %r\n", file, mbname);
2492 s_free(path);
2493 return -1;
2495 close(fd);
2496 }else
2497 if (singleton && fsaccess(mailfs, singleton, 0)==0){
2498 if ((p = strchr(singleton, '/')) == nil){
2499 fprint(2, "!bad mbox name");
2500 return -1;
2502 n = p-singleton;
2503 strncpy(mbname, singleton, n);
2504 mbname[n+1] = 0;
2505 path = s_reset(nil);
2506 mboxpath(mbname, user, path, 0);
2507 }else{
2508 if(file)
2509 strecpy(mbname, mbname+sizeof mbname, file);
2510 else
2511 strcpy(mbname, "mbox");
2512 path = s_reset(nil);
2513 mboxpath(mbname, user, path, 0);
2516 snprint(root, sizeof root, "%s", mbname);
2517 rootlen = strlen(root);
2519 if(mbpath != nil)
2520 s_free(mbpath);
2521 mbpath = path;
2522 return 0;
2525 /* like tokenize but for into lines */
2526 int
2527 lineize(char *s, char **f, int n)
2529 int i;
2531 for(i = 0; *s && i < n; i++){
2532 f[i] = s;
2533 s = strchr(s, '\n');
2534 if(s == nil)
2535 break;
2536 *s++ = 0;
2538 return i;
2543 String*
2544 rooted(String *s)
2546 static char buf[256];
2548 if(strcmp(root, ".") != 0)
2549 return s;
2550 snprint(buf, sizeof(buf), "/mail/fs/%s/%s", mbname, s_to_c(s));
2551 s_free(s);
2552 return s_copy(buf);
2555 int
2556 plumb(Message *m, Ctype *cp)
2558 String *s;
2559 Plumbmsg *pm;
2560 static int fd = -2;
2562 if(cp->plumbdest == nil)
2563 return -1;
2565 if(fd < -1)
2566 fd = plumbopen("send", OWRITE);
2567 if(fd < 0)
2568 return -1;
2570 pm = mallocz(sizeof(Plumbmsg), 1);
2571 pm->src = strdup("mail");
2572 if(*cp->plumbdest)
2573 pm->dst = strdup(cp->plumbdest);
2574 pm->wdir = nil;
2575 pm->type = strdup("text");
2576 pm->ndata = -1;
2577 s = rooted(extendpath(m->path, "body"));
2578 if(cp->ext != nil){
2579 s_append(s, ".");
2580 s_append(s, cp->ext);
2582 pm->data = strdup(s_to_c(s));
2583 s_free(s);
2584 plumbsend(fd, pm);
2585 plumbfree(pm);
2586 return 0;
2589 void
2590 regerror(char *x)
2592 USED(x);
2595 String*
2596 addrecolon(char *s)
2598 String *str;
2600 if(cistrncmp(s, "re:", 3) != 0){
2601 str = s_copy("Re: ");
2602 s_append(str, s);
2603 } else
2604 str = s_copy(s);
2605 return str;
2608 void
2609 exitfs(char *rv)
2611 threadexitsall(rv);