| #include "common.h" |
| #include <ctype.h> |
| #include <plumb.h> |
| #include <9pclient.h> |
| #include <thread.h> |
| |
| #define system nedsystem |
| #define rcmd nedrcmd |
| |
| 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 }, |
| { "message/delivery-status", "txt", 1, 0 }, |
| { "image/bmp", "bmp", 0, "image" }, |
| { "image/jpeg", "jpg", 0, "image" }, |
| { "image/gif", "gif", 0, "image" }, |
| { "image/png", "png", 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 system(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 *mailfs; |
| |
| void |
| usage(void) |
| { |
| fprint(2, "usage: %s [-nr] [-f mailfile] [-s mailfile]\n", argv0); |
| fprint(2, " %s -c dir\n", argv0); |
| threadexitsall("usage"); |
| } |
| |
| void |
| catchnote(void *x, char *note) |
| { |
| USED(x); |
| |
| 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; |
| String *prompt; |
| char *file, *singleton, *service; |
| |
| Binit(&out, 1, OWRITE); |
| |
| file = nil; |
| singleton = nil; |
| reverse = 1; |
| cflag = 0; |
| service = "mail"; |
| ARGBEGIN { |
| case 'S': |
| service = EARGF(usage()); |
| break; |
| 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); |
| threadexitsall(0); |
| } |
| |
| if(argc) |
| usage(); |
| if((mailfs = nsmount(service, nil)) == nil) |
| sysfatal("cannot mount %s: %r", service); |
| |
| switchmb(file, singleton); |
| |
| 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 = ⊤ |
| 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); |
| } |
| |
| static char* |
| mkaddrs(char *t) |
| { |
| int i, nf, inquote; |
| char **f, *s; |
| Fmt fmt; |
| |
| inquote = 0; |
| nf = 2; |
| for(s=t; *s; s++){ |
| if(*s == '\'') |
| inquote = !inquote; |
| if(*s == ' ' && !inquote) |
| nf++; |
| } |
| f = malloc(nf*sizeof f[0]); |
| if(f == nil) |
| return nil; |
| nf = tokenize(t, f, nf); |
| fmtstrinit(&fmt); |
| for(i=0; i+1<nf; i+=2){ |
| if(i > 0) |
| fmtprint(&fmt, " "); |
| /* if(f[i][0] == 0 || strcmp(f[i], f[i+1]) == 0) */ |
| fmtprint(&fmt, "%s", f[i+1]); |
| /* else */ |
| /* fmtprint(&fmt, "%s <%s>", f[i], f[i+1]); */ |
| } |
| free(f); |
| return fmtstrflush(&fmt); |
| } |
| |
| /* */ |
| /* read the message info */ |
| /* */ |
| Message* |
| file2message(Message *parent, char *name) |
| { |
| Message *m; |
| String *path; |
| char *f[30], *s, *t; |
| int i, nf; |
| |
| 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"); |
| m->from = ""; |
| m->to = ""; |
| m->cc = ""; |
| m->replyto = ""; |
| m->date = ""; |
| m->subject = ""; |
| m->type = ""; |
| m->disposition = ""; |
| m->filename = ""; |
| nf = lineize(s_to_c(m->info), f, nelem(f)); |
| for(i=0; i<nf; i++){ |
| s = f[i]; |
| t = strchr(f[i], ' '); |
| if(t == nil) |
| continue; |
| *t++ = 0; |
| |
| if(strcmp(s, "from") == 0) |
| m->from = mkaddrs(t); |
| else if(strcmp(s, "to") == 0) |
| m->to = mkaddrs(t); |
| else if(strcmp(s, "cc") == 0) |
| m->cc = mkaddrs(t); |
| else if(strcmp(s, "replyto") == 0) |
| m->replyto = mkaddrs(t); |
| else if(strcmp(s, "unixdate") == 0 && (t=strchr(t, ' ')) != nil) |
| m->date = t; |
| else if(strcmp(s, "subject") == 0) |
| m->subject = t; |
| else if(strcmp(s, "type") == 0) |
| m->type = t; |
| else if(strcmp(s, "disposition") == 0) |
| m->disposition = t; |
| else if(strcmp(s, "filename") == 0) |
| m->filename = t; |
| } |
| 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) |
| { |
| int i, n, highest, newmsgs; |
| CFid *fd; |
| |
| Dir *d; |
| Message *first, *last, *m; |
| |
| fd = fsopen(mailfs, s_to_c(parent->path), OREAD); |
| if(fd == 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(fd, &d); |
| for(i = 0; i < n; i++){ |
| if((d[i].qid.type & QTDIR) == 0) |
| continue; |
| if(atoi(d[i].name) <= highest) |
| continue; |
| 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; |
| } |
| } |
| free(d); |
| fsclose(fd); |
| parent->child = first; |
| |
| /* renumber and file longest from */ |
| i = 1; |
| longestfrom = 12; |
| for(m = first; m != nil; m = m->next){ |
| m->id = natural ? m->fileno : i++; |
| n = strlen(m->from); |
| if(n > longestfrom) |
| longestfrom = 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; |
| int n, m; |
| CFid *fd; |
| |
| s = extendpath(dir, file); |
| fd = fsopen(mailfs, s_to_c(s), OREAD); |
| s_grow(s, 512); /* avoid multiple reads on info files */ |
| s_reset(s); |
| if(fd == nil) |
| return s; |
| |
| for(;;){ |
| n = s->end - s->ptr; |
| if(n == 0){ |
| s_grow(s, 128); |
| continue; |
| } |
| m = fsread(fd, s->ptr, n); |
| if(m <= 0) |
| break; |
| s->ptr += m; |
| if(m < n) |
| break; |
| } |
| s_terminate(s); |
| fsclose(fd); |
| |
| return s; |
| } |
| |
| /* */ |
| /* get the length of a file */ |
| /* */ |
| int |
| filelen(String *dir, char *file) |
| { |
| String *path; |
| Dir *d; |
| int rv; |
| |
| path = extendpath(dir, file); |
| d = fsdirstat(mailfs, 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 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; |
| |
| 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); |
| 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 = ⊤ |
| 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]; |
| int i, rv; |
| CFid *fd; |
| String *path; |
| |
| path = extendpath(m->path, "raw"); |
| fd = fsopen(mailfs, s_to_c(path), OREAD); |
| if(fd == 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(;;){ |
| i = fsread(fd, 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; |
| if(fsseek(fd, -128LL, 1) < 0) |
| break; |
| } |
| |
| fsclose(fd); |
| 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 *x, Message *m) |
| { |
| USED(x); |
| |
| if(m == &top) |
| return nil; |
| aichcmd(m, 0); |
| return nil; |
| } |
| |
| Message* |
| hcmd(Cmd *x, Message *m) |
| { |
| char hdr[256]; |
| |
| USED(x); |
| if(m == &top) |
| return nil; |
| |
| snprintheader(hdr, sizeof(hdr), m); |
| Bprint(&out, "%s\n", hdr); |
| return nil; |
| } |
| |
| Message* |
| bcmd(Cmd *x, Message *m) |
| { |
| int i; |
| Message *om = m; |
| |
| USED(x); |
| 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 *x, Message *m) |
| { |
| USED(x); |
| if(m == &top) |
| return m->child; |
| return m->next; |
| } |
| |
| int |
| printpart(String *s, char *part) |
| { |
| char buf[4096]; |
| int n, tot; |
| CFid *fd; |
| String *path; |
| |
| path = extendpath(s, part); |
| fd = fsopen(mailfs, s_to_c(path), OREAD); |
| s_free(path); |
| if(fd == nil){ |
| fprint(2, "!message dissappeared\n"); |
| return 0; |
| } |
| tot = 0; |
| while((n = fsread(fd, buf, sizeof(buf))) > 0){ |
| if(interrupted) |
| break; |
| if(Bwrite(&out, buf, n) <= 0) |
| break; |
| tot += n; |
| } |
| fsclose(fd); |
| return tot; |
| } |
| |
| int |
| printhtml(Message *m) |
| { |
| Cmd c; |
| |
| c.an = 3; |
| c.av[1] = "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 *x, Message *m) |
| { |
| USED(x); |
| if(m == &top) |
| return ⊤ |
| 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 *x, Message *m) |
| { |
| Message *nm; |
| Ctype *cp; |
| String *s; |
| char buf[128]; |
| |
| USED(x); |
| if(m == &top) |
| return ⊤ |
| 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) |
| { |
| int fd; |
| char *p; |
| String *path; |
| Biobuf *b; |
| |
| path = extendpath(s, part); |
| fd = fsopenfd(mailfs, s_to_c(path), OREAD); |
| s_free(path); |
| if(fd < 0){ |
| fprint(2, "!message disappeared\n"); |
| return; |
| } |
| b = Bfdopen(fd, OREAD); |
| if(b == 0){ |
| fprint(2, "out of memory\n"); |
| close(fd); |
| 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 *x, Message *m) |
| { |
| Message *nm; |
| Ctype *cp; |
| |
| USED(x); |
| if(m == &top) |
| return ⊤ |
| 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; |
| int deld, n; |
| CFid *fd; |
| int i; |
| |
| doflush = 0; |
| deld = 0; |
| |
| snprint(buf, sizeof buf, "%s/ctl", mbname); |
| fd = fsopen(mailfs, buf, OWRITE); |
| if(fd == nil){ |
| fprint(2, "!can't delete mail, opening %s: %r\n", buf); |
| exitfs(0); |
| } |
| e = &buf[sizeof(buf)]; |
| p = seprint(buf, e, "delete"); |
| 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){ |
| fswrite(fd, buf, p-buf); |
| n = 0; |
| p = seprint(buf, e, "delete"); |
| } |
| 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(fd, buf, p-buf); |
| |
| fsclose(fd); |
| |
| 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 ⊤ |
| } |
| return cur; |
| } |
| |
| Message* |
| qcmd(Cmd *x, Message *m) |
| { |
| USED(x); |
| USED(m); |
| |
| flushdeleted(nil); |
| |
| if(didopen) |
| closemb(); |
| Bflush(&out); |
| |
| exitfs(0); |
| return nil; /* not reached */ |
| } |
| |
| Message* |
| ycmd(Cmd *x, Message *m) |
| { |
| USED(x); |
| |
| doflush = 1; |
| |
| return icmd(nil, m); |
| } |
| |
| Message* |
| xcmd(Cmd *x, Message *m) |
| { |
| USED(x); |
| USED(m); |
| |
| exitfs(0); |
| return nil; /* not reached */ |
| } |
| |
| Message* |
| eqcmd(Cmd *x, Message *m) |
| { |
| USED(x); |
| |
| if(m == &top) |
| Bprint(&out, "0\n"); |
| else |
| Bprint(&out, "%d\n", m->id); |
| return nil; |
| } |
| |
| Message* |
| dcmd(Cmd *x, Message *m) |
| { |
| USED(x); |
| |
| if(m == &top){ |
| Bprint(&out, "!address\n"); |
| return nil; |
| } |
| while(m->parent != &top) |
| m = m->parent; |
| m->deleted = 1; |
| return m; |
| } |
| |
| Message* |
| ucmd(Cmd *x, Message *m) |
| { |
| USED(x); |
| |
| 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 *x, Message *m) |
| { |
| int n; |
| char buf[1024]; |
| CFid *fd; |
| |
| USED(x); |
| snprint(buf, sizeof buf, "%s/ctl", mbname); |
| fd = fsopen(mailfs, buf, OWRITE); |
| if(fd){ |
| fswrite(fd, "refresh", 7); |
| fsclose(fd); |
| } |
| n = dir2message(&top, reverse); |
| if(n > 0) |
| Bprint(&out, "%d new message%s\n", n, plural(n)); |
| return m; |
| } |
| |
| Message* |
| helpcmd(Cmd *x, Message *m) |
| { |
| int i; |
| |
| USED(x); |
| 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) |
| { |
| static char *marshal; |
| Waitmsg *w; |
| int pid, i; |
| |
| if(marshal == nil) |
| marshal = unsharp("#9/bin/upas/marshal"); |
| |
| /* 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: |
| Bprint(&out, "!%s", 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); |
| exec(marshal, av); |
| fprint(2, "couldn't exec %s\n", 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 != ⊤ 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 != ⊤ 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 buf[256]; |
| CFid *fd; |
| char *file, *msg; |
| |
| 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(file[0] == '/' || (file[0]=='.' && file[1]=='/')){ |
| if(appendtofile(m, "raw", file, 1) < 0) |
| return nil; |
| }else{ |
| snprint(buf, sizeof buf, "%s/ctl", mbname); |
| if((fd = fsopen(mailfs, buf, OWRITE)) == nil) |
| return nil; |
| msg = strrchr(s_to_c(m->path), '/'); |
| if(msg == nil) |
| msg = s_to_c(m->path); |
| else |
| msg++; |
| if(fsprint(fd, "save %s %s", file, msg) < 0){ |
| fsclose(fd); |
| return nil; |
| } |
| fsclose(fd); |
| } |
| 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 |
| system(char *cmd, char **av, int in) |
| { |
| int pid; |
| |
| switch(pid=fork()){ |
| case -1: |
| return; |
| case 0: |
| if(strcmp(cmd, "rc") == 0) |
| cmd = unsharp("#9/bin/rc"); |
| 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; |
| system("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; |
| int i, fd; |
| |
| 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); |
| fd = fsopenfd(mailfs, s_to_c(path), OREAD); |
| s_free(path); |
| |
| if(fd < 0){ /* compatibility with older upas/fs */ |
| path = extendpath(m->path, "raw"); |
| fd = fsopenfd(mailfs, s_to_c(path), OREAD); |
| s_free(path); |
| } |
| if(fd < 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; |
| system("rc", av, fd); /* system closes fd */ |
| 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"); |
| } |
| |
| void |
| closemb(void) |
| { |
| CFid *fd; |
| |
| fd = fsopen(mailfs, "ctl", OWRITE); |
| if(fd == nil) |
| sysfatal("can't open ctl: %r"); |
| |
| /* close current mailbox */ |
| if(*mbname && strcmp(mbname, "mbox") != 0) |
| fsprint(fd, "close %s", mbname); |
| |
| fsclose(fd); |
| } |
| |
| 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(0 && (file || (singleton && fsaccess(mailfs, singleton, 0) < 0))){ |
| /* XXX all wrong */ |
| fprint(2, "file=%s singleton=%s\n", file, singleton); |
| 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 && fsaccess(mailfs, singleton, 0)==0){ |
| if ((p = strchr(singleton, '/')) == nil){ |
| fprint(2, "!bad mbox name"); |
| return -1; |
| } |
| n = p-singleton; |
| strncpy(mbname, singleton, n); |
| mbname[n+1] = 0; |
| path = s_reset(nil); |
| mboxpath(mbname, user, path, 0); |
| }else{ |
| if(file) |
| strecpy(mbname, mbname+sizeof mbname, file); |
| else |
| strcpy(mbname, "mbox"); |
| path = s_reset(nil); |
| mboxpath(mbname, user, path, 0); |
| } |
| |
| snprint(root, sizeof root, "%s", mbname); |
| 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 *x) |
| { |
| USED(x); |
| } |
| |
| 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) |
| { |
| threadexitsall(rv); |
| } |