aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/upas/ned/nedmail.c
diff options
context:
space:
mode:
authorrsc <devnull@localhost>2005-10-29 16:26:44 +0000
committerrsc <devnull@localhost>2005-10-29 16:26:44 +0000
commit5cdb17983ae6e6367ad7a940cb219eab247a9304 (patch)
tree8ca1ef49af2a96e7daebe624d91fdf679814a057 /src/cmd/upas/ned/nedmail.c
parentcd3745196389579fb78b9b01ef1daefb5a57aa71 (diff)
downloadplan9port-5cdb17983ae6e6367ad7a940cb219eab247a9304.tar.gz
plan9port-5cdb17983ae6e6367ad7a940cb219eab247a9304.tar.bz2
plan9port-5cdb17983ae6e6367ad7a940cb219eab247a9304.zip
Thanks to John Cummings.
Diffstat (limited to 'src/cmd/upas/ned/nedmail.c')
-rw-r--r--src/cmd/upas/ned/nedmail.c2586
1 files changed, 2586 insertions, 0 deletions
diff --git a/src/cmd/upas/ned/nedmail.c b/src/cmd/upas/ned/nedmail.c
new file mode 100644
index 00000000..4fa7a88f
--- /dev/null
+++ b/src/cmd/upas/ned/nedmail.c
@@ -0,0 +1,2586 @@
+#include "common.h"
+#include <ctype.h>
+#include <plumb.h>
+#include <9pclient.h>
+#include <thread.h>
+
+typedef struct Message Message;
+typedef struct Ctype Ctype;
+typedef struct Cmd Cmd;
+
+char root[Pathlen];
+char mbname[Elemlen];
+int rootlen;
+int didopen;
+char *user;
+char wd[2048];
+String *mbpath;
+int natural;
+int doflush;
+
+int interrupted;
+
+struct Message {
+ Message *next;
+ Message *prev;
+ Message *cmd;
+ Message *child;
+ Message *parent;
+ String *path;
+ int id;
+ int len;
+ int fileno; // number of directory
+ String *info;
+ char *from;
+ char *to;
+ char *cc;
+ char *replyto;
+ char *date;
+ char *subject;
+ char *type;
+ char *disposition;
+ char *filename;
+ char deleted;
+ char stored;
+};
+
+Message top;
+
+struct Ctype {
+ char *type;
+ char *ext;
+ int display;
+ char *plumbdest;
+ Ctype *next;
+};
+
+Ctype ctype[] = {
+ { "text/plain", "txt", 1, 0 },
+ { "text/html", "htm", 1, 0 },
+ { "text/html", "html", 1, 0 },
+ { "text/tab-separated-values", "tsv", 1, 0 },
+ { "text/richtext", "rtx", 1, 0 },
+ { "text/rtf", "rtf", 1, 0 },
+ { "text", "txt", 1, 0 },
+ { "message/rfc822", "msg", 0, 0 },
+ { "image/bmp", "bmp", 0, "image" },
+ { "image/jpeg", "jpg", 0, "image" },
+ { "image/gif", "gif", 0, "image" },
+ { "application/pdf", "pdf", 0, "postscript" },
+ { "application/postscript", "ps", 0, "postscript" },
+ { "application/", 0, 0, 0 },
+ { "image/", 0, 0, 0 },
+ { "multipart/", "mul", 0, 0 },
+
+};
+
+Message* acmd(Cmd*, Message*);
+Message* bcmd(Cmd*, Message*);
+Message* dcmd(Cmd*, Message*);
+Message* eqcmd(Cmd*, Message*);
+Message* hcmd(Cmd*, Message*);
+Message* Hcmd(Cmd*, Message*);
+Message* helpcmd(Cmd*, Message*);
+Message* icmd(Cmd*, Message*);
+Message* pcmd(Cmd*, Message*);
+Message* qcmd(Cmd*, Message*);
+Message* rcmd(Cmd*, Message*);
+Message* scmd(Cmd*, Message*);
+Message* ucmd(Cmd*, Message*);
+Message* wcmd(Cmd*, Message*);
+Message* xcmd(Cmd*, Message*);
+Message* ycmd(Cmd*, Message*);
+Message* pipecmd(Cmd*, Message*);
+Message* rpipecmd(Cmd*, Message*);
+Message* bangcmd(Cmd*, Message*);
+Message* Pcmd(Cmd*, Message*);
+Message* mcmd(Cmd*, Message*);
+Message* fcmd(Cmd*, Message*);
+Message* quotecmd(Cmd*, Message*);
+
+struct {
+ char *cmd;
+ int args;
+ Message* (*f)(Cmd*, Message*);
+ char *help;
+} cmdtab[] = {
+ { "a", 1, acmd, "a reply to sender and recipients" },
+ { "A", 1, acmd, "A reply to sender and recipients with copy" },
+ { "b", 0, bcmd, "b print the next 10 headers" },
+ { "d", 0, dcmd, "d mark for deletion" },
+ { "f", 0, fcmd, "f file message by from address" },
+ { "h", 0, hcmd, "h print elided message summary (,h for all)" },
+ { "help", 0, helpcmd, "help print this info" },
+ { "H", 0, Hcmd, "H print message's MIME structure " },
+ { "i", 0, icmd, "i incorporate new mail" },
+ { "m", 1, mcmd, "m addr forward mail" },
+ { "M", 1, mcmd, "M addr forward mail with message" },
+ { "p", 0, pcmd, "p print the processed message" },
+ { "P", 0, Pcmd, "P print the raw message" },
+ { "\"", 0, quotecmd, "\" print a quoted version of msg" },
+ { "q", 0, qcmd, "q exit and remove all deleted mail" },
+ { "r", 1, rcmd, "r [addr] reply to sender plus any addrs specified" },
+ { "rf", 1, rcmd, "rf [addr]file message and reply" },
+ { "R", 1, rcmd, "R [addr] reply including copy of message" },
+ { "Rf", 1, rcmd, "Rf [addr]file message and reply with copy" },
+ { "s", 1, scmd, "s file append raw message to file" },
+ { "u", 0, ucmd, "u remove deletion mark" },
+ { "w", 1, wcmd, "w file store message contents as file" },
+ { "x", 0, xcmd, "x exit without flushing deleted messages" },
+ { "y", 0, ycmd, "y synchronize with mail box" },
+ { "=", 1, eqcmd, "= print current message number" },
+ { "|", 1, pipecmd, "|cmd pipe message body to a command" },
+ { "||", 1, rpipecmd, "||cmd pipe raw message to a command" },
+ { "!", 1, bangcmd, "!cmd run a command" },
+ { nil, 0, nil, nil },
+};
+
+enum
+{
+ NARG= 32,
+};
+
+struct Cmd {
+ Message *msgs;
+ Message *(*f)(Cmd*, Message*);
+ int an;
+ char *av[NARG];
+ int delete;
+};
+
+Biobuf out;
+int startedfs;
+int reverse;
+int longestfrom = 12;
+
+String* file2string(String*, char*);
+int dir2message(Message*, int);
+int filelen(String*, char*);
+String* extendpath(String*, char*);
+void snprintheader(char*, int, Message*);
+void cracktime(char*, char*, int);
+int cistrncmp(char*, char*, int);
+int cistrcmp(char*, char*);
+Reprog* parsesearch(char**);
+char* parseaddr(char**, Message*, Message*, Message*, Message**);
+char* parsecmd(char*, Cmd*, Message*, Message*);
+char* readline(char*, char*, int);
+void messagecount(Message*);
+void system9(char*, char**, int);
+void mkid(String*, Message*);
+int switchmb(char*, char*);
+void closemb(void);
+int lineize(char*, char**, int);
+int rawsearch(Message*, Reprog*);
+Message* dosingleton(Message*, char*);
+String* rooted(String*);
+int plumb(Message*, Ctype*);
+String* addrecolon(char*);
+void exitfs(char*);
+Message* flushdeleted(Message*);
+
+CFsys *upasfs;
+
+void
+usage(void)
+{
+ fprint(2, "usage: %s [-nr] [-f mailfile] [-s mailfile]\n", argv0);
+ fprint(2, " %s -c dir\n", argv0);
+ threadexits("usage");
+}
+
+void
+catchnote(void* dummy, char *note)
+{
+ if(strstr(note, "interrupt") != nil){
+ interrupted = 1;
+ noted(NCONT);
+ }
+ noted(NDFLT);
+}
+
+char *
+plural(int n)
+{
+ if (n == 1)
+ return "";
+
+ return "s";
+}
+
+void
+threadmain(int argc, char **argv)
+{
+ Message *cur, *m, *x;
+ char cmdline[4*1024];
+ Cmd cmd;
+ Ctype *cp;
+ char *err;
+ int n, cflag;
+ char *av[4];
+ String *prompt;
+ char *file, *singleton;
+ char *fscmd;
+
+ Binit(&out, 1, OWRITE);
+
+ file = nil;
+ singleton = nil;
+ reverse = 1;
+ cflag = 0;
+ ARGBEGIN {
+ case 'c':
+ cflag = 1;
+ break;
+ case 'f':
+ file = EARGF(usage());
+ break;
+ case 's':
+ singleton = EARGF(usage());
+ break;
+ case 'r':
+ reverse = 0;
+ break;
+ case 'n':
+ natural = 1;
+ reverse = 0;
+ break;
+ default:
+ usage();
+ break;
+ } ARGEND;
+
+ user = getlog();
+ if(user == nil || *user == 0)
+ sysfatal("can't read user name");
+
+ if(cflag){
+ if(argc > 0)
+ creatembox(user, argv[0]);
+ else
+ creatembox(user, nil);
+ threadexits(0);
+ }
+
+ if(argc)
+ usage();
+
+#if 0 /* jpc */
+ if(access("/mail/fs/ctl", 0) < 0){
+ startedfs = 1;
+ av[0] = "fs";
+ av[1] = "-p";
+ av[2] = 0;
+ system9("/bin/upas/fs", av, -1);
+ }
+#endif
+ if( (upasfs = nsmount("upasfs", nil)) == nil ) {
+ startedfs = 1;
+ av[0] = "fs";
+ av[1] = "-p";
+ av[2] = 0;
+ fscmd = unsharp("#9/bin/upas/fs");
+ system9(fscmd, av, -1);
+ }
+
+fprint(2,"switchmb\n");
+ switchmb(file, singleton);
+fprint(2,"switchmb2\n");
+
+ top.path = s_copy(root);
+
+ for(cp = ctype; cp < ctype+nelem(ctype)-1; cp++)
+ cp->next = cp+1;
+
+ if(singleton != nil){
+ cur = dosingleton(&top, singleton);
+ if(cur == nil){
+ Bprint(&out, "no message\n");
+ exitfs(0);
+ }
+ pcmd(nil, cur);
+ } else {
+ cur = &top;
+ n = dir2message(&top, reverse);
+ if(n < 0)
+ sysfatal("can't read %s", s_to_c(top.path));
+ Bprint(&out, "%d message%s\n", n, plural(n));
+ }
+
+
+ notify(catchnote);
+ prompt = s_new();
+ for(;;){
+ s_reset(prompt);
+ if(cur == &top)
+ s_append(prompt, ": ");
+ else {
+ mkid(prompt, cur);
+ s_append(prompt, ": ");
+ }
+
+ // leave space at the end of cmd line in case parsecmd needs to
+ // add a space after a '|' or '!'
+ if(readline(s_to_c(prompt), cmdline, sizeof(cmdline)-1) == nil)
+ break;
+ err = parsecmd(cmdline, &cmd, top.child, cur);
+ if(err != nil){
+ Bprint(&out, "!%s\n", err);
+ continue;
+ }
+ if(singleton != nil && cmd.f == icmd){
+ Bprint(&out, "!illegal command\n");
+ continue;
+ }
+ interrupted = 0;
+ if(cmd.msgs == nil || cmd.msgs == &top){
+ x = (*cmd.f)(&cmd, &top);
+ if(x != nil)
+ cur = x;
+ } else for(m = cmd.msgs; m != nil; m = m->cmd){
+ x = m;
+ if(cmd.delete){
+ dcmd(&cmd, x);
+
+ // dp acts differently than all other commands
+ // since its an old lesk idiom that people love.
+ // it deletes the current message, moves the current
+ // pointer ahead one and prints.
+ if(cmd.f == pcmd){
+ if(x->next == nil){
+ Bprint(&out, "!address\n");
+ cur = x;
+ break;
+ } else
+ x = x->next;
+ }
+ }
+ x = (*cmd.f)(&cmd, x);
+ if(x != nil)
+ cur = x;
+ if(interrupted)
+ break;
+ if(singleton != nil && (cmd.delete || cmd.f == dcmd))
+ qcmd(nil, nil);
+ }
+ if(doflush)
+ cur = flushdeleted(cur);
+ }
+ qcmd(nil, nil);
+}
+
+//
+// read the message info
+//
+Message*
+file2message(Message *parent, char *name)
+{
+ Message *m;
+ String *path;
+ char *f[10];
+
+ m = mallocz(sizeof(Message), 1);
+ if(m == nil)
+ return nil;
+ m->path = path = extendpath(parent->path, name);
+ m->fileno = atoi(name);
+ m->info = file2string(path, "info");
+ lineize(s_to_c(m->info), f, nelem(f));
+ m->from = f[0];
+ m->to = f[1];
+ m->cc = f[2];
+ m->replyto = f[3];
+ m->date = f[4];
+ m->subject = f[5];
+ m->type = f[6];
+ m->disposition = f[7];
+ m->filename = f[8];
+ m->len = filelen(path, "raw");
+ if(strstr(m->type, "multipart") != nil || strcmp(m->type, "message/rfc822") == 0)
+ dir2message(m, 0);
+ m->parent = parent;
+
+ return m;
+}
+
+void
+freemessage(Message *m)
+{
+ Message *nm, *next;
+
+ for(nm = m->child; nm != nil; nm = next){
+ next = nm->next;
+ freemessage(nm);
+ }
+ s_free(m->path);
+ s_free(m->info);
+ free(m);
+}
+
+//
+// read a directory into a list of messages
+//
+int
+dir2message(Message *parent, int reverse)
+{
+//jpc int i, n, fd, highest, newmsgs;
+ int i, n, highest, newmsgs;
+ CFid *fid;
+
+ Dir *d;
+ Message *first, *last, *m;
+
+/* fd = open(s_to_c(parent->path), OREAD);
+ if(fd < 0)
+ return -1; jpc */
+ fid = fsopen(upasfs, s_to_c(parent->path), OREAD);
+ if(fid == nil)
+ return -1;
+
+ // count current entries
+ first = parent->child;
+ highest = newmsgs = 0;
+ for(last = parent->child; last != nil && last->next != nil; last = last->next)
+ if(last->fileno > highest)
+ highest = last->fileno;
+ if(last != nil)
+ if(last->fileno > highest)
+ highest = last->fileno;
+
+ n = fsdirreadall(fid, &d);
+ fprint(2,"read %d messages\n", n);
+ for(i = 0; i < n; i++){
+ if((d[i].qid.type & QTDIR) == 0)
+ continue;
+ if(atoi(d[i].name) <= highest)
+ continue;
+ fprint(2,"calling file2message %d\n", i);
+ m = file2message(parent, d[i].name);
+ // fprint(2,"returned from file2message\n");
+ if(m == nil)
+ break;
+ newmsgs++;
+ if(reverse){
+ m->next = first;
+ if(first != nil)
+ first->prev = m;
+ first = m;
+ } else {
+ if(first == nil)
+ first = m;
+ else
+ last->next = m;
+ m->prev = last;
+ last = m;
+ }
+ }
+ fprint(2,"exiting loop\n");
+ free(d);
+ fprint(2,"close fid\n");
+ fsclose(fid);
+ fprint(2,"fid closed\n");
+ parent->child = first;
+
+ // renumber and file longest from
+ i = 1;
+ longestfrom = 12;
+ for(m = first; m != nil; m = m->next){
+ fprint(2,"m:%x from: %s\n", m, m->from);
+ m->id = natural ? m->fileno : i++;
+ n = strlen(m->from);
+ fprint(2,"in loop\n");
+ if(n > longestfrom)
+ longestfrom = n;
+ }
+ fprint(2,"exiting dir2message\n");
+
+ return newmsgs;
+}
+
+//
+// point directly to a message
+//
+Message*
+dosingleton(Message *parent, char *path)
+{
+ char *p, *np;
+ Message *m;
+
+ // walk down to message and read it
+ if(strlen(path) < rootlen)
+ return nil;
+ if(path[rootlen] != '/')
+ return nil;
+ p = path+rootlen+1;
+ np = strchr(p, '/');
+ if(np != nil)
+ *np = 0;
+ m = file2message(parent, p);
+ if(m == nil)
+ return nil;
+ parent->child = m;
+ m->id = 1;
+
+ // walk down to requested component
+ while(np != nil){
+ *np = '/';
+ np = strchr(np+1, '/');
+ if(np != nil)
+ *np = 0;
+ for(m = m->child; m != nil; m = m->next)
+ if(strcmp(path, s_to_c(m->path)) == 0)
+ return m;
+ if(m == nil)
+ return nil;
+ }
+ return m;
+}
+
+//
+// read a file into a string
+//
+String*
+file2string(String *dir, char *file)
+{
+ String *s;
+//jpc int fd, n, m;
+ int n, m;
+ CFid *fid;
+
+ s = extendpath(dir, file);
+// jpc fd = open(s_to_c(s), OREAD);
+ fid = fsopen(upasfs,s_to_c(s), OREAD);
+ s_grow(s, 512); /* avoid multiple reads on info files */
+ s_reset(s);
+//jpc if(fd < 0)
+ if(fid == nil)
+ return s;
+
+ for(;;){
+ n = s->end - s->ptr;
+ if(n == 0){
+ s_grow(s, 128);
+ continue;
+ }
+//jpc m = read(fd, s->ptr, n);
+ m = fsread(fid, s->ptr, n);
+ if(m <= 0)
+ break;
+ s->ptr += m;
+ if(m < n)
+ break;
+ }
+ s_terminate(s);
+//jpc close(fd);
+ fsclose(fid);
+
+ return s;
+}
+
+//
+// get the length of a file
+//
+int
+filelen(String *dir, char *file)
+{
+ String *path;
+ Dir *d;
+ int rv;
+
+ path = extendpath(dir, file);
+//jpc d = dirstat(s_to_c(path));
+ d = fsdirstat(upasfs,s_to_c(path));
+ if(d == nil){
+ s_free(path);
+ return -1;
+ }
+ s_free(path);
+ rv = d->length;
+ free(d);
+ return rv;
+}
+
+//
+// walk the path name an element
+//
+String*
+extendpath(String *dir, char *name)
+{
+ String *path;
+
+ if(strcmp(s_to_c(dir), ".") == 0)
+ path = s_new();
+ else {
+ path = s_copy(s_to_c(dir));
+ s_append(path, "/");
+ }
+ s_append(path, name);
+ return path;
+}
+
+int
+cistrncmp(char *a, char *b, int n)
+{
+ while(n-- > 0){
+ if(tolower(*a++) != tolower(*b++))
+ return -1;
+ }
+ return 0;
+}
+
+int
+cistrcmp(char *a, char *b)
+{
+ for(;;){
+ if(tolower(*a) != tolower(*b++))
+ return -1;
+ if(*a++ == 0)
+ break;
+ }
+ return 0;
+}
+
+char*
+nosecs(char *t)
+{
+ char *p;
+
+ p = strchr(t, ':');
+ if(p == nil)
+ return t;
+ p = strchr(p+1, ':');
+ if(p != nil)
+ *p = 0;
+ return t;
+}
+
+char *months[12] =
+{
+ "jan", "feb", "mar", "apr", "may", "jun",
+ "jul", "aug", "sep", "oct", "nov", "dec"
+};
+
+int
+month(char *m)
+{
+ int i;
+
+ for(i = 0; i < 12; i++)
+ if(cistrcmp(m, months[i]) == 0)
+ return i+1;
+ return 1;
+}
+
+enum
+{
+ Yearsecs= 365*24*60*60
+};
+
+void
+cracktime(char *d, char *out, int len)
+{
+ char in[64];
+ char *f[6];
+ int n;
+ Tm tm;
+ long now, then;
+ char *dtime;
+
+ *out = 0;
+ if(d == nil)
+ return;
+ strncpy(in, d, sizeof(in));
+ in[sizeof(in)-1] = 0;
+ n = getfields(in, f, 6, 1, " \t\r\n");
+ if(n != 6){
+ // unknown style
+ snprint(out, 16, "%10.10s", d);
+ return;
+ }
+ now = time(0);
+ memset(&tm, 0, sizeof tm);
+ if(strchr(f[0], ',') != nil && strchr(f[4], ':') != nil){
+ // 822 style
+ tm.year = atoi(f[3])-1900;
+ tm.mon = month(f[2]);
+ tm.mday = atoi(f[1]);
+ dtime = nosecs(f[4]);
+ then = tm2sec(&tm);
+ } else if(strchr(f[3], ':') != nil){
+ // unix style
+ tm.year = atoi(f[5])-1900;
+ tm.mon = month(f[1]);
+ tm.mday = atoi(f[2]);
+ dtime = nosecs(f[3]);
+ then = tm2sec(&tm);
+ } else {
+ then = now;
+ tm = *localtime(now);
+ dtime = "";
+ }
+
+ if(now - then < Yearsecs/2)
+ snprint(out, len, "%2d/%2.2d %s", tm.mon, tm.mday, dtime);
+ else
+ snprint(out, len, "%2d/%2.2d %4d", tm.mon, tm.mday, tm.year+1900);
+}
+
+Ctype*
+findctype(Message *m)
+{
+ char *p;
+ char ftype[128];
+ int n, pfd[2];
+ Ctype *a, *cp;
+ /* static Ctype nulltype = { "", 0, 0, 0 }; jpc */
+ static Ctype bintype = { "application/octet-stream", "bin", 0, 0 };
+
+ for(cp = ctype; cp; cp = cp->next)
+ if(strncmp(cp->type, m->type, strlen(cp->type)) == 0)
+ return cp;
+
+/* use file(1) for any unknown mimetypes
+ *
+ * if (strcmp(m->type, bintype.type) != 0)
+ * return &nulltype;
+ */
+ if(pipe(pfd) < 0)
+ return &bintype;
+
+ *ftype = 0;
+ switch(fork()){
+ case -1:
+ break;
+ case 0:
+ close(pfd[1]);
+ close(0);
+ dup(pfd[0], 0);
+ close(1);
+ dup(pfd[0], 1);
+//jpc execl("/bin/file", "file", "-m", s_to_c(extendpath(m->path, "body")), nil);
+ execl(unsharp("#9/bin/file"), "file", "-m", s_to_c(extendpath(m->path, "body")), nil);
+ threadexits(0);
+ default:
+ close(pfd[0]);
+ n = read(pfd[1], ftype, sizeof(ftype));
+ if(n > 0)
+ ftype[n] = 0;
+ close(pfd[1]);
+ waitpid();
+ break;
+ }
+
+ if (*ftype=='\0' || (p = strchr(ftype, '/')) == nil)
+ return &bintype;
+ *p++ = 0;
+
+ a = mallocz(sizeof(Ctype), 1);
+ a->type = strdup(ftype);
+ a->ext = strdup(p);
+ a->display = 0;
+ a->plumbdest = strdup(ftype);
+ for(cp = ctype; cp->next; cp = cp->next)
+ continue;
+ cp->next = a;
+ a->next = nil;
+ return a;
+}
+
+void
+mkid(String *s, Message *m)
+{
+ char buf[32];
+
+ if(m->parent != &top){
+ mkid(s, m->parent);
+ s_append(s, ".");
+ }
+ sprint(buf, "%d", m->id);
+ s_append(s, buf);
+}
+
+void
+snprintheader(char *buf, int len, Message *m)
+{
+ char timebuf[32];
+ String *id;
+ char *p, *q;;
+
+ // create id
+ id = s_new();
+ mkid(id, m);
+
+ if(*m->from == 0){
+ // no from
+ snprint(buf, len, "%-3s %s %6d %s",
+ s_to_c(id),
+ m->type,
+ m->len,
+ m->filename);
+ } else if(*m->subject){
+ q = p = strdup(m->subject);
+ while(*p == ' ')
+ p++;
+ if(strlen(p) > 50)
+ p[50] = 0;
+ cracktime(m->date, timebuf, sizeof(timebuf));
+ snprint(buf, len, "%-3s %c%c%c %6d %11.11s %-*.*s %s",
+ s_to_c(id),
+ m->child ? 'H' : ' ',
+ m->deleted ? 'd' : ' ',
+ m->stored ? 's' : ' ',
+ m->len,
+ timebuf,
+ longestfrom, longestfrom, m->from,
+ p);
+ free(q);
+ } else {
+ cracktime(m->date, timebuf, sizeof(timebuf));
+ snprint(buf, len, "%-3s %c%c%c %6d %11.11s %s",
+ s_to_c(id),
+ m->child ? 'H' : ' ',
+ m->deleted ? 'd' : ' ',
+ m->stored ? 's' : ' ',
+ m->len,
+ timebuf,
+ m->from);
+ }
+ s_free(id);
+}
+
+char *spaces = " ";
+
+void
+snprintHeader(char *buf, int len, int indent, Message *m)
+{
+ String *id;
+ char typeid[64];
+ char *p, *e;
+
+ // create id
+ id = s_new();
+ mkid(id, m);
+
+ e = buf + len;
+
+ snprint(typeid, sizeof typeid, "%s %s", s_to_c(id), m->type);
+ if(indent < 6)
+ p = seprint(buf, e, "%-32s %-6d ", typeid, m->len);
+ else
+ p = seprint(buf, e, "%-64s %-6d ", typeid, m->len);
+ if(m->filename && *m->filename)
+ p = seprint(p, e, "(file,%s)", m->filename);
+ if(m->from && *m->from)
+ p = seprint(p, e, "(from,%s)", m->from);
+ if(m->subject && *m->subject)
+ seprint(p, e, "(subj,%s)", m->subject);
+
+ s_free(id);
+}
+
+char sstring[256];
+
+// cmd := range cmd ' ' arg-list ;
+// range := address
+// | address ',' address
+// | 'g' search ;
+// address := msgno
+// | search ;
+// msgno := number
+// | number '/' msgno ;
+// search := '/' string '/'
+// | '%' string '%' ;
+//
+Reprog*
+parsesearch(char **pp)
+{
+ char *p, *np;
+ int c, n;
+
+ p = *pp;
+ c = *p++;
+ np = strchr(p, c);
+ if(np != nil){
+ *np++ = 0;
+ *pp = np;
+ } else {
+ n = strlen(p);
+ *pp = p + n;
+ }
+ if(*p == 0)
+ p = sstring;
+ else{
+ strncpy(sstring, p, sizeof(sstring));
+ sstring[sizeof(sstring)-1] = 0;
+ }
+ return regcomp(p);
+}
+
+char*
+parseaddr(char **pp, Message *first, Message *cur, Message *unspec, Message **mp)
+{
+ int n;
+ Message *m;
+ char *p;
+ Reprog *prog;
+ int c, sign;
+ char buf[256];
+
+ *mp = nil;
+ p = *pp;
+
+ if(*p == '+'){
+ sign = 1;
+ p++;
+ *pp = p;
+ } else if(*p == '-'){
+ sign = -1;
+ p++;
+ *pp = p;
+ } else
+ sign = 0;
+
+ switch(*p){
+ default:
+ if(sign){
+ n = 1;
+ goto number;
+ }
+ *mp = unspec;
+ break;
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ n = strtoul(p, pp, 10);
+ if(n == 0){
+ if(sign)
+ *mp = cur;
+ else
+ *mp = &top;
+ break;
+ }
+ number:
+ m = nil;
+ switch(sign){
+ case 0:
+ for(m = first; m != nil; m = m->next)
+ if(m->id == n)
+ break;
+ break;
+ case -1:
+ if(cur != &top)
+ for(m = cur; m != nil && n > 0; n--)
+ m = m->prev;
+ break;
+ case 1:
+ if(cur == &top){
+ n--;
+ cur = first;
+ }
+ for(m = cur; m != nil && n > 0; n--)
+ m = m->next;
+ break;
+ }
+ if(m == nil)
+ return "address";
+ *mp = m;
+ break;
+ case '%':
+ case '/':
+ case '?':
+ c = *p;
+ prog = parsesearch(pp);
+ if(prog == nil)
+ return "badly formed regular expression";
+ m = nil;
+ switch(c){
+ case '%':
+ for(m = cur == &top ? first : cur->next; m != nil; m = m->next){
+ if(rawsearch(m, prog))
+ break;
+ }
+ break;
+ case '/':
+ for(m = cur == &top ? first : cur->next; m != nil; m = m->next){
+ snprintheader(buf, sizeof(buf), m);
+ if(regexec(prog, buf, nil, 0))
+ break;
+ }
+ break;
+ case '?':
+ for(m = cur == &top ? nil : cur->prev; m != nil; m = m->prev){
+ snprintheader(buf, sizeof(buf), m);
+ if(regexec(prog, buf, nil, 0))
+ break;
+ }
+ break;
+ }
+ if(m == nil)
+ return "search";
+ *mp = m;
+ free(prog);
+ break;
+ case '$':
+ for(m = first; m != nil && m->next != nil; m = m->next)
+ ;
+ *mp = m;
+ *pp = p+1;
+ break;
+ case '.':
+ *mp = cur;
+ *pp = p+1;
+ break;
+ case ',':
+ *mp = first;
+ *pp = p;
+ break;
+ }
+
+ if(*mp != nil && **pp == '.'){
+ (*pp)++;
+ if((*mp)->child == nil)
+ return "no sub parts";
+ return parseaddr(pp, (*mp)->child, (*mp)->child, (*mp)->child, mp);
+ }
+ if(**pp == '+' || **pp == '-' || **pp == '/' || **pp == '%')
+ return parseaddr(pp, first, *mp, *mp, mp);
+
+ return nil;
+}
+
+//
+// search a message for a regular expression match
+//
+int
+rawsearch(Message *m, Reprog *prog)
+{
+ char buf[4096+1];
+//jpc int i, fd, rv;
+ int i, rv;
+ CFid *fid;
+ String *path;
+
+ path = extendpath(m->path, "raw");
+//jpc fd = open(s_to_c(path), OREAD);
+//jpc if(fd < 0)
+//jpc return 0;
+ fid = fsopen(upasfs,s_to_c(path), OREAD);
+ if(fid == nil)
+ return 0;
+
+ // march through raw message 4096 bytes at a time
+ // with a 128 byte overlap to chain the re search.
+ rv = 0;
+ for(;;){
+//jpc i = read(fd, buf, sizeof(buf)-1);
+ i = fsread(fid, buf, sizeof(buf)-1);
+ if(i <= 0)
+ break;
+ buf[i] = 0;
+ if(regexec(prog, buf, nil, 0)){
+ rv = 1;
+ break;
+ }
+ if(i < sizeof(buf)-1)
+ break;
+//jpc if(seek(fd, -128LL, 1) < 0)
+ if(fsseek(fid, -128LL, 1) < 0)
+ break;
+ }
+
+ fsclose(fid);
+ s_free(path);
+ return rv;
+}
+
+
+char*
+parsecmd(char *p, Cmd *cmd, Message *first, Message *cur)
+{
+ Reprog *prog;
+ Message *m, *s, *e, **l, *last;
+ char buf[256];
+ char *err;
+ int i, c;
+ char *q;
+ static char errbuf[Errlen];
+
+ cmd->delete = 0;
+ l = &cmd->msgs;
+ *l = nil;
+
+ // eat white space
+ while(*p == ' ')
+ p++;
+
+ // null command is a special case (advance and print)
+ if(*p == 0){
+ if(cur == &top){
+ // special case
+ m = first;
+ } else {
+ // walk to the next message even if we have to go up
+ m = cur->next;
+ while(m == nil && cur->parent != nil){
+ cur = cur->parent;
+ m = cur->next;
+ }
+ }
+ if(m == nil)
+ return "address";
+ *l = m;
+ m->cmd = nil;
+ cmd->an = 0;
+ cmd->f = pcmd;
+ return nil;
+ }
+
+ // global search ?
+ if(*p == 'g'){
+ p++;
+
+ // no search string means all messages
+ if(*p != '/' && *p != '%'){
+ for(m = first; m != nil; m = m->next){
+ *l = m;
+ l = &m->cmd;
+ *l = nil;
+ }
+ } else {
+ // mark all messages matching this search string
+ c = *p;
+ prog = parsesearch(&p);
+ if(prog == nil)
+ return "badly formed regular expression";
+ if(c == '%'){
+ for(m = first; m != nil; m = m->next){
+ if(rawsearch(m, prog)){
+ *l = m;
+ l = &m->cmd;
+ *l = nil;
+ }
+ }
+ } else {
+ for(m = first; m != nil; m = m->next){
+ snprintheader(buf, sizeof(buf), m);
+ if(regexec(prog, buf, nil, 0)){
+ *l = m;
+ l = &m->cmd;
+ *l = nil;
+ }
+ }
+ }
+ free(prog);
+ }
+ } else {
+
+ // parse an address
+ s = e = nil;
+ err = parseaddr(&p, first, cur, cur, &s);
+ if(err != nil)
+ return err;
+ if(*p == ','){
+ // this is an address range
+ if(s == &top)
+ s = first;
+ p++;
+ for(last = s; last != nil && last->next != nil; last = last->next)
+ ;
+ err = parseaddr(&p, first, cur, last, &e);
+ if(err != nil)
+ return err;
+
+ // select all messages in the range
+ for(; s != nil; s = s->next){
+ *l = s;
+ l = &s->cmd;
+ *l = nil;
+ if(s == e)
+ break;
+ }
+ if(s == nil)
+ return "null address range";
+ } else {
+ // single address
+ if(s != &top){
+ *l = s;
+ s->cmd = nil;
+ }
+ }
+ }
+
+ // insert a space after '!'s and '|'s
+ for(q = p; *q; q++)
+ if(*q != '!' && *q != '|')
+ break;
+ if(q != p && *q != ' '){
+ memmove(q+1, q, strlen(q)+1);
+ *q = ' ';
+ }
+
+ cmd->an = getfields(p, cmd->av, nelem(cmd->av) - 1, 1, " \t\r\n");
+ if(cmd->an == 0 || *cmd->av[0] == 0)
+ cmd->f = pcmd;
+ else {
+ // hack to allow all messages to start with 'd'
+ if(*(cmd->av[0]) == 'd' && *(cmd->av[0]+1) != 0){
+ cmd->delete = 1;
+ cmd->av[0]++;
+ }
+
+ // search command table
+ for(i = 0; cmdtab[i].cmd != nil; i++)
+ if(strcmp(cmd->av[0], cmdtab[i].cmd) == 0)
+ break;
+ if(cmdtab[i].cmd == nil)
+ return "illegal command";
+ if(cmdtab[i].args == 0 && cmd->an > 1){
+ snprint(errbuf, sizeof(errbuf), "%s doesn't take an argument", cmdtab[i].cmd);
+ return errbuf;
+ }
+ cmd->f = cmdtab[i].f;
+ }
+ return nil;
+}
+
+// inefficient read from standard input
+char*
+readline(char *prompt, char *line, int len)
+{
+ char *p, *e;
+ int n;
+
+retry:
+ interrupted = 0;
+ Bprint(&out, "%s", prompt);
+ Bflush(&out);
+ e = line + len;
+ for(p = line; p < e; p++){
+ n = read(0, p, 1);
+ if(n < 0){
+ if(interrupted)
+ goto retry;
+ return nil;
+ }
+ if(n == 0)
+ return nil;
+ if(*p == '\n')
+ break;
+ }
+ *p = 0;
+ return line;
+}
+
+void
+messagecount(Message *m)
+{
+ int i;
+
+ i = 0;
+ for(; m != nil; m = m->next)
+ i++;
+ Bprint(&out, "%d message%s\n", i, plural(i));
+}
+
+Message*
+aichcmd(Message *m, int indent)
+{
+ char hdr[256];
+
+ if(m == &top)
+ return nil;
+
+ snprintHeader(hdr, sizeof(hdr), indent, m);
+ Bprint(&out, "%s\n", hdr);
+ for(m = m->child; m != nil; m = m->next)
+ aichcmd(m, indent+1);
+ return nil;
+}
+
+Message*
+Hcmd(Cmd* dummy, Message *m)
+{
+ if(m == &top)
+ return nil;
+ aichcmd(m, 0);
+ return nil;
+}
+
+Message*
+hcmd(Cmd* dummy, Message *m)
+{
+ char hdr[256];
+
+ if(m == &top)
+ return nil;
+
+ snprintheader(hdr, sizeof(hdr), m);
+ Bprint(&out, "%s\n", hdr);
+ return nil;
+}
+
+Message*
+bcmd(Cmd* dummy, Message *m)
+{
+ int i;
+ Message *om = m;
+
+ if(m == &top)
+ m = top.child;
+ for(i = 0; i < 10 && m != nil; i++){
+ hcmd(nil, m);
+ om = m;
+ m = m->next;
+ }
+
+ return om;
+}
+
+Message*
+ncmd(Cmd* dummy, Message *m)
+{
+ if(m == &top)
+ return m->child;
+ return m->next;
+}
+
+int
+printpart(String *s, char *part)
+{
+ char buf[4096];
+//jpc int n, fd, tot;
+ int n, tot;
+ CFid *fid;
+ String *path;
+
+ path = extendpath(s, part);
+//jpc fd = open(s_to_c(path), OREAD);
+ fid = fsopen(upasfs,s_to_c(path), OREAD);
+ s_free(path);
+//jpc if(fd < 0){
+ if(fid == nil){
+ fprint(2, "!message dissappeared\n");
+ return 0;
+ }
+ tot = 0;
+//jpc while((n = read(fd, buf, sizeof(buf))) > 0){
+ while((n = fsread(fid, buf, sizeof(buf))) > 0){
+ if(interrupted)
+ break;
+ if(Bwrite(&out, buf, n) <= 0)
+ break;
+ tot += n;
+ }
+ fsclose(fid);
+ return tot;
+}
+
+int
+printhtml(Message *m)
+{
+ Cmd c;
+
+ c.an = 3;
+ c.av[1] = unsharp("#9/bin/htmlfmt");
+ c.av[2] = "-l 40 -cutf-8";
+ Bprint(&out, "!%s\n", c.av[1]);
+ Bflush(&out);
+ pipecmd(&c, m);
+ return 0;
+}
+
+Message*
+Pcmd(Cmd* dummy, Message *m)
+{
+ if(m == &top)
+ return &top;
+ if(m->parent == &top)
+ printpart(m->path, "unixheader");
+ printpart(m->path, "raw");
+ return m;
+}
+
+void
+compress(char *p)
+{
+ char *np;
+ int last;
+
+ last = ' ';
+ for(np = p; *p; p++){
+ if(*p != ' ' || last != ' '){
+ last = *p;
+ *np++ = last;
+ }
+ }
+ *np = 0;
+}
+
+Message*
+pcmd(Cmd* dummy, Message *m)
+{
+ Message *nm;
+ Ctype *cp;
+ String *s;
+ char buf[128];
+
+ if(m == &top)
+ return &top;
+ if(m->parent == &top)
+ printpart(m->path, "unixheader");
+ if(printpart(m->path, "header") > 0)
+ Bprint(&out, "\n");
+ cp = findctype(m);
+ if(cp->display){
+ if(strcmp(m->type, "text/html") == 0)
+ printhtml(m);
+ else
+ printpart(m->path, "body");
+ } else if(strcmp(m->type, "multipart/alternative") == 0){
+ for(nm = m->child; nm != nil; nm = nm->next){
+ cp = findctype(nm);
+ if(cp->ext != nil && strncmp(cp->ext, "txt", 3) == 0)
+ break;
+ }
+ if(nm == nil)
+ for(nm = m->child; nm != nil; nm = nm->next){
+ cp = findctype(nm);
+ if(cp->display)
+ break;
+ }
+ if(nm != nil)
+ pcmd(nil, nm);
+ else
+ hcmd(nil, m);
+ } else if(strncmp(m->type, "multipart/", 10) == 0){
+ nm = m->child;
+ if(nm != nil){
+ // always print first part
+ pcmd(nil, nm);
+
+ for(nm = nm->next; nm != nil; nm = nm->next){
+ s = rooted(s_clone(nm->path));
+ cp = findctype(nm);
+ snprintHeader(buf, sizeof buf, -1, nm);
+ compress(buf);
+ if(strcmp(nm->disposition, "inline") == 0){
+ if(cp->ext != nil)
+ Bprint(&out, "\n--- %s %s/body.%s\n\n",
+ buf, s_to_c(s), cp->ext);
+ else
+ Bprint(&out, "\n--- %s %s/body\n\n",
+ buf, s_to_c(s));
+ pcmd(nil, nm);
+ } else {
+ if(cp->ext != nil)
+ Bprint(&out, "\n!--- %s %s/body.%s\n",
+ buf, s_to_c(s), cp->ext);
+ else
+ Bprint(&out, "\n!--- %s %s/body\n",
+ buf, s_to_c(s));
+ }
+ s_free(s);
+ }
+ } else {
+ hcmd(nil, m);
+ }
+ } else if(strcmp(m->type, "message/rfc822") == 0){
+ pcmd(nil, m->child);
+ } else if(plumb(m, cp) >= 0)
+ Bprint(&out, "\n!--- using plumber to display message of type %s\n", m->type);
+ else
+ Bprint(&out, "\n!--- cannot display messages of type %s\n", m->type);
+
+ return m;
+}
+
+void
+printpartindented(String *s, char *part, char *indent)
+{
+ char *p;
+ String *path;
+ Biobuf *b;
+
+ fprint(2,"printpartindented: fixme\n");
+ path = extendpath(s, part);
+ b = Bopen(s_to_c(path), OREAD);
+ s_free(path);
+ if(b == nil){
+ fprint(2, "!message dissappeared\n");
+ return;
+ }
+ while((p = Brdline(b, '\n')) != nil){
+ if(interrupted)
+ break;
+ p[Blinelen(b)-1] = 0;
+ if(Bprint(&out, "%s%s\n", indent, p) <= 0)
+ break;
+ }
+ Bprint(&out, "\n");
+ Bterm(b);
+}
+
+Message*
+quotecmd(Cmd* dummy, Message *m)
+{
+ Message *nm;
+ Ctype *cp;
+
+ if(m == &top)
+ return &top;
+ Bprint(&out, "\n");
+ if(m->from != nil && *m->from)
+ Bprint(&out, "On %s, %s wrote:\n", m->date, m->from);
+ cp = findctype(m);
+ if(cp->display){
+ printpartindented(m->path, "body", "> ");
+ } else if(strcmp(m->type, "multipart/alternative") == 0){
+ for(nm = m->child; nm != nil; nm = nm->next){
+ cp = findctype(nm);
+ if(cp->ext != nil && strncmp(cp->ext, "txt", 3) == 0)
+ break;
+ }
+ if(nm == nil)
+ for(nm = m->child; nm != nil; nm = nm->next){
+ cp = findctype(nm);
+ if(cp->display)
+ break;
+ }
+ if(nm != nil)
+ quotecmd(nil, nm);
+ } else if(strncmp(m->type, "multipart/", 10) == 0){
+ nm = m->child;
+ if(nm != nil){
+ cp = findctype(nm);
+ if(cp->display || strncmp(m->type, "multipart/", 10) == 0)
+ quotecmd(nil, nm);
+ }
+ }
+ return m;
+}
+
+// really delete messages
+Message*
+flushdeleted(Message *cur)
+{
+ Message *m, **l;
+ char buf[1024], *p, *e, *msg;
+//jpc int deld, n, fd;
+ int deld, n;
+ CFid *fid;
+ int i;
+
+ doflush = 0;
+ deld = 0;
+
+//jpc fd = open("/mail/fs/ctl", ORDWR);
+//jpc if(fd < 0){
+//jpc fprint(2, "!can't delete mail, opening /mail/fs/ctl: %r\n");
+//jpc exitfs(0);
+//jpc }
+ fid = fsopen(upasfs,"ctl", ORDWR);
+ if(fid == nil){
+ fprint(2, "!can't delete mail, opening /mail/fs/ctl: %r\n");
+ exitfs(0);
+ }
+ e = &buf[sizeof(buf)];
+ p = seprint(buf, e, "delete %s", mbname);
+ n = 0;
+ for(l = &top.child; *l != nil;){
+ m = *l;
+ if(!m->deleted){
+ l = &(*l)->next;
+ continue;
+ }
+
+ // don't return a pointer to a deleted message
+ if(m == cur)
+ cur = m->next;
+
+ deld++;
+ msg = strrchr(s_to_c(m->path), '/');
+ if(msg == nil)
+ msg = s_to_c(m->path);
+ else
+ msg++;
+ if(e-p < 10){
+//jpc write(fd, buf, p-buf);
+ fswrite(fid, buf, p-buf);
+ n = 0;
+ p = seprint(buf, e, "delete %s", mbname);
+ }
+ p = seprint(p, e, " %s", msg);
+ n++;
+
+ // unchain and free
+ *l = m->next;
+ if(m->next)
+ m->next->prev = m->prev;
+ freemessage(m);
+ }
+ if(n)
+ fswrite(fid, buf, p-buf);
+//jpc write(fd, buf, p-buf);
+
+//jpc close(fd);
+ fsclose(fid);
+
+ if(deld)
+ Bprint(&out, "!%d message%s deleted\n", deld, plural(deld));
+
+ // renumber
+ i = 1;
+ for(m = top.child; m != nil; m = m->next)
+ m->id = natural ? m->fileno : i++;
+
+ // if we're out of messages, go back to first
+ // if no first, return the fake first
+ if(cur == nil){
+ if(top.child)
+ return top.child;
+ else
+ return &top;
+ }
+ return cur;
+}
+
+Message*
+qcmd(Cmd* dummy, Message* dummy2)
+{
+ flushdeleted(nil);
+
+ if(didopen)
+ closemb();
+ Bflush(&out);
+
+ exitfs(0);
+ return nil; // not reached
+}
+
+Message*
+ycmd(Cmd* dummy, Message *m)
+{
+ doflush = 1;
+
+ return icmd(nil, m);
+}
+
+Message*
+xcmd(Cmd* dummy, Message* dummy2)
+{
+ exitfs(0);
+ return nil; // not reached
+}
+
+Message*
+eqcmd(Cmd* dummy, Message *m)
+{
+ if(m == &top)
+ Bprint(&out, "0\n");
+ else
+ Bprint(&out, "%d\n", m->id);
+ return nil;
+}
+
+Message*
+dcmd(Cmd* dummy, Message *m)
+{
+ if(m == &top){
+ Bprint(&out, "!address\n");
+ return nil;
+ }
+ while(m->parent != &top)
+ m = m->parent;
+ m->deleted = 1;
+ return m;
+}
+
+Message*
+ucmd(Cmd* dummy, Message *m)
+{
+ if(m == &top)
+ return nil;
+ while(m->parent != &top)
+ m = m->parent;
+ if(m->deleted < 0)
+ Bprint(&out, "!can't undelete, already flushed\n");
+ m->deleted = 0;
+ return m;
+}
+
+
+Message*
+icmd(Cmd* dummy, Message *m)
+{
+ int n;
+
+ n = dir2message(&top, reverse);
+ if(n > 0)
+ Bprint(&out, "%d new message%s\n", n, plural(n));
+ return m;
+}
+
+Message*
+helpcmd(Cmd* dummy, Message *m)
+{
+ int i;
+
+ Bprint(&out, "Commands are of the form [<range>] <command> [args]\n");
+ Bprint(&out, "<range> := <addr> | <addr>','<addr>| 'g'<search>\n");
+ Bprint(&out, "<addr> := '.' | '$' | '^' | <number> | <search> | <addr>'+'<addr> | <addr>'-'<addr>\n");
+ Bprint(&out, "<search> := '/'<regexp>'/' | '?'<regexp>'?' | '%%'<regexp>'%%'\n");
+ Bprint(&out, "<command> :=\n");
+ for(i = 0; cmdtab[i].cmd != nil; i++)
+ Bprint(&out, "%s\n", cmdtab[i].help);
+ return m;
+}
+
+int
+tomailer(char **av)
+{
+ Waitmsg *w;
+ int pid, i;
+
+ // start the mailer and get out of the way
+ switch(pid = fork()){
+ case -1:
+ fprint(2, "can't fork: %r\n");
+ return -1;
+ case 0:
+//jpc Bprint(&out, "!/bin/upas/marshal");
+ Bprint(&out, "!%s",unsharp("#9/bin/upas/marshal"));
+ for(i = 1; av[i]; i++){
+ if(strchr(av[i], ' ') != nil)
+ Bprint(&out, " '%s'", av[i]);
+ else
+ Bprint(&out, " %s", av[i]);
+ }
+ Bprint(&out, "\n");
+ Bflush(&out);
+ av[0] = "marshal";
+ chdir(wd);
+//jpc exec("/bin/upas/marshal", av);
+//jpc fprint(2, "couldn't exec /bin/upas/marshal\n");
+ exec(unsharp("#9/bin/upas/marshal"), av);
+ fprint(2, "couldn't exec %s\n",unsharp("#9/bin/upas/marshal"));
+ threadexits(0);
+ default:
+ w = wait();
+ if(w == nil){
+ if(interrupted)
+ postnote(PNPROC, pid, "die");
+ waitpid();
+ return -1;
+ }
+ if(w->msg[0]){
+ fprint(2, "mailer failed: %s\n", w->msg);
+ free(w);
+ return -1;
+ }
+ free(w);
+ Bprint(&out, "!\n");
+ break;
+ }
+ return 0;
+}
+
+//
+// like tokenize but obey "" quoting
+//
+int
+tokenize822(char *str, char **args, int max)
+{
+ int na;
+ int intok = 0, inquote = 0;
+
+ if(max <= 0)
+ return 0;
+ for(na=0; ;str++)
+ switch(*str) {
+ case ' ':
+ case '\t':
+ if(inquote)
+ goto Default;
+ /* fall through */
+ case '\n':
+ *str = 0;
+ if(!intok)
+ continue;
+ intok = 0;
+ if(na < max)
+ continue;
+ /* fall through */
+ case 0:
+ return na;
+ case '"':
+ inquote ^= 1;
+ /* fall through */
+ Default:
+ default:
+ if(intok)
+ continue;
+ args[na++] = str;
+ intok = 1;
+ }
+ return 0; /* can't get here; silence compiler */
+}
+
+Message*
+rcmd(Cmd *c, Message *m)
+{
+ char *av[128];
+ int i, ai = 1;
+ Message *nm;
+ char *addr;
+ String *path = nil;
+ String *rpath;
+ String *subject = nil;
+ String *from;
+
+ if(m == &top){
+ Bprint(&out, "!address\n");
+ return nil;
+ }
+
+ addr = nil;
+ for(nm = m; nm != &top; nm = nm->parent){
+ if(*nm->replyto != 0){
+ addr = nm->replyto;
+ break;
+ }
+ }
+ if(addr == nil){
+ Bprint(&out, "!no reply address\n");
+ return nil;
+ }
+
+ if(nm == &top){
+ print("!noone to reply to\n");
+ return nil;
+ }
+
+ for(nm = m; nm != &top; nm = nm->parent){
+ if(*nm->subject){
+ av[ai++] = "-s";
+ subject = addrecolon(nm->subject);
+ av[ai++] = s_to_c(subject);;
+ break;
+ }
+ }
+
+ av[ai++] = "-R";
+ rpath = rooted(s_clone(m->path));
+ av[ai++] = s_to_c(rpath);
+
+ if(strchr(c->av[0], 'f') != nil){
+ fcmd(c, m);
+ av[ai++] = "-F";
+ }
+
+ if(strchr(c->av[0], 'R') != nil){
+ av[ai++] = "-t";
+ av[ai++] = "message/rfc822";
+ av[ai++] = "-A";
+ path = rooted(extendpath(m->path, "raw"));
+ av[ai++] = s_to_c(path);
+ }
+
+ for(i = 1; i < c->an && ai < nelem(av)-1; i++)
+ av[ai++] = c->av[i];
+ from = s_copy(addr);
+ ai += tokenize822(s_to_c(from), &av[ai], nelem(av) - ai);
+ av[ai] = 0;
+ if(tomailer(av) < 0)
+ m = nil;
+ s_free(path);
+ s_free(rpath);
+ s_free(subject);
+ s_free(from);
+ return m;
+}
+
+Message*
+mcmd(Cmd *c, Message *m)
+{
+ char **av;
+ int i, ai;
+ String *path;
+
+ if(m == &top){
+ Bprint(&out, "!address\n");
+ return nil;
+ }
+
+ if(c->an < 2){
+ fprint(2, "!usage: M list-of addresses\n");
+ return nil;
+ }
+
+ ai = 1;
+ av = malloc(sizeof(char*)*(c->an + 8));
+
+ av[ai++] = "-t";
+ if(m->parent == &top)
+ av[ai++] = "message/rfc822";
+ else
+ av[ai++] = "mime";
+
+ av[ai++] = "-A";
+ path = rooted(extendpath(m->path, "raw"));
+ av[ai++] = s_to_c(path);
+
+ if(strchr(c->av[0], 'M') == nil)
+ av[ai++] = "-n";
+
+ for(i = 1; i < c->an; i++)
+ av[ai++] = c->av[i];
+ av[ai] = 0;
+
+ if(tomailer(av) < 0)
+ m = nil;
+ if(path != nil)
+ s_free(path);
+ free(av);
+ return m;
+}
+
+Message*
+acmd(Cmd *c, Message *m)
+{
+ char *av[128];
+ int i, ai;
+ String *from, *to, *cc, *path = nil, *subject = nil;
+
+ if(m == &top){
+ Bprint(&out, "!address\n");
+ return nil;
+ }
+
+ ai = 1;
+ if(*m->subject){
+ av[ai++] = "-s";
+ subject = addrecolon(m->subject);
+ av[ai++] = s_to_c(subject);
+ }
+
+ if(strchr(c->av[0], 'A') != nil){
+ av[ai++] = "-t";
+ av[ai++] = "message/rfc822";
+ av[ai++] = "-A";
+ path = rooted(extendpath(m->path, "raw"));
+ av[ai++] = s_to_c(path);
+ }
+
+ for(i = 1; i < c->an && ai < nelem(av)-1; i++)
+ av[ai++] = c->av[i];
+ from = s_copy(m->from);
+ ai += tokenize822(s_to_c(from), &av[ai], nelem(av) - ai);
+ to = s_copy(m->to);
+ ai += tokenize822(s_to_c(to), &av[ai], nelem(av) - ai);
+ cc = s_copy(m->cc);
+ ai += tokenize822(s_to_c(cc), &av[ai], nelem(av) - ai);
+ av[ai] = 0;
+ if(tomailer(av) < 0)
+ return nil;
+ s_free(from);
+ s_free(to);
+ s_free(cc);
+ s_free(subject);
+ s_free(path);
+ return m;
+}
+
+String *
+relpath(char *path, String *to)
+{
+ if (*path=='/' || strncmp(path, "./", 2) == 0
+ || strncmp(path, "../", 3) == 0) {
+ to = s_append(to, path);
+ } else if(mbpath) {
+ to = s_append(to, s_to_c(mbpath));
+ to->ptr = strrchr(to->base, '/')+1;
+ s_append(to, path);
+ }
+ return to;
+}
+
+int
+appendtofile(Message *m, char *part, char *base, int mbox)
+{
+ String *file, *h;
+ int in, out, rv;
+
+ file = extendpath(m->path, part);
+ in = open(s_to_c(file), OREAD);
+ if(in < 0){
+ fprint(2, "!message disappeared\n");
+ return -1;
+ }
+
+ s_reset(file);
+
+ relpath(base, file);
+ if(sysisdir(s_to_c(file))){
+ s_append(file, "/");
+ if(m->filename && strchr(m->filename, '/') == nil)
+ s_append(file, m->filename);
+ else {
+ s_append(file, "att.XXXXXXXXXXX");
+ mktemp(s_to_c(file));
+ }
+ }
+ if(mbox)
+ out = open(s_to_c(file), OWRITE);
+ else
+ out = open(s_to_c(file), OWRITE|OTRUNC);
+ if(out < 0){
+ out = create(s_to_c(file), OWRITE, 0666);
+ if(out < 0){
+ fprint(2, "!can't open %s: %r\n", s_to_c(file));
+ close(in);
+ s_free(file);
+ return -1;
+ }
+ }
+ if(mbox)
+ seek(out, 0, 2);
+
+ // put on a 'From ' line
+ if(mbox){
+ while(m->parent != &top)
+ m = m->parent;
+ h = file2string(m->path, "unixheader");
+ fprint(out, "%s", s_to_c(h));
+ s_free(h);
+ }
+
+ // copy the message escaping what we have to ad adding newlines if we have to
+ if(mbox)
+ rv = appendfiletombox(in, out);
+ else
+ rv = appendfiletofile(in, out);
+
+ close(in);
+ close(out);
+
+ if(rv >= 0)
+ print("!saved in %s\n", s_to_c(file));
+ s_free(file);
+ return rv;
+}
+
+Message*
+scmd(Cmd *c, Message *m)
+{
+ char *file;
+
+ if(m == &top){
+ Bprint(&out, "!address\n");
+ return nil;
+ }
+
+ switch(c->an){
+ case 1:
+ file = "stored";
+ break;
+ case 2:
+ file = c->av[1];
+ break;
+ default:
+ fprint(2, "!usage: s filename\n");
+ return nil;
+ }
+
+ if(appendtofile(m, "raw", file, 1) < 0)
+ return nil;
+
+ m->stored = 1;
+ return m;
+}
+
+Message*
+wcmd(Cmd *c, Message *m)
+{
+ char *file;
+
+ if(m == &top){
+ Bprint(&out, "!address\n");
+ return nil;
+ }
+
+ switch(c->an){
+ case 2:
+ file = c->av[1];
+ break;
+ case 1:
+ if(*m->filename == 0){
+ fprint(2, "!usage: w filename\n");
+ return nil;
+ }
+ file = strrchr(m->filename, '/');
+ if(file != nil)
+ file++;
+ else
+ file = m->filename;
+ break;
+ default:
+ fprint(2, "!usage: w filename\n");
+ return nil;
+ }
+
+ if(appendtofile(m, "body", file, 0) < 0)
+ return nil;
+ m->stored = 1;
+ return m;
+}
+
+char *specialfile[] =
+{
+ "pipeto",
+ "pipefrom",
+ "L.mbox",
+ "forward",
+ "names"
+};
+
+// return 1 if this is a special file
+static int
+special(String *s)
+{
+ char *p;
+ int i;
+
+ p = strrchr(s_to_c(s), '/');
+ if(p == nil)
+ p = s_to_c(s);
+ else
+ p++;
+ for(i = 0; i < nelem(specialfile); i++)
+ if(strcmp(p, specialfile[i]) == 0)
+ return 1;
+ return 0;
+}
+
+// open the folder using the recipients account name
+static String*
+foldername(char *rcvr)
+{
+ char *p;
+ int c;
+ String *file;
+ Dir *d;
+ int scarey;
+
+ file = s_new();
+ mboxpath("f", user, file, 0);
+ d = dirstat(s_to_c(file));
+
+ // if $mail/f exists, store there, otherwise in $mail
+ s_restart(file);
+ if(d && d->qid.type == QTDIR){
+ scarey = 0;
+ s_append(file, "f/");
+ } else {
+ scarey = 1;
+ }
+ free(d);
+
+ p = strrchr(rcvr, '!');
+ if(p != nil)
+ rcvr = p+1;
+
+ while(*rcvr && *rcvr != '@'){
+ c = *rcvr++;
+ if(c == '/')
+ c = '_';
+ s_putc(file, c);
+ }
+ s_terminate(file);
+
+ if(scarey && special(file)){
+ fprint(2, "!won't overwrite %s\n", s_to_c(file));
+ s_free(file);
+ return nil;
+ }
+
+ return file;
+}
+
+Message*
+fcmd(Cmd *c, Message *m)
+{
+ String *folder;
+
+ if(c->an > 1){
+ fprint(2, "!usage: f takes no arguments\n");
+ return nil;
+ }
+
+ if(m == &top){
+ Bprint(&out, "!address\n");
+ return nil;
+ }
+
+ folder = foldername(m->from);
+ if(folder == nil)
+ return nil;
+
+ if(appendtofile(m, "raw", s_to_c(folder), 1) < 0){
+ s_free(folder);
+ return nil;
+ }
+ s_free(folder);
+
+ m->stored = 1;
+ return m;
+}
+
+void
+system9(char *cmd, char **av, int in)
+{
+ int pid;
+
+ switch(pid=fork()){
+ case -1:
+ return;
+ case 0:
+ if(in >= 0){
+ close(0);
+ dup(in, 0);
+ close(in);
+ }
+ if(wd[0] != 0)
+ chdir(wd);
+ exec(cmd, av);
+ fprint(2, "!couldn't exec %s\n", cmd);
+ threadexits(0);
+ default:
+ if(in >= 0)
+ close(in);
+ while(waitpid() < 0){
+ if(!interrupted)
+ break;
+ postnote(PNPROC, pid, "die");
+ continue;
+ }
+ break;
+ }
+}
+
+Message*
+bangcmd(Cmd *c, Message *m)
+{
+ char cmd[4*1024];
+ char *p, *e;
+ char *av[4];
+ int i;
+
+ cmd[0] = 0;
+ p = cmd;
+ e = cmd+sizeof(cmd);
+ for(i = 1; i < c->an; i++)
+ p = seprint(p, e, "%s ", c->av[i]);
+ av[0] = "rc";
+ av[1] = "-c";
+ av[2] = cmd;
+ av[3] = 0;
+ system9(unsharp("#9/bin/rc"), av, -1);
+ Bprint(&out, "!\n");
+ return m;
+}
+
+Message*
+xpipecmd(Cmd *c, Message *m, char *part)
+{
+ char cmd[128];
+ char *p, *e;
+ char *av[4];
+ String *path;
+//jpc int i, fd;
+ int i;
+ CFid *fid;
+
+ if(c->an < 2){
+ Bprint(&out, "!usage: | cmd\n");
+ return nil;
+ }
+
+ if(m == &top){
+ Bprint(&out, "!address\n");
+ return nil;
+ }
+
+ path = extendpath(m->path, part);
+//jpc fd = open(s_to_c(path), OREAD);
+ fid = fsopen(upasfs,s_to_c(path), OREAD);
+ s_free(path);
+//jpc if(fd < 0){ // compatibility with older upas/fs
+ if(fid == nil){ // compatibility with older upas/fs
+ path = extendpath(m->path, "raw");
+//jpc fd = open(s_to_c(path), OREAD);
+ fid = fsopen(upasfs,s_to_c(path), OREAD);
+ s_free(path);
+ }
+ if(fid < 0){
+ fprint(2, "!message disappeared\n");
+ return nil;
+ }
+
+ p = cmd;
+ e = cmd+sizeof(cmd);
+ cmd[0] = 0;
+ for(i = 1; i < c->an; i++)
+ p = seprint(p, e, "%s ", c->av[i]);
+ av[0] = "rc";
+ av[1] = "-c";
+ av[2] = cmd;
+ av[3] = 0;
+// system9("/bin/rc", av, fd); /* system closes fd */
+ system9(unsharp("#9/bin/rc"), av, 0);
+ fsclose(fid);
+ Bprint(&out, "!\n");
+ return m;
+}
+
+Message*
+pipecmd(Cmd *c, Message *m)
+{
+ return xpipecmd(c, m, "body");
+}
+
+Message*
+rpipecmd(Cmd *c, Message *m)
+{
+ return xpipecmd(c, m, "rawunix");
+}
+
+#if 0 /* jpc */
+void
+closemb(void)
+{
+ int fd;
+
+ fd = open("/mail/fs/ctl", ORDWR);
+ if(fd < 0)
+ sysfatal("can't open /mail/fs/ctl: %r");
+
+ // close current mailbox
+ if(*mbname && strcmp(mbname, "mbox") != 0)
+ fprint(fd, "close %s", mbname);
+
+ close(fd);
+}
+#endif
+void
+closemb(void)
+{
+ CFid *fid;
+ char s[256];
+
+ fid = fsopen(upasfs,"ctl", ORDWR);
+ if(fid == nil)
+ sysfatal("can't open upasfs/ctl: %r");
+
+ // close current mailbox
+ if(*mbname && strcmp(mbname, "mbox") != 0) {
+ snprint(s, 256, "close %s", mbname);
+ fswrite(fid,s,strlen(s));
+ }
+
+//jpc close(fd);
+ fsclose(fid);
+}
+
+int
+switchmb(char *file, char *singleton)
+{
+ char *p;
+ int n, fd;
+ String *path;
+ char buf[256];
+
+ // if the user didn't say anything and there
+ // is an mbox mounted already, use that one
+ // so that the upas/fs -fdefault default is honored.
+ if(file
+ || (singleton && access(singleton, 0)<0)
+/* || (!singleton && access("/mail/fs/mbox", 0)<0)){ jpc */
+ || (!singleton && fsdirstat(upasfs, "upasfs/mbox") )){
+ fprint(2,"can't access /mail/fs/mbox\n");
+ if(file == nil)
+ file = "mbox";
+
+ // close current mailbox
+ closemb();
+ didopen = 1;
+
+ fd = open("/mail/fs/ctl", ORDWR);
+ if(fd < 0)
+ sysfatal("can't open /mail/fs/ctl: %r");
+
+ path = s_new();
+
+ // get an absolute path to the mail box
+ if(strncmp(file, "./", 2) == 0){
+ // resolve path here since upas/fs doesn't know
+ // our working directory
+ if(getwd(buf, sizeof(buf)-strlen(file)) == nil){
+ fprint(2, "!can't get working directory: %s\n", buf);
+ return -1;
+ }
+ s_append(path, buf);
+ s_append(path, file+1);
+ } else {
+ mboxpath(file, user, path, 0);
+ }
+
+ // make up a handle to use when talking to fs
+ p = strrchr(file, '/');
+ if(p == nil){
+ // if its in the mailbox directory, just use the name
+ strncpy(mbname, file, sizeof(mbname));
+ mbname[sizeof(mbname)-1] = 0;
+ } else {
+ // make up a mailbox name
+ p = strrchr(s_to_c(path), '/');
+ p++;
+ if(*p == 0){
+ fprint(2, "!bad mbox name");
+ return -1;
+ }
+ strncpy(mbname, p, sizeof(mbname));
+ mbname[sizeof(mbname)-1] = 0;
+ n = strlen(mbname);
+ if(n > Elemlen-12)
+ n = Elemlen-12;
+ sprint(mbname+n, "%ld", time(0));
+ }
+
+ if(fprint(fd, "open %s %s", s_to_c(path), mbname) < 0){
+ fprint(2, "!can't 'open %s %s': %r\n", file, mbname);
+ s_free(path);
+ return -1;
+ }
+ close(fd);
+ }else
+ if (singleton && access(singleton, 0)==0
+ && strncmp(singleton, "/mail/fs/", 9) == 0){
+ if ((p = strchr(singleton +10, '/')) == nil){
+ fprint(2, "!bad mbox name");
+ return -1;
+ }
+ n = p-(singleton+9);
+ strncpy(mbname, singleton+9, n);
+ mbname[n+1] = 0;
+ path = s_reset(nil);
+ mboxpath(mbname, user, path, 0);
+ }else{
+ path = s_reset(nil);
+ mboxpath("mbox", user, path, 0);
+ strcpy(mbname, "mbox");
+ }
+
+ sprint(root, "%s", mbname);
+ if(getwd(wd, sizeof(wd)) == 0)
+ wd[0] = 0;
+ if(singleton == nil && chdir(root) >= 0)
+ strcpy(root, ".");
+ rootlen = strlen(root);
+
+ if(mbpath != nil)
+ s_free(mbpath);
+ mbpath = path;
+ return 0;
+}
+
+// like tokenize but for into lines
+int
+lineize(char *s, char **f, int n)
+{
+ int i;
+
+ for(i = 0; *s && i < n; i++){
+ f[i] = s;
+ s = strchr(s, '\n');
+ if(s == nil)
+ break;
+ *s++ = 0;
+ }
+ return i;
+}
+
+
+
+String*
+rooted(String *s)
+{
+ static char buf[256];
+
+ if(strcmp(root, ".") != 0)
+ return s;
+ snprint(buf, sizeof(buf), "/mail/fs/%s/%s", mbname, s_to_c(s));
+ s_free(s);
+ return s_copy(buf);
+}
+
+int
+plumb(Message *m, Ctype *cp)
+{
+ String *s;
+ Plumbmsg *pm;
+ static int fd = -2;
+
+ if(cp->plumbdest == nil)
+ return -1;
+
+ if(fd < -1)
+ fd = plumbopen("send", OWRITE);
+ if(fd < 0)
+ return -1;
+
+ pm = mallocz(sizeof(Plumbmsg), 1);
+ pm->src = strdup("mail");
+ if(*cp->plumbdest)
+ pm->dst = strdup(cp->plumbdest);
+ pm->wdir = nil;
+ pm->type = strdup("text");
+ pm->ndata = -1;
+ s = rooted(extendpath(m->path, "body"));
+ if(cp->ext != nil){
+ s_append(s, ".");
+ s_append(s, cp->ext);
+ }
+ pm->data = strdup(s_to_c(s));
+ s_free(s);
+ plumbsend(fd, pm);
+ plumbfree(pm);
+ return 0;
+}
+
+void
+regerror(char* dummy)
+{
+}
+
+String*
+addrecolon(char *s)
+{
+ String *str;
+
+ if(cistrncmp(s, "re:", 3) != 0){
+ str = s_copy("Re: ");
+ s_append(str, s);
+ } else
+ str = s_copy(s);
+ return str;
+}
+
+void
+exitfs(char *rv)
+{
+ if(startedfs) {
+ fsunmount(upasfs);
+ /* unmount(nil, "/mail/fs"); jpc */
+ }
+//jpc chdir("/sys/src/cmd/upas/ned");
+ threadexits(rv);
+}