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