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;
230 Binit(&out, 1, OWRITE);
241 file = EARGF(usage());
244 singleton = EARGF(usage());
259 if(user == nil || *user == 0)
260 sysfatal("can't read user name");
264 creatembox(user, argv[0]);
266 creatembox(user, nil);
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++)
282 if(singleton != nil){
283 cur = dosingleton(&top, singleton);
285 Bprint(&out, "no message\n");
291 n = dir2message(&top, reverse);
293 sysfatal("can't read %s", s_to_c(top.path));
294 Bprint(&out, "%d message%s\n", n, plural(n));
303 s_append(prompt, ": ");
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)
313 err = parsecmd(cmdline, &cmd, top.child, cur);
315 Bprint(&out, "!%s\n", err);
318 if(singleton != nil && cmd.f == icmd){
319 Bprint(&out, "!illegal command\n");
323 if(cmd.msgs == nil || cmd.msgs == &top){
324 x = (*cmd.f)(&cmd, &top);
327 } else for(m = cmd.msgs; m != nil; m = m->cmd){
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. */
338 Bprint(&out, "!address\n");
345 x = (*cmd.f)(&cmd, x);
350 if(singleton != nil && (cmd.delete || cmd.f == dcmd))
354 cur = flushdeleted(cur);
371 if(*s == ' ' && !inquote)
374 f = malloc(nf*sizeof f[0]);
377 nf = tokenize(t, f, nf);
379 for(i=0; i+1<nf; i+=2){
382 /* if(f[i][0] == 0 || strcmp(f[i], f[i+1]) == 0) */
383 fmtprint(&fmt, "%s", f[i+1]);
385 /* fmtprint(&fmt, "%s <%s>", f[i], f[i+1]); */
388 return fmtstrflush(&fmt);
392 /* read the message info */
395 file2message(Message *parent, char *name)
402 m = mallocz(sizeof(Message), 1);
405 m->path = path = extendpath(parent->path, name);
406 m->fileno = atoi(name);
407 m->info = file2string(path, "info");
417 nf = lineize(s_to_c(m->info), f, nelem(f));
420 t = strchr(f[i], ' ');
425 if(strcmp(s, "from") == 0)
426 m->from = mkaddrs(t);
427 else if(strcmp(s, "to") == 0)
429 else if(strcmp(s, "cc") == 0)
431 else if(strcmp(s, "replyto") == 0)
432 m->replyto = mkaddrs(t);
433 else if(strcmp(s, "unixdate") == 0 && (t=strchr(t, ' ')) != nil)
435 else if(strcmp(s, "subject") == 0)
437 else if(strcmp(s, "type") == 0)
439 else if(strcmp(s, "disposition") == 0)
441 else if(strcmp(s, "filename") == 0)
444 m->len = filelen(path, "raw");
445 if(strstr(m->type, "multipart") != nil || strcmp(m->type, "message/rfc822") == 0)
453 freemessage(Message *m)
457 for(nm = m->child; nm != nil; nm = next){
467 /* read a directory into a list of messages */
470 dir2message(Message *parent, int reverse)
472 int i, n, highest, newmsgs;
476 Message *first, *last, *m;
478 fd = fsopen(mailfs, s_to_c(parent->path), OREAD);
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;
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)
496 if(atoi(d[i].name) <= highest)
498 m = file2message(parent, d[i].name);
499 /* fprint(2,"returned from file2message\n"); */
519 parent->child = first;
521 /* renumber and file longest from */
524 for(m = first; m != nil; m = m->next){
525 m->id = natural ? m->fileno : i++;
535 /* point directly to a message */
538 dosingleton(Message *parent, char *path)
543 /* walk down to message and read it */
544 if(strlen(path) < rootlen)
546 if(path[rootlen] != '/')
552 m = file2message(parent, p);
558 /* walk down to requested component */
561 np = strchr(np+1, '/');
564 for(m = m->child; m != nil; m = m->next)
565 if(strcmp(path, s_to_c(m->path)) == 0)
574 /* read a file into a string */
577 file2string(String *dir, char *file)
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 */
596 m = fsread(fd, s->ptr, n);
610 /* get the length of a file */
613 filelen(String *dir, char *file)
619 path = extendpath(dir, file);
620 d = fsdirstat(mailfs, s_to_c(path));
632 /* walk the path name an element */
635 extendpath(String *dir, char *name)
639 if(strcmp(s_to_c(dir), ".") == 0)
642 path = s_copy(s_to_c(dir));
645 s_append(path, name);
650 cistrncmp(char *a, char *b, int n)
653 if(tolower(*a++) != tolower(*b++))
660 cistrcmp(char *a, char *b)
663 if(tolower(*a) != tolower(*b++))
679 p = strchr(p+1, ':');
687 "jan", "feb", "mar", "apr", "may", "jun",
688 "jul", "aug", "sep", "oct", "nov", "dec"
696 for(i = 0; i < 12; i++)
697 if(cistrcmp(m, months[i]) == 0)
704 Yearsecs= 365*24*60*60
708 cracktime(char *d, char *out, int len)
720 strncpy(in, d, sizeof(in));
721 in[sizeof(in)-1] = 0;
722 n = getfields(in, f, 6, 1, " \t\r\n");
725 snprint(out, 16, "%10.10s", d);
729 memset(&tm, 0, sizeof tm);
730 if(strchr(f[0], ',') != nil && strchr(f[4], ':') != nil){
732 tm.year = atoi(f[3])-1900;
733 tm.mon = month(f[2]);
734 tm.mday = atoi(f[1]);
735 dtime = nosecs(f[4]);
737 } else if(strchr(f[3], ':') != nil){
739 tm.year = atoi(f[5])-1900;
740 tm.mon = month(f[1]);
741 tm.mday = atoi(f[2]);
742 dtime = nosecs(f[3]);
746 tm = *localtime(now);
750 if(now - then < Yearsecs/2)
751 snprint(out, len, "%2d/%2.2d %s", tm.mon, tm.mday, dtime);
753 snprint(out, len, "%2d/%2.2d %4d", tm.mon, tm.mday, tm.year+1900);
757 findctype(Message *m)
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)
782 execl(unsharp("#9/bin/file"), "file", "-m", s_to_c(extendpath(m->path, "body")), nil);
786 n = read(pfd[1], ftype, sizeof(ftype));
794 if (*ftype=='\0' || (p = strchr(ftype, '/')) == nil)
798 a = mallocz(sizeof(Ctype), 1);
799 a->type = strdup(ftype);
802 a->plumbdest = strdup(ftype);
803 for(cp = ctype; cp->next; cp = cp->next)
811 mkid(String *s, Message *m)
815 if(m->parent != &top){
819 sprint(buf, "%d", m->id);
824 snprintheader(char *buf, int len, Message *m)
836 snprint(buf, len, "%-3s %s %6d %s",
841 } else if(*m->subject){
842 q = p = strdup(m->subject);
847 cracktime(m->date, timebuf, sizeof(timebuf));
848 snprint(buf, len, "%-3s %c%c%c %6d %11.11s %-*.*s %s",
850 m->child ? 'H' : ' ',
851 m->deleted ? 'd' : ' ',
852 m->stored ? 's' : ' ',
855 longestfrom, longestfrom, m->from,
859 cracktime(m->date, timebuf, sizeof(timebuf));
860 snprint(buf, len, "%-3s %c%c%c %6d %11.11s %s",
862 m->child ? 'H' : ' ',
863 m->deleted ? 'd' : ' ',
864 m->stored ? 's' : ' ',
875 snprintHeader(char *buf, int len, int indent, Message *m)
887 snprint(typeid, sizeof typeid, "%s %s", s_to_c(id), m->type);
889 p = seprint(buf, e, "%-32s %-6d ", typeid, m->len);
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);
904 /* cmd := range cmd ' ' arg-list ; */
905 /* range := address */
906 /* | address ',' address */
908 /* address := msgno */
910 /* msgno := number */
911 /* | number '/' msgno ; */
912 /* search := '/' string '/' */
913 /* | '%' string '%' ; */
916 parsesearch(char **pp)
934 strncpy(sstring, p, sizeof(sstring));
935 sstring[sizeof(sstring)-1] = 0;
941 parseaddr(char **pp, Message *first, Message *cur, Message *unspec, Message **mp)
957 } else if(*p == '-'){
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);
986 for(m = first; m != nil; m = m->next)
992 for(m = cur; m != nil && n > 0; n--)
1000 for(m = cur; m != nil && n > 0; n--)
1012 prog = parsesearch(pp);
1014 return "badly formed regular expression";
1018 for(m = cur == &top ? first : cur->next; m != nil; m = m->next){
1019 if(rawsearch(m, prog))
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))
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))
1044 for(m = first; m != nil && m->next != nil; m = m->next)
1059 if(*mp != nil && **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);
1072 /* search a message for a regular expression match */
1075 rawsearch(Message *m, Reprog *prog)
1082 path = extendpath(m->path, "raw");
1083 fd = fsopen(mailfs, s_to_c(path), OREAD);
1087 /* march through raw message 4096 bytes at a time */
1088 /* with a 128 byte overlap to chain the re search. */
1091 i = fsread(fd, buf, sizeof(buf)-1);
1095 if(regexec(prog, buf, nil, 0)){
1099 if(i < sizeof(buf)-1)
1101 if(fsseek(fd, -128LL, 1) < 0)
1112 parsecmd(char *p, Cmd *cmd, Message *first, Message *cur)
1115 Message *m, *s, *e, **l, *last;
1120 static char errbuf[Errlen];
1126 /* eat white space */
1130 /* null command is a special case (advance and print) */
1136 /* walk to the next message even if we have to go up */
1138 while(m == nil && cur->parent != nil){
1152 /* global search ? */
1156 /* no search string means all messages */
1157 if(*p != '/' && *p != '%'){
1158 for(m = first; m != nil; m = m->next){
1164 /* mark all messages matching this search string */
1166 prog = parsesearch(&p);
1168 return "badly formed regular expression";
1170 for(m = first; m != nil; m = m->next){
1171 if(rawsearch(m, prog)){
1178 for(m = first; m != nil; m = m->next){
1179 snprintheader(buf, sizeof(buf), m);
1180 if(regexec(prog, buf, nil, 0)){
1191 /* parse an address */
1193 err = parseaddr(&p, first, cur, cur, &s);
1197 /* this is an address range */
1201 for(last = s; last != nil && last->next != nil; last = last->next)
1203 err = parseaddr(&p, first, cur, last, &e);
1207 /* select all messages in the range */
1208 for(; s != nil; s = s->next){
1216 return "null address range";
1218 /* single address */
1226 /* insert a space after '!'s and '|'s */
1228 if(*q != '!' && *q != '|')
1230 if(q != p && *q != ' '){
1231 memmove(q+1, q, strlen(q)+1);
1235 cmd->an = getfields(p, cmd->av, nelem(cmd->av) - 1, 1, " \t\r\n");
1236 if(cmd->an == 0 || *cmd->av[0] == 0)
1239 /* hack to allow all messages to start with 'd' */
1240 if(*(cmd->av[0]) == 'd' && *(cmd->av[0]+1) != 0){
1245 /* search command table */
1246 for(i = 0; cmdtab[i].cmd != nil; i++)
1247 if(strcmp(cmd->av[0], cmdtab[i].cmd) == 0)
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);
1255 cmd->f = cmdtab[i].f;
1260 /* inefficient read from standard input */
1262 readline(char *prompt, char *line, int len)
1269 Bprint(&out, "%s", prompt);
1272 for(p = line; p < e; p++){
1289 messagecount(Message *m)
1294 for(; m != nil; m = m->next)
1296 Bprint(&out, "%d message%s\n", i, plural(i));
1300 aichcmd(Message *m, int indent)
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);
1315 Hcmd(Cmd *x, Message *m)
1326 hcmd(Cmd *x, Message *m)
1334 snprintheader(hdr, sizeof(hdr), m);
1335 Bprint(&out, "%s\n", hdr);
1340 bcmd(Cmd *x, Message *m)
1348 for(i = 0; i < 10 && m != nil; i++){
1358 ncmd(Cmd *x, Message *m)
1367 printpart(String *s, char *part)
1374 path = extendpath(s, part);
1375 fd = fsopen(mailfs, s_to_c(path), OREAD);
1378 fprint(2, "!message dissappeared\n");
1382 while((n = fsread(fd, buf, sizeof(buf))) > 0){
1385 if(Bwrite(&out, buf, n) <= 0)
1394 printhtml(Message *m)
1399 c.av[1] = "htmlfmt";
1400 c.av[2] = "-l 40 -cutf-8";
1401 Bprint(&out, "!%s\n", c.av[1]);
1408 Pcmd(Cmd *x, Message *m)
1413 if(m->parent == &top)
1414 printpart(m->path, "unixheader");
1415 printpart(m->path, "raw");
1426 for(np = p; *p; p++){
1427 if(*p != ' ' || last != ' '){
1436 pcmd(Cmd *x, Message *m)
1446 if(m->parent == &top)
1447 printpart(m->path, "unixheader");
1448 if(printpart(m->path, "header") > 0)
1452 if(strcmp(m->type, "text/html") == 0)
1455 printpart(m->path, "body");
1456 } else if(strcmp(m->type, "multipart/alternative") == 0){
1457 for(nm = m->child; nm != nil; nm = nm->next){
1459 if(cp->ext != nil && strncmp(cp->ext, "txt", 3) == 0)
1463 for(nm = m->child; nm != nil; nm = nm->next){
1472 } else if(strncmp(m->type, "multipart/", 10) == 0){
1475 /* always print first part */
1478 for(nm = nm->next; nm != nil; nm = nm->next){
1479 s = rooted(s_clone(nm->path));
1481 snprintHeader(buf, sizeof buf, -1, nm);
1483 if(strcmp(nm->disposition, "inline") == 0){
1485 Bprint(&out, "\n--- %s %s/body.%s\n\n",
1486 buf, s_to_c(s), cp->ext);
1488 Bprint(&out, "\n--- %s %s/body\n\n",
1493 Bprint(&out, "\n!--- %s %s/body.%s\n",
1494 buf, s_to_c(s), cp->ext);
1496 Bprint(&out, "\n!--- %s %s/body\n",
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);
1509 Bprint(&out, "\n!--- cannot display messages of type %s\n", m->type);
1515 printpartindented(String *s, char *part, char *indent)
1522 path = extendpath(s, part);
1523 fd = fsopenfd(mailfs, s_to_c(path), OREAD);
1526 fprint(2, "!message disappeared\n");
1529 b = Bfdopen(fd, OREAD);
1531 fprint(2, "out of memory\n");
1535 while((p = Brdline(b, '\n')) != nil){
1538 p[Blinelen(b)-1] = 0;
1539 if(Bprint(&out, "%s%s\n", indent, p) < 0)
1547 quotecmd(Cmd *x, Message *m)
1556 if(m->from != nil && *m->from)
1557 Bprint(&out, "On %s, %s wrote:\n", m->date, m->from);
1560 printpartindented(m->path, "body", "> ");
1561 } else if(strcmp(m->type, "multipart/alternative") == 0){
1562 for(nm = m->child; nm != nil; nm = nm->next){
1564 if(cp->ext != nil && strncmp(cp->ext, "txt", 3) == 0)
1568 for(nm = m->child; nm != nil; nm = nm->next){
1575 } else if(strncmp(m->type, "multipart/", 10) == 0){
1579 if(cp->display || strncmp(m->type, "multipart/", 10) == 0)
1586 /* really delete messages */
1588 flushdeleted(Message *cur)
1591 char buf[1024], *p, *e, *msg;
1599 snprint(buf, sizeof buf, "%s/ctl", mbname);
1600 fd = fsopen(mailfs, buf, OWRITE);
1602 fprint(2, "!can't delete mail, opening %s: %r\n", buf);
1605 e = &buf[sizeof(buf)];
1606 p = seprint(buf, e, "delete");
1608 for(l = &top.child; *l != nil;){
1615 /* don't return a pointer to a deleted message */
1620 msg = strrchr(s_to_c(m->path), '/');
1622 msg = s_to_c(m->path);
1626 fswrite(fd, buf, p-buf);
1628 p = seprint(buf, e, "delete");
1630 p = seprint(p, e, " %s", msg);
1633 /* unchain and free */
1636 m->next->prev = m->prev;
1640 fswrite(fd, buf, p-buf);
1645 Bprint(&out, "!%d message%s deleted\n", deld, plural(deld));
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 */
1664 qcmd(Cmd *x, Message *m)
1676 return nil; /* not reached */
1680 ycmd(Cmd *x, Message *m)
1686 return icmd(nil, m);
1690 xcmd(Cmd *x, Message *m)
1696 return nil; /* not reached */
1700 eqcmd(Cmd *x, Message *m)
1705 Bprint(&out, "0\n");
1707 Bprint(&out, "%d\n", m->id);
1712 dcmd(Cmd *x, Message *m)
1717 Bprint(&out, "!address\n");
1720 while(m->parent != &top)
1727 ucmd(Cmd *x, Message *m)
1733 while(m->parent != &top)
1736 Bprint(&out, "!can't undelete, already flushed\n");
1743 icmd(Cmd *x, Message *m)
1750 snprint(buf, sizeof buf, "%s/ctl", mbname);
1751 fd = fsopen(mailfs, buf, OWRITE);
1753 fswrite(fd, "refresh", 7);
1756 n = dir2message(&top, reverse);
1758 Bprint(&out, "%d new message%s\n", n, plural(n));
1763 helpcmd(Cmd *x, Message *m)
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);
1781 static char *marshal;
1786 marshal = unsharp("#9/bin/upas/marshal");
1788 /* start the mailer and get out of the way */
1789 switch(pid = fork()){
1791 fprint(2, "can't fork: %r\n");
1794 Bprint(&out, "!%s", marshal);
1795 for(i = 1; av[i]; i++){
1796 if(strchr(av[i], ' ') != nil)
1797 Bprint(&out, " '%s'", av[i]);
1799 Bprint(&out, " %s", av[i]);
1806 fprint(2, "couldn't exec %s\n", marshal);
1812 postnote(PNPROC, pid, "die");
1817 fprint(2, "mailer failed: %s\n", w->msg);
1822 Bprint(&out, "!\n");
1829 /* like tokenize but obey "" quoting */
1832 tokenize822(char *str, char **args, int max)
1835 int intok = 0, inquote = 0;
1866 return 0; /* can't get here; silence compiler */
1870 rcmd(Cmd *c, Message *m)
1878 String *subject = nil;
1882 Bprint(&out, "!address\n");
1887 for(nm = m; nm != ⊤ nm = nm->parent){
1888 if(*nm->replyto != 0){
1894 Bprint(&out, "!no reply address\n");
1899 print("!noone to reply to\n");
1903 for(nm = m; nm != ⊤ nm = nm->parent){
1906 subject = addrecolon(nm->subject);
1907 av[ai++] = s_to_c(subject);;
1913 rpath = rooted(s_clone(m->path));
1914 av[ai++] = s_to_c(rpath);
1916 if(strchr(c->av[0], 'f') != nil){
1921 if(strchr(c->av[0], 'R') != nil){
1923 av[ai++] = "message/rfc822";
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);
1934 if(tomailer(av) < 0)
1944 mcmd(Cmd *c, Message *m)
1951 Bprint(&out, "!address\n");
1956 fprint(2, "!usage: M list-of addresses\n");
1961 av = malloc(sizeof(char*)*(c->an + 8));
1964 if(m->parent == &top)
1965 av[ai++] = "message/rfc822";
1970 path = rooted(extendpath(m->path, "raw"));
1971 av[ai++] = s_to_c(path);
1973 if(strchr(c->av[0], 'M') == nil)
1976 for(i = 1; i < c->an; i++)
1977 av[ai++] = c->av[i];
1980 if(tomailer(av) < 0)
1989 acmd(Cmd *c, Message *m)
1993 String *from, *to, *cc, *path = nil, *subject = nil;
1996 Bprint(&out, "!address\n");
2003 subject = addrecolon(m->subject);
2004 av[ai++] = s_to_c(subject);
2007 if(strchr(c->av[0], 'A') != nil){
2009 av[ai++] = "message/rfc822";
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);
2020 ai += tokenize822(s_to_c(to), &av[ai], nelem(av) - ai);
2022 ai += tokenize822(s_to_c(cc), &av[ai], nelem(av) - ai);
2024 if(tomailer(av) < 0)
2035 relpath(char *path, String *to)
2037 if (*path=='/' || strncmp(path, "./", 2) == 0
2038 || strncmp(path, "../", 3) == 0) {
2039 to = s_append(to, path);
2041 to = s_append(to, s_to_c(mbpath));
2042 to->ptr = strrchr(to->base, '/')+1;
2049 appendtofile(Message *m, char *part, char *base, int mbox)
2054 file = extendpath(m->path, part);
2055 in = open(s_to_c(file), OREAD);
2057 fprint(2, "!message disappeared\n");
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);
2069 s_append(file, "att.XXXXXXXXXXX");
2070 mktemp(s_to_c(file));
2074 out = open(s_to_c(file), OWRITE);
2076 out = open(s_to_c(file), OWRITE|OTRUNC);
2078 out = create(s_to_c(file), OWRITE, 0666);
2080 fprint(2, "!can't open %s: %r\n", s_to_c(file));
2089 /* put on a 'From ' line */
2091 while(m->parent != &top)
2093 h = file2string(m->path, "unixheader");
2094 fprint(out, "%s", s_to_c(h));
2098 /* copy the message escaping what we have to ad adding newlines if we have to */
2100 rv = appendfiletombox(in, out);
2102 rv = appendfiletofile(in, out);
2108 print("!saved in %s\n", s_to_c(file));
2114 scmd(Cmd *c, Message *m)
2121 Bprint(&out, "!address\n");
2133 fprint(2, "!usage: s filename\n");
2137 if(file[0] == '/' || (file[0]=='.' && file[1]=='/')){
2138 if(appendtofile(m, "raw", file, 1) < 0)
2141 snprint(buf, sizeof buf, "%s/ctl", mbname);
2142 if((fd = fsopen(mailfs, buf, OWRITE)) == nil)
2144 msg = strrchr(s_to_c(m->path), '/');
2146 msg = s_to_c(m->path);
2149 if(fsprint(fd, "save %s %s", file, msg) < 0){
2160 wcmd(Cmd *c, Message *m)
2165 Bprint(&out, "!address\n");
2174 if(*m->filename == 0){
2175 fprint(2, "!usage: w filename\n");
2178 file = strrchr(m->filename, '/');
2185 fprint(2, "!usage: w filename\n");
2189 if(appendtofile(m, "body", file, 0) < 0)
2195 char *specialfile[] =
2204 /* return 1 if this is a special file */
2211 p = strrchr(s_to_c(s), '/');
2216 for(i = 0; i < nelem(specialfile); i++)
2217 if(strcmp(p, specialfile[i]) == 0)
2222 /* open the folder using the recipients account name */
2224 foldername(char *rcvr)
2233 mboxpath("f", user, file, 0);
2234 d = dirstat(s_to_c(file));
2236 /* if $mail/f exists, store there, otherwise in $mail */
2238 if(d && d->qid.type == QTDIR){
2240 s_append(file, "f/");
2246 p = strrchr(rcvr, '!');
2250 while(*rcvr && *rcvr != '@'){
2258 if(scarey && special(file)){
2259 fprint(2, "!won't overwrite %s\n", s_to_c(file));
2268 fcmd(Cmd *c, Message *m)
2273 fprint(2, "!usage: f takes no arguments\n");
2278 Bprint(&out, "!address\n");
2282 folder = foldername(m->from);
2286 if(appendtofile(m, "raw", s_to_c(folder), 1) < 0){
2297 system(char *cmd, char **av, int in)
2305 if(strcmp(cmd, "rc") == 0)
2306 cmd = unsharp("#9/bin/rc");
2315 fprint(2, "!couldn't exec %s\n", cmd);
2320 while(waitpid() < 0){
2323 postnote(PNPROC, pid, "die");
2331 bangcmd(Cmd *c, Message *m)
2340 e = cmd+sizeof(cmd);
2341 for(i = 1; i < c->an; i++)
2342 p = seprint(p, e, "%s ", c->av[i]);
2347 system("rc", av, -1);
2348 Bprint(&out, "!\n");
2353 xpipecmd(Cmd *c, Message *m, char *part)
2362 Bprint(&out, "!usage: | cmd\n");
2367 Bprint(&out, "!address\n");
2371 path = extendpath(m->path, part);
2372 fd = fsopenfd(mailfs, s_to_c(path), OREAD);
2375 if(fd < 0){ /* compatibility with older upas/fs */
2376 path = extendpath(m->path, "raw");
2377 fd = fsopenfd(mailfs, s_to_c(path), OREAD);
2381 fprint(2, "!message disappeared\n");
2386 e = cmd+sizeof(cmd);
2388 for(i = 1; i < c->an; i++)
2389 p = seprint(p, e, "%s ", c->av[i]);
2394 system("rc", av, fd); /* system closes fd */
2395 Bprint(&out, "!\n");
2400 pipecmd(Cmd *c, Message *m)
2402 return xpipecmd(c, m, "body");
2406 rpipecmd(Cmd *c, Message *m)
2408 return xpipecmd(c, m, "rawunix");
2416 fd = fsopen(mailfs, "ctl", OWRITE);
2418 sysfatal("can't open ctl: %r");
2420 /* close current mailbox */
2421 if(*mbname && strcmp(mbname, "mbox") != 0)
2422 fsprint(fd, "close %s", mbname);
2428 switchmb(char *file, char *singleton)
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))){
2440 fprint(2, "file=%s singleton=%s\n", file, singleton);
2444 /* close current mailbox */
2448 fd = open("/mail/fs/ctl", ORDWR);
2450 sysfatal("can't open /mail/fs/ctl: %r");
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);
2462 s_append(path, buf);
2463 s_append(path, file+1);
2465 mboxpath(file, user, path, 0);
2468 /* make up a handle to use when talking to fs */
2469 p = strrchr(file, '/');
2471 /* if its in the mailbox directory, just use the name */
2472 strncpy(mbname, file, sizeof(mbname));
2473 mbname[sizeof(mbname)-1] = 0;
2475 /* make up a mailbox name */
2476 p = strrchr(s_to_c(path), '/');
2479 fprint(2, "!bad mbox name");
2482 strncpy(mbname, p, sizeof(mbname));
2483 mbname[sizeof(mbname)-1] = 0;
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);
2497 if (singleton && fsaccess(mailfs, singleton, 0)==0){
2498 if ((p = strchr(singleton, '/')) == nil){
2499 fprint(2, "!bad mbox name");
2503 strncpy(mbname, singleton, n);
2505 path = s_reset(nil);
2506 mboxpath(mbname, user, path, 0);
2509 strecpy(mbname, mbname+sizeof mbname, file);
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);
2525 /* like tokenize but for into lines */
2527 lineize(char *s, char **f, int n)
2531 for(i = 0; *s && i < n; i++){
2533 s = strchr(s, '\n');
2546 static char buf[256];
2548 if(strcmp(root, ".") != 0)
2550 snprint(buf, sizeof(buf), "/mail/fs/%s/%s", mbname, s_to_c(s));
2556 plumb(Message *m, Ctype *cp)
2562 if(cp->plumbdest == nil)
2566 fd = plumbopen("send", OWRITE);
2570 pm = mallocz(sizeof(Plumbmsg), 1);
2571 pm->src = strdup("mail");
2573 pm->dst = strdup(cp->plumbdest);
2575 pm->type = strdup("text");
2577 s = rooted(extendpath(m->path, "body"));
2580 s_append(s, cp->ext);
2582 pm->data = strdup(s_to_c(s));
2600 if(cistrncmp(s, "re:", 3) != 0){
2601 str = s_copy("Re: ");