7 #define system nedsystem
10 typedef struct Message Message;
11 typedef struct Ctype Ctype;
12 typedef struct Cmd Cmd;
35 int fileno; /* number of directory */
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 },
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*);
109 Message* (*f)(Cmd*, Message*);
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" },
150 Message *(*f)(Cmd*, Message*);
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*);
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*);
185 Message* flushdeleted(Message*);
192 fprint(2, "usage: %s [-nr] [-f mailfile] [-s mailfile]\n", argv0);
193 fprint(2, " %s -c dir\n", argv0);
194 threadexitsall("usage");
198 catchnote(void *x, char *note)
202 if(strstr(note, "interrupt") != nil){
219 threadmain(int argc, char **argv)
221 Message *cur, *m, *x;
222 char cmdline[4*1024];
228 char *file, *singleton, *service;
230 Binit(&out, 1, OWRITE);
239 service = EARGF(usage());
245 file = EARGF(usage());
248 singleton = EARGF(usage());
263 if(user == nil || *user == 0)
264 sysfatal("can't read user name");
268 creatembox(user, argv[0]);
270 creatembox(user, nil);
276 if((mailfs = nsmount(service, nil)) == nil)
277 sysfatal("cannot mount %s: %r", service);
279 switchmb(file, singleton);
281 top.path = s_copy(root);
283 for(cp = ctype; cp < ctype+nelem(ctype)-1; cp++)
286 if(singleton != nil){
287 cur = dosingleton(&top, singleton);
289 Bprint(&out, "no message\n");
295 n = dir2message(&top, reverse);
297 sysfatal("can't read %s", s_to_c(top.path));
298 Bprint(&out, "%d message%s\n", n, plural(n));
307 s_append(prompt, ": ");
310 s_append(prompt, ": ");
313 /* leave space at the end of cmd line in case parsecmd needs to */
314 /* add a space after a '|' or '!' */
315 if(readline(s_to_c(prompt), cmdline, sizeof(cmdline)-1) == nil)
317 err = parsecmd(cmdline, &cmd, top.child, cur);
319 Bprint(&out, "!%s\n", err);
322 if(singleton != nil && cmd.f == icmd){
323 Bprint(&out, "!illegal command\n");
327 if(cmd.msgs == nil || cmd.msgs == &top){
328 x = (*cmd.f)(&cmd, &top);
331 } else for(m = cmd.msgs; m != nil; m = m->cmd){
336 /* dp acts differently than all other commands */
337 /* since its an old lesk idiom that people love. */
338 /* it deletes the current message, moves the current */
339 /* pointer ahead one and prints. */
342 Bprint(&out, "!address\n");
349 x = (*cmd.f)(&cmd, x);
354 if(singleton != nil && (cmd.delete || cmd.f == dcmd))
358 cur = flushdeleted(cur);
375 if(*s == ' ' && !inquote)
378 f = malloc(nf*sizeof f[0]);
381 nf = tokenize(t, f, nf);
383 for(i=0; i+1<nf; i+=2){
386 /* if(f[i][0] == 0 || strcmp(f[i], f[i+1]) == 0) */
387 fmtprint(&fmt, "%s", f[i+1]);
389 /* fmtprint(&fmt, "%s <%s>", f[i], f[i+1]); */
392 return fmtstrflush(&fmt);
396 /* read the message info */
399 file2message(Message *parent, char *name)
406 m = mallocz(sizeof(Message), 1);
409 m->path = path = extendpath(parent->path, name);
410 m->fileno = atoi(name);
411 m->info = file2string(path, "info");
421 nf = lineize(s_to_c(m->info), f, nelem(f));
424 t = strchr(f[i], ' ');
429 if(strcmp(s, "from") == 0)
430 m->from = mkaddrs(t);
431 else if(strcmp(s, "to") == 0)
433 else if(strcmp(s, "cc") == 0)
435 else if(strcmp(s, "replyto") == 0)
436 m->replyto = mkaddrs(t);
437 else if(strcmp(s, "unixdate") == 0 && (t=strchr(t, ' ')) != nil)
439 else if(strcmp(s, "subject") == 0)
441 else if(strcmp(s, "type") == 0)
443 else if(strcmp(s, "disposition") == 0)
445 else if(strcmp(s, "filename") == 0)
448 m->len = filelen(path, "raw");
449 if(strstr(m->type, "multipart") != nil || strcmp(m->type, "message/rfc822") == 0)
457 freemessage(Message *m)
461 for(nm = m->child; nm != nil; nm = next){
471 /* read a directory into a list of messages */
474 dir2message(Message *parent, int reverse)
476 int i, n, highest, newmsgs;
480 Message *first, *last, *m;
482 fd = fsopen(mailfs, s_to_c(parent->path), OREAD);
486 /* count current entries */
487 first = parent->child;
488 highest = newmsgs = 0;
489 for(last = parent->child; last != nil && last->next != nil; last = last->next)
490 if(last->fileno > highest)
491 highest = last->fileno;
493 if(last->fileno > highest)
494 highest = last->fileno;
496 n = fsdirreadall(fd, &d);
497 for(i = 0; i < n; i++){
498 if((d[i].qid.type & QTDIR) == 0)
500 if(atoi(d[i].name) <= highest)
502 m = file2message(parent, d[i].name);
503 /* fprint(2,"returned from file2message\n"); */
523 parent->child = first;
525 /* renumber and file longest from */
528 for(m = first; m != nil; m = m->next){
529 m->id = natural ? m->fileno : i++;
539 /* point directly to a message */
542 dosingleton(Message *parent, char *path)
547 /* walk down to message and read it */
548 if(strlen(path) < rootlen)
550 if(path[rootlen] != '/')
556 m = file2message(parent, p);
562 /* walk down to requested component */
565 np = strchr(np+1, '/');
568 for(m = m->child; m != nil; m = m->next)
569 if(strcmp(path, s_to_c(m->path)) == 0)
578 /* read a file into a string */
581 file2string(String *dir, char *file)
587 s = extendpath(dir, file);
588 fd = fsopen(mailfs, s_to_c(s), OREAD);
589 s_grow(s, 512); /* avoid multiple reads on info files */
600 m = fsread(fd, s->ptr, n);
614 /* get the length of a file */
617 filelen(String *dir, char *file)
623 path = extendpath(dir, file);
624 d = fsdirstat(mailfs, s_to_c(path));
636 /* walk the path name an element */
639 extendpath(String *dir, char *name)
643 if(strcmp(s_to_c(dir), ".") == 0)
646 path = s_copy(s_to_c(dir));
649 s_append(path, name);
654 cistrncmp(char *a, char *b, int n)
657 if(tolower(*a++) != tolower(*b++))
664 cistrcmp(char *a, char *b)
667 if(tolower(*a) != tolower(*b++))
683 p = strchr(p+1, ':');
691 "jan", "feb", "mar", "apr", "may", "jun",
692 "jul", "aug", "sep", "oct", "nov", "dec"
700 for(i = 0; i < 12; i++)
701 if(cistrcmp(m, months[i]) == 0)
708 Yearsecs= 365*24*60*60
712 cracktime(char *d, char *out, int len)
724 strncpy(in, d, sizeof(in));
725 in[sizeof(in)-1] = 0;
726 n = getfields(in, f, 6, 1, " \t\r\n");
729 snprint(out, 16, "%10.10s", d);
733 memset(&tm, 0, sizeof tm);
734 if(strchr(f[0], ',') != nil && strchr(f[4], ':') != nil){
736 tm.year = atoi(f[3])-1900;
737 tm.mon = month(f[2]);
738 tm.mday = atoi(f[1]);
739 dtime = nosecs(f[4]);
741 } else if(strchr(f[3], ':') != nil){
743 tm.year = atoi(f[5])-1900;
744 tm.mon = month(f[1]);
745 tm.mday = atoi(f[2]);
746 dtime = nosecs(f[3]);
750 tm = *localtime(now);
754 if(now - then < Yearsecs/2)
755 snprint(out, len, "%2d/%2.2d %s", tm.mon, tm.mday, dtime);
757 snprint(out, len, "%2d/%2.2d %4d", tm.mon, tm.mday, tm.year+1900);
761 findctype(Message *m)
767 static Ctype bintype = { "application/octet-stream", "bin", 0, 0 };
769 for(cp = ctype; cp; cp = cp->next)
770 if(strncmp(cp->type, m->type, strlen(cp->type)) == 0)
786 execl(unsharp("#9/bin/file"), "file", "-m", s_to_c(extendpath(m->path, "body")), nil);
790 n = read(pfd[1], ftype, sizeof(ftype));
798 if (*ftype=='\0' || (p = strchr(ftype, '/')) == nil)
802 a = mallocz(sizeof(Ctype), 1);
803 a->type = strdup(ftype);
806 a->plumbdest = strdup(ftype);
807 for(cp = ctype; cp->next; cp = cp->next)
815 mkid(String *s, Message *m)
819 if(m->parent != &top){
823 sprint(buf, "%d", m->id);
828 snprintheader(char *buf, int len, Message *m)
840 snprint(buf, len, "%-3s %s %6d %s",
845 } else if(*m->subject){
846 q = p = strdup(m->subject);
851 cracktime(m->date, timebuf, sizeof(timebuf));
852 snprint(buf, len, "%-3s %c%c%c %6d %11.11s %-*.*s %s",
854 m->child ? 'H' : ' ',
855 m->deleted ? 'd' : ' ',
856 m->stored ? 's' : ' ',
859 longestfrom, longestfrom, m->from,
863 cracktime(m->date, timebuf, sizeof(timebuf));
864 snprint(buf, len, "%-3s %c%c%c %6d %11.11s %s",
866 m->child ? 'H' : ' ',
867 m->deleted ? 'd' : ' ',
868 m->stored ? 's' : ' ',
879 snprintHeader(char *buf, int len, int indent, Message *m)
891 snprint(typeid, sizeof typeid, "%s %s", s_to_c(id), m->type);
893 p = seprint(buf, e, "%-32s %-6d ", typeid, m->len);
895 p = seprint(buf, e, "%-64s %-6d ", typeid, m->len);
896 if(m->filename && *m->filename)
897 p = seprint(p, e, "(file,%s)", m->filename);
898 if(m->from && *m->from)
899 p = seprint(p, e, "(from,%s)", m->from);
900 if(m->subject && *m->subject)
901 seprint(p, e, "(subj,%s)", m->subject);
908 /* cmd := range cmd ' ' arg-list ; */
909 /* range := address */
910 /* | address ',' address */
912 /* address := msgno */
914 /* msgno := number */
915 /* | number '/' msgno ; */
916 /* search := '/' string '/' */
917 /* | '%' string '%' ; */
920 parsesearch(char **pp)
938 strncpy(sstring, p, sizeof(sstring));
939 sstring[sizeof(sstring)-1] = 0;
945 parseaddr(char **pp, Message *first, Message *cur, Message *unspec, Message **mp)
961 } else if(*p == '-'){
976 case '0': case '1': case '2': case '3': case '4':
977 case '5': case '6': case '7': case '8': case '9':
978 n = strtoul(p, pp, 10);
990 for(m = first; m != nil; m = m->next)
996 for(m = cur; m != nil && n > 0; n--)
1004 for(m = cur; m != nil && n > 0; n--)
1016 prog = parsesearch(pp);
1018 return "badly formed regular expression";
1022 for(m = cur == &top ? first : cur->next; m != nil; m = m->next){
1023 if(rawsearch(m, prog))
1028 for(m = cur == &top ? first : cur->next; m != nil; m = m->next){
1029 snprintheader(buf, sizeof(buf), m);
1030 if(regexec(prog, buf, nil, 0))
1035 for(m = cur == &top ? nil : cur->prev; m != nil; m = m->prev){
1036 snprintheader(buf, sizeof(buf), m);
1037 if(regexec(prog, buf, nil, 0))
1048 for(m = first; m != nil && m->next != nil; m = m->next)
1063 if(*mp != nil && **pp == '.'){
1065 if((*mp)->child == nil)
1066 return "no sub parts";
1067 return parseaddr(pp, (*mp)->child, (*mp)->child, (*mp)->child, mp);
1069 if(**pp == '+' || **pp == '-' || **pp == '/' || **pp == '%')
1070 return parseaddr(pp, first, *mp, *mp, mp);
1076 /* search a message for a regular expression match */
1079 rawsearch(Message *m, Reprog *prog)
1086 path = extendpath(m->path, "raw");
1087 fd = fsopen(mailfs, s_to_c(path), OREAD);
1091 /* march through raw message 4096 bytes at a time */
1092 /* with a 128 byte overlap to chain the re search. */
1095 i = fsread(fd, buf, sizeof(buf)-1);
1099 if(regexec(prog, buf, nil, 0)){
1103 if(i < sizeof(buf)-1)
1105 if(fsseek(fd, -128LL, 1) < 0)
1116 parsecmd(char *p, Cmd *cmd, Message *first, Message *cur)
1119 Message *m, *s, *e, **l, *last;
1124 static char errbuf[Errlen];
1130 /* eat white space */
1134 /* null command is a special case (advance and print) */
1140 /* walk to the next message even if we have to go up */
1142 while(m == nil && cur->parent != nil){
1156 /* global search ? */
1160 /* no search string means all messages */
1161 if(*p != '/' && *p != '%'){
1162 for(m = first; m != nil; m = m->next){
1168 /* mark all messages matching this search string */
1170 prog = parsesearch(&p);
1172 return "badly formed regular expression";
1174 for(m = first; m != nil; m = m->next){
1175 if(rawsearch(m, prog)){
1182 for(m = first; m != nil; m = m->next){
1183 snprintheader(buf, sizeof(buf), m);
1184 if(regexec(prog, buf, nil, 0)){
1195 /* parse an address */
1197 err = parseaddr(&p, first, cur, cur, &s);
1201 /* this is an address range */
1205 for(last = s; last != nil && last->next != nil; last = last->next)
1207 err = parseaddr(&p, first, cur, last, &e);
1211 /* select all messages in the range */
1212 for(; s != nil; s = s->next){
1220 return "null address range";
1222 /* single address */
1230 /* insert a space after '!'s and '|'s */
1232 if(*q != '!' && *q != '|')
1234 if(q != p && *q != ' '){
1235 memmove(q+1, q, strlen(q)+1);
1239 cmd->an = getfields(p, cmd->av, nelem(cmd->av) - 1, 1, " \t\r\n");
1240 if(cmd->an == 0 || *cmd->av[0] == 0)
1243 /* hack to allow all messages to start with 'd' */
1244 if(*(cmd->av[0]) == 'd' && *(cmd->av[0]+1) != 0){
1249 /* search command table */
1250 for(i = 0; cmdtab[i].cmd != nil; i++)
1251 if(strcmp(cmd->av[0], cmdtab[i].cmd) == 0)
1253 if(cmdtab[i].cmd == nil)
1254 return "illegal command";
1255 if(cmdtab[i].args == 0 && cmd->an > 1){
1256 snprint(errbuf, sizeof(errbuf), "%s doesn't take an argument", cmdtab[i].cmd);
1259 cmd->f = cmdtab[i].f;
1264 /* inefficient read from standard input */
1266 readline(char *prompt, char *line, int len)
1273 Bprint(&out, "%s", prompt);
1276 for(p = line; p < e; p++){
1293 messagecount(Message *m)
1298 for(; m != nil; m = m->next)
1300 Bprint(&out, "%d message%s\n", i, plural(i));
1304 aichcmd(Message *m, int indent)
1311 snprintHeader(hdr, sizeof(hdr), indent, m);
1312 Bprint(&out, "%s\n", hdr);
1313 for(m = m->child; m != nil; m = m->next)
1314 aichcmd(m, indent+1);
1319 Hcmd(Cmd *x, Message *m)
1330 hcmd(Cmd *x, Message *m)
1338 snprintheader(hdr, sizeof(hdr), m);
1339 Bprint(&out, "%s\n", hdr);
1344 bcmd(Cmd *x, Message *m)
1352 for(i = 0; i < 10 && m != nil; i++){
1362 ncmd(Cmd *x, Message *m)
1371 printpart(String *s, char *part)
1378 path = extendpath(s, part);
1379 fd = fsopen(mailfs, s_to_c(path), OREAD);
1382 fprint(2, "!message dissappeared\n");
1386 while((n = fsread(fd, buf, sizeof(buf))) > 0){
1389 if(Bwrite(&out, buf, n) <= 0)
1398 printhtml(Message *m)
1403 c.av[1] = "htmlfmt";
1404 c.av[2] = "-l 40 -cutf-8";
1405 Bprint(&out, "!%s\n", c.av[1]);
1412 Pcmd(Cmd *x, Message *m)
1417 if(m->parent == &top)
1418 printpart(m->path, "unixheader");
1419 printpart(m->path, "raw");
1430 for(np = p; *p; p++){
1431 if(*p != ' ' || last != ' '){
1440 pcmd(Cmd *x, Message *m)
1450 if(m->parent == &top)
1451 printpart(m->path, "unixheader");
1452 if(printpart(m->path, "header") > 0)
1456 if(strcmp(m->type, "text/html") == 0)
1459 printpart(m->path, "body");
1460 } else if(strcmp(m->type, "multipart/alternative") == 0){
1461 for(nm = m->child; nm != nil; nm = nm->next){
1463 if(cp->ext != nil && strncmp(cp->ext, "txt", 3) == 0)
1467 for(nm = m->child; nm != nil; nm = nm->next){
1476 } else if(strncmp(m->type, "multipart/", 10) == 0){
1479 /* always print first part */
1482 for(nm = nm->next; nm != nil; nm = nm->next){
1483 s = rooted(s_clone(nm->path));
1485 snprintHeader(buf, sizeof buf, -1, nm);
1487 if(strcmp(nm->disposition, "inline") == 0){
1489 Bprint(&out, "\n--- %s %s/body.%s\n\n",
1490 buf, s_to_c(s), cp->ext);
1492 Bprint(&out, "\n--- %s %s/body\n\n",
1497 Bprint(&out, "\n!--- %s %s/body.%s\n",
1498 buf, s_to_c(s), cp->ext);
1500 Bprint(&out, "\n!--- %s %s/body\n",
1508 } else if(strcmp(m->type, "message/rfc822") == 0){
1509 pcmd(nil, m->child);
1510 } else if(plumb(m, cp) >= 0)
1511 Bprint(&out, "\n!--- using plumber to display message of type %s\n", m->type);
1513 Bprint(&out, "\n!--- cannot display messages of type %s\n", m->type);
1519 printpartindented(String *s, char *part, char *indent)
1526 path = extendpath(s, part);
1527 fd = fsopenfd(mailfs, s_to_c(path), OREAD);
1530 fprint(2, "!message disappeared\n");
1533 b = Bfdopen(fd, OREAD);
1535 fprint(2, "out of memory\n");
1539 while((p = Brdline(b, '\n')) != nil){
1542 p[Blinelen(b)-1] = 0;
1543 if(Bprint(&out, "%s%s\n", indent, p) < 0)
1551 quotecmd(Cmd *x, Message *m)
1560 if(m->from != nil && *m->from)
1561 Bprint(&out, "On %s, %s wrote:\n", m->date, m->from);
1564 printpartindented(m->path, "body", "> ");
1565 } else if(strcmp(m->type, "multipart/alternative") == 0){
1566 for(nm = m->child; nm != nil; nm = nm->next){
1568 if(cp->ext != nil && strncmp(cp->ext, "txt", 3) == 0)
1572 for(nm = m->child; nm != nil; nm = nm->next){
1579 } else if(strncmp(m->type, "multipart/", 10) == 0){
1583 if(cp->display || strncmp(m->type, "multipart/", 10) == 0)
1590 /* really delete messages */
1592 flushdeleted(Message *cur)
1595 char buf[1024], *p, *e, *msg;
1603 snprint(buf, sizeof buf, "%s/ctl", mbname);
1604 fd = fsopen(mailfs, buf, OWRITE);
1606 fprint(2, "!can't delete mail, opening %s: %r\n", buf);
1609 e = &buf[sizeof(buf)];
1610 p = seprint(buf, e, "delete");
1612 for(l = &top.child; *l != nil;){
1619 /* don't return a pointer to a deleted message */
1624 msg = strrchr(s_to_c(m->path), '/');
1626 msg = s_to_c(m->path);
1630 fswrite(fd, buf, p-buf);
1632 p = seprint(buf, e, "delete");
1634 p = seprint(p, e, " %s", msg);
1637 /* unchain and free */
1640 m->next->prev = m->prev;
1644 fswrite(fd, buf, p-buf);
1649 Bprint(&out, "!%d message%s deleted\n", deld, plural(deld));
1653 for(m = top.child; m != nil; m = m->next)
1654 m->id = natural ? m->fileno : i++;
1656 /* if we're out of messages, go back to first */
1657 /* if no first, return the fake first */
1668 qcmd(Cmd *x, Message *m)
1680 return nil; /* not reached */
1684 ycmd(Cmd *x, Message *m)
1690 return icmd(nil, m);
1694 xcmd(Cmd *x, Message *m)
1700 return nil; /* not reached */
1704 eqcmd(Cmd *x, Message *m)
1709 Bprint(&out, "0\n");
1711 Bprint(&out, "%d\n", m->id);
1716 dcmd(Cmd *x, Message *m)
1721 Bprint(&out, "!address\n");
1724 while(m->parent != &top)
1731 ucmd(Cmd *x, Message *m)
1737 while(m->parent != &top)
1740 Bprint(&out, "!can't undelete, already flushed\n");
1747 icmd(Cmd *x, Message *m)
1754 snprint(buf, sizeof buf, "%s/ctl", mbname);
1755 fd = fsopen(mailfs, buf, OWRITE);
1757 fswrite(fd, "refresh", 7);
1760 n = dir2message(&top, reverse);
1762 Bprint(&out, "%d new message%s\n", n, plural(n));
1767 helpcmd(Cmd *x, Message *m)
1772 Bprint(&out, "Commands are of the form [<range>] <command> [args]\n");
1773 Bprint(&out, "<range> := <addr> | <addr>','<addr>| 'g'<search>\n");
1774 Bprint(&out, "<addr> := '.' | '$' | '^' | <number> | <search> | <addr>'+'<addr> | <addr>'-'<addr>\n");
1775 Bprint(&out, "<search> := '/'<regexp>'/' | '?'<regexp>'?' | '%%'<regexp>'%%'\n");
1776 Bprint(&out, "<command> :=\n");
1777 for(i = 0; cmdtab[i].cmd != nil; i++)
1778 Bprint(&out, "%s\n", cmdtab[i].help);
1785 static char *marshal;
1790 marshal = unsharp("#9/bin/upas/marshal");
1792 /* start the mailer and get out of the way */
1793 switch(pid = fork()){
1795 fprint(2, "can't fork: %r\n");
1798 Bprint(&out, "!%s", marshal);
1799 for(i = 1; av[i]; i++){
1800 if(strchr(av[i], ' ') != nil)
1801 Bprint(&out, " '%s'", av[i]);
1803 Bprint(&out, " %s", av[i]);
1810 fprint(2, "couldn't exec %s\n", marshal);
1816 postnote(PNPROC, pid, "die");
1821 fprint(2, "mailer failed: %s\n", w->msg);
1826 Bprint(&out, "!\n");
1833 /* like tokenize but obey "" quoting */
1836 tokenize822(char *str, char **args, int max)
1839 int intok = 0, inquote = 0;
1870 return 0; /* can't get here; silence compiler */
1874 rcmd(Cmd *c, Message *m)
1882 String *subject = nil;
1886 Bprint(&out, "!address\n");
1891 for(nm = m; nm != ⊤ nm = nm->parent){
1892 if(*nm->replyto != 0){
1898 Bprint(&out, "!no reply address\n");
1903 print("!noone to reply to\n");
1907 for(nm = m; nm != ⊤ nm = nm->parent){
1910 subject = addrecolon(nm->subject);
1911 av[ai++] = s_to_c(subject);;
1917 rpath = rooted(s_clone(m->path));
1918 av[ai++] = s_to_c(rpath);
1920 if(strchr(c->av[0], 'f') != nil){
1925 if(strchr(c->av[0], 'R') != nil){
1927 av[ai++] = "message/rfc822";
1929 path = rooted(extendpath(m->path, "raw"));
1930 av[ai++] = s_to_c(path);
1933 for(i = 1; i < c->an && ai < nelem(av)-1; i++)
1934 av[ai++] = c->av[i];
1935 from = s_copy(addr);
1936 ai += tokenize822(s_to_c(from), &av[ai], nelem(av) - ai);
1938 if(tomailer(av) < 0)
1948 mcmd(Cmd *c, Message *m)
1955 Bprint(&out, "!address\n");
1960 fprint(2, "!usage: M list-of addresses\n");
1965 av = malloc(sizeof(char*)*(c->an + 8));
1968 if(m->parent == &top)
1969 av[ai++] = "message/rfc822";
1974 path = rooted(extendpath(m->path, "raw"));
1975 av[ai++] = s_to_c(path);
1977 if(strchr(c->av[0], 'M') == nil)
1980 for(i = 1; i < c->an; i++)
1981 av[ai++] = c->av[i];
1984 if(tomailer(av) < 0)
1993 acmd(Cmd *c, Message *m)
1997 String *from, *to, *cc, *path = nil, *subject = nil;
2000 Bprint(&out, "!address\n");
2007 subject = addrecolon(m->subject);
2008 av[ai++] = s_to_c(subject);
2011 if(strchr(c->av[0], 'A') != nil){
2013 av[ai++] = "message/rfc822";
2015 path = rooted(extendpath(m->path, "raw"));
2016 av[ai++] = s_to_c(path);
2019 for(i = 1; i < c->an && ai < nelem(av)-1; i++)
2020 av[ai++] = c->av[i];
2021 from = s_copy(m->from);
2022 ai += tokenize822(s_to_c(from), &av[ai], nelem(av) - ai);
2024 ai += tokenize822(s_to_c(to), &av[ai], nelem(av) - ai);
2026 ai += tokenize822(s_to_c(cc), &av[ai], nelem(av) - ai);
2028 if(tomailer(av) < 0)
2039 relpath(char *path, String *to)
2041 if (*path=='/' || strncmp(path, "./", 2) == 0
2042 || strncmp(path, "../", 3) == 0) {
2043 to = s_append(to, path);
2045 to = s_append(to, s_to_c(mbpath));
2046 to->ptr = strrchr(to->base, '/')+1;
2053 appendtofile(Message *m, char *part, char *base, int mbox)
2058 file = extendpath(m->path, part);
2059 in = open(s_to_c(file), OREAD);
2061 fprint(2, "!message disappeared\n");
2067 relpath(base, file);
2068 if(sysisdir(s_to_c(file))){
2069 s_append(file, "/");
2070 if(m->filename && strchr(m->filename, '/') == nil)
2071 s_append(file, m->filename);
2073 s_append(file, "att.XXXXXXXXXXX");
2074 mktemp(s_to_c(file));
2078 out = open(s_to_c(file), OWRITE);
2080 out = open(s_to_c(file), OWRITE|OTRUNC);
2082 out = create(s_to_c(file), OWRITE, 0666);
2084 fprint(2, "!can't open %s: %r\n", s_to_c(file));
2093 /* put on a 'From ' line */
2095 while(m->parent != &top)
2097 h = file2string(m->path, "unixheader");
2098 fprint(out, "%s", s_to_c(h));
2102 /* copy the message escaping what we have to ad adding newlines if we have to */
2104 rv = appendfiletombox(in, out);
2106 rv = appendfiletofile(in, out);
2112 print("!saved in %s\n", s_to_c(file));
2118 scmd(Cmd *c, Message *m)
2125 Bprint(&out, "!address\n");
2137 fprint(2, "!usage: s filename\n");
2141 if(file[0] == '/' || (file[0]=='.' && file[1]=='/')){
2142 if(appendtofile(m, "raw", file, 1) < 0)
2145 snprint(buf, sizeof buf, "%s/ctl", mbname);
2146 if((fd = fsopen(mailfs, buf, OWRITE)) == nil)
2148 msg = strrchr(s_to_c(m->path), '/');
2150 msg = s_to_c(m->path);
2153 if(fsprint(fd, "save %s %s", file, msg) < 0){
2164 wcmd(Cmd *c, Message *m)
2169 Bprint(&out, "!address\n");
2178 if(*m->filename == 0){
2179 fprint(2, "!usage: w filename\n");
2182 file = strrchr(m->filename, '/');
2189 fprint(2, "!usage: w filename\n");
2193 if(appendtofile(m, "body", file, 0) < 0)
2199 char *specialfile[] =
2208 /* return 1 if this is a special file */
2215 p = strrchr(s_to_c(s), '/');
2220 for(i = 0; i < nelem(specialfile); i++)
2221 if(strcmp(p, specialfile[i]) == 0)
2226 /* open the folder using the recipients account name */
2228 foldername(char *rcvr)
2237 mboxpath("f", user, file, 0);
2238 d = dirstat(s_to_c(file));
2240 /* if $mail/f exists, store there, otherwise in $mail */
2242 if(d && d->qid.type == QTDIR){
2244 s_append(file, "f/");
2250 p = strrchr(rcvr, '!');
2254 while(*rcvr && *rcvr != '@'){
2262 if(scarey && special(file)){
2263 fprint(2, "!won't overwrite %s\n", s_to_c(file));
2272 fcmd(Cmd *c, Message *m)
2277 fprint(2, "!usage: f takes no arguments\n");
2282 Bprint(&out, "!address\n");
2286 folder = foldername(m->from);
2290 if(appendtofile(m, "raw", s_to_c(folder), 1) < 0){
2301 system(char *cmd, char **av, int in)
2309 if(strcmp(cmd, "rc") == 0)
2310 cmd = unsharp("#9/bin/rc");
2319 fprint(2, "!couldn't exec %s\n", cmd);
2324 while(waitpid() < 0){
2327 postnote(PNPROC, pid, "die");
2335 bangcmd(Cmd *c, Message *m)
2344 e = cmd+sizeof(cmd);
2345 for(i = 1; i < c->an; i++)
2346 p = seprint(p, e, "%s ", c->av[i]);
2351 system("rc", av, -1);
2352 Bprint(&out, "!\n");
2357 xpipecmd(Cmd *c, Message *m, char *part)
2366 Bprint(&out, "!usage: | cmd\n");
2371 Bprint(&out, "!address\n");
2375 path = extendpath(m->path, part);
2376 fd = fsopenfd(mailfs, s_to_c(path), OREAD);
2379 if(fd < 0){ /* compatibility with older upas/fs */
2380 path = extendpath(m->path, "raw");
2381 fd = fsopenfd(mailfs, s_to_c(path), OREAD);
2385 fprint(2, "!message disappeared\n");
2390 e = cmd+sizeof(cmd);
2392 for(i = 1; i < c->an; i++)
2393 p = seprint(p, e, "%s ", c->av[i]);
2398 system("rc", av, fd); /* system closes fd */
2399 Bprint(&out, "!\n");
2404 pipecmd(Cmd *c, Message *m)
2406 return xpipecmd(c, m, "body");
2410 rpipecmd(Cmd *c, Message *m)
2412 return xpipecmd(c, m, "rawunix");
2420 fd = fsopen(mailfs, "ctl", OWRITE);
2422 sysfatal("can't open ctl: %r");
2424 /* close current mailbox */
2425 if(*mbname && strcmp(mbname, "mbox") != 0)
2426 fsprint(fd, "close %s", mbname);
2432 switchmb(char *file, char *singleton)
2439 /* if the user didn't say anything and there */
2440 /* is an mbox mounted already, use that one */
2441 /* so that the upas/fs -fdefault default is honored. */
2442 if(0 && (file || (singleton && fsaccess(mailfs, singleton, 0) < 0))){
2444 fprint(2, "file=%s singleton=%s\n", file, singleton);
2448 /* close current mailbox */
2452 fd = open("/mail/fs/ctl", ORDWR);
2454 sysfatal("can't open /mail/fs/ctl: %r");
2458 /* get an absolute path to the mail box */
2459 if(strncmp(file, "./", 2) == 0){
2460 /* resolve path here since upas/fs doesn't know */
2461 /* our working directory */
2462 if(getwd(buf, sizeof(buf)-strlen(file)) == nil){
2463 fprint(2, "!can't get working directory: %s\n", buf);
2466 s_append(path, buf);
2467 s_append(path, file+1);
2469 mboxpath(file, user, path, 0);
2472 /* make up a handle to use when talking to fs */
2473 p = strrchr(file, '/');
2475 /* if its in the mailbox directory, just use the name */
2476 strncpy(mbname, file, sizeof(mbname));
2477 mbname[sizeof(mbname)-1] = 0;
2479 /* make up a mailbox name */
2480 p = strrchr(s_to_c(path), '/');
2483 fprint(2, "!bad mbox name");
2486 strncpy(mbname, p, sizeof(mbname));
2487 mbname[sizeof(mbname)-1] = 0;
2491 sprint(mbname+n, "%ld", time(0));
2494 if(fprint(fd, "open %s %s", s_to_c(path), mbname) < 0){
2495 fprint(2, "!can't 'open %s %s': %r\n", file, mbname);
2501 if (singleton && fsaccess(mailfs, singleton, 0)==0){
2502 if ((p = strchr(singleton, '/')) == nil){
2503 fprint(2, "!bad mbox name");
2507 strncpy(mbname, singleton, n);
2509 path = s_reset(nil);
2510 mboxpath(mbname, user, path, 0);
2513 strecpy(mbname, mbname+sizeof mbname, file);
2515 strcpy(mbname, "mbox");
2516 path = s_reset(nil);
2517 mboxpath(mbname, user, path, 0);
2520 snprint(root, sizeof root, "%s", mbname);
2521 rootlen = strlen(root);
2529 /* like tokenize but for into lines */
2531 lineize(char *s, char **f, int n)
2535 for(i = 0; *s && i < n; i++){
2537 s = strchr(s, '\n');
2550 static char buf[256];
2552 if(strcmp(root, ".") != 0)
2554 snprint(buf, sizeof(buf), "/mail/fs/%s/%s", mbname, s_to_c(s));
2560 plumb(Message *m, Ctype *cp)
2566 if(cp->plumbdest == nil)
2570 fd = plumbopen("send", OWRITE);
2574 pm = mallocz(sizeof(Plumbmsg), 1);
2575 pm->src = strdup("mail");
2577 pm->dst = strdup(cp->plumbdest);
2579 pm->type = strdup("text");
2581 s = rooted(extendpath(m->path, "body"));
2584 s_append(s, cp->ext);
2586 pm->data = strdup(s_to_c(s));
2604 if(cistrncmp(s, "re:", 3) != 0){
2605 str = s_copy("Re: ");