#include "common.h" #include #include #include #include #include "all.h" typedef struct Exec Exec; enum { STACK = 8192, }; enum { NARGS = 100, NARGCHAR = 8*1024, EXECSTACK = STACK+(NARGS+1)*sizeof(char*)+NARGCHAR }; struct Exec { char *prog; char **argv; int p[2]; /* p[1] is write to program; p[0] set to prog fd 0*/ int q[2]; /* q[0] is read from program; q[1] set to prog fd 1 */ Channel *sync; }; int debug = 0; int paranoid = 0; char *goodtypes[] = { "text", "text/plain", "message/rfc822", "text/richtext", "text/tab-separated-values", "application/octet-stream", nil, }; int fieldcmp(String* s, char* c) { return cistrcmp(s_to_c(s), c); } void mesgline(int fd, char *header, String *value) { char* s; if (value == nil || fd < 0) return; if(s_len(value) > 0){ s = malloc(s_len(value)+2); decquoted(s, s_to_c(value), s_to_c(value)+s_len(value)); fprint(fd, "%s: %s", header, s); free(s); } } void checkpath(char* fn) { static char* pref; if (pref == nil) pref = smprint("/usr/%s/mail/", getuser()); if (paranoid && strncmp(fn, pref, strlen(pref))) sysfatal("path %s not in $home/mail/", fn); } void writeattach(char* fn, char* data, long len) { int fd; checkpath(fn); if (access(fn, AEXIST) >= 0){ fprint(2, "fn: %s: file exists\n", fn); } else { fd = create(fn, OWRITE, 0660); if (fd < 0){ fprint(2, "fn: %s: %r\n", fn); return; } if (write(fd, data, len) != len) fprint(2, "fn: %s: %r\n", fn); close(fd); } } int mimedisplay(Message *m, char *name, int fileonly) { char *dest; char* fn; char* p; if (!m) return 0; if(m->disposition == Dfile || (m->filename && s_len(m->filename)!=0)){ if(s_len(m->filename) == 0){ if (strlen(m->name) == 0) dest = strdup("attach"); else dest = strdup(m->name); p = strchr(dest, '\n'); if (p) *p = 0; }else dest = strdup(s_to_c(m->filename)); if (access(name, AEXIST) < 0) fn = smprint("%s.%s", name, dest); else fn = smprint("%s/%s", name, dest); writeattach(fn, m->body, m->bend - m->body); free(fn); free(dest); return 1; }else if(!fileonly) print("\tfile is %sbody\n", name); // , ext(m->type) return 0; } /* copy argv to stack and free the incoming strings, so we don't leak argument vectors */ void buildargv(char **inargv, char *argv[NARGS+1], char args[NARGCHAR]) { int i, n; char *s, *a; s = args; for(i=0; i= NARGCHAR) /* too many characters */ break; argv[i] = s; memmove(s, a, n); s += n; free(a); } argv[i] = nil; } void execproc(void *v) { struct Exec *e; int p[2], q[2]; char *prog; char *argv[NARGS+1], args[NARGCHAR]; e = v; p[0] = e->p[0]; p[1] = e->p[1]; q[0] = e->q[0]; q[1] = e->q[1]; prog = e->prog; /* known not to be malloc'ed */ rfork(RFFDG); sendul(e->sync, 1); buildargv(e->argv, argv, args); free(e->argv); chanfree(e->sync); free(e); dup(p[0], 0); close(p[0]); close(p[1]); if(q[0]){ dup(q[1], 1); close(q[0]); close(q[1]); } procexec(nil, prog, argv); //fprint(2, "exec: %s", e->prog); //{int i; //for(i=0; argv[i]; i++) print(" '%s'", argv[i]); //print("\n"); //} //argv[0] = "cat"; //argv[1] = nil; //procexec(nil, "/bin/cat", argv); fprint(2, "Mail: can't exec %s: %r\n", prog); threadexits("can't exec"); } char* formathtml(char *body, int *np) { int i, j, p[2], q[2]; Exec *e; char buf[1024]; Channel *sync; e = emalloc(sizeof(struct Exec)); if(pipe(p) < 0 || pipe(q) < 0) return("pipe"); e->p[0] = p[0]; e->p[1] = p[1]; e->q[0] = q[0]; e->q[1] = q[1]; e->argv = emalloc(4*sizeof(char*)); e->argv[0] = strdup("htmlfmt"); e->argv[1] = strdup("-cutf-8"); e->argv[2] = strdup("-a"); e->argv[3] = nil; e->prog = "/bin/htmlfmt"; sync = chancreate(sizeof(int), 0); e->sync = sync; proccreate(execproc, e, EXECSTACK); recvul(sync); close(p[0]); close(q[1]); if((i=write(p[1], body, *np)) != *np){ fprint(2, "Mail: warning: htmlfmt failed: wrote %d of %d: %r\n", i, *np); close(p[1]); close(q[0]); return body; } close(p[1]); body = nil; i = 0; for(;;){ j = read(q[0], buf, sizeof buf); if(j <= 0) break; body = realloc(body, i+j+1); if(body == nil) return nil; memmove(body+i, buf, j); i += j; body[i] = '\0'; } close(q[0]); *np = i; return body; } int isprintable(String *type) { int i; if (!type) return 0; for(i=0; goodtypes[i]!=nil; i++) if(strcmp(s_to_c(type), goodtypes[i])==0) return 1; return 0; } void decode(Message*); int datafile(char* dir) { int fd; char* fn; checkpath(dir); if (access(dir, AREAD) < 0){ fd = create(dir, OREAD, DMDIR|0750); if (fd < 0) return -1; close(fd); } fn = smprint("%s/text", dir); fd = open(fn, OWRITE); if (fd < 0) fd = create(fn, OWRITE, 0660); if (fd >= 0) seek(fd, 0, 2); free(fn); return fd; } int mkmesgfs(Message* m, char* dir) { char *s, *subdir, *name; Message *mp, *thisone; int n, body; int dfd; if (m == nil) return 1; //print("*** at %s (%s)\n", dir, m->type ? s_to_c(m->type) : "none"); dfd = -1; /* suppress headers of envelopes */ if(fieldcmp(m->type, "message/rfc822") != 0){ dfd = datafile(dir); if (dfd < 0) return 0; mesgline(dfd, "From", m->from822); mesgline(dfd, "Date", m->date822); mesgline(dfd, "To", m->to822); mesgline(dfd, "CC", m->cc822); mesgline(dfd, "Bcc", m->bcc822); mesgline(dfd, "Reply-To", m->replyto822); mesgline(dfd, "Subject", m->subject822); fprint(dfd, "\n"); } if(m->part == nil){ /* single part message */ if(fieldcmp(m->type, "text")==0|| (m->type && strncmp(s_to_c(m->type), "text/", 5)==0)){ mimedisplay(m, dir, 1); if (dfd < 0) dfd = datafile(dir); n = m->bend - m->body; if (fieldcmp(m->type, "text/html") == 0){ s = formathtml(m->body, &n); if (n > 0) write(dfd, s, n); free(s); } else if (n > 0) write(dfd, m->body, n); }else mimedisplay(m, dir, 0); }else{ /* multi-part message, either multipart/* or message/rfc822 */ thisone = nil; if(fieldcmp(m->type, "multipart/alternative") == 0){ thisone = m->part; /* in case we can't find a good one */ for(mp=m->part; mp!=nil; mp=mp->next) if(isprintable(mp->type)){ thisone = mp; break; } body=0; } else body = 1; for(mp=m->part; mp!=nil; mp=mp->next){ if(thisone!=nil && mp!=thisone) continue; /* For multipart/arternative kludges, * consider as text all the inner ones as well. */ if (fieldcmp(mp->type, "multipart/alternative")==0) subdir = strdup(dir); else if (thisone || body){ subdir = strdup(dir); body = 0; } else subdir = smprint("%s/%s", dir, mp->name); name = strdup(mp->name); /* skip first element in name because it's already in window name */ dprint(2, "\n===> %s \n",name); if(fieldcmp(mp->type, "text")==0 || (mp->type && strncmp(s_to_c(mp->type), "text/", 5)==0)){ if (!mimedisplay(mp, dir, 1)){ if (dfd < 0) dfd = datafile(dir); if (dfd < 0) return 0; mesgline(dfd, "From", mp->from822); mesgline(dfd, "Date", mp->date822); mesgline(dfd, "To", mp->to822); mesgline(dfd, "CC", mp->cc822); mesgline(dfd, "Bcc", mp->bcc822); mesgline(dfd, "Subject", mp->subject822); mesgline(dfd, "Reply-To", mp->replyto822); fprint(dfd, "\n"); n = mp->bend - mp->body; if (fieldcmp(mp->type, "text/html") == 0){ s = formathtml(mp->body, &n); if (n > 0) write(dfd, s, n); free(s); } else if (n > 0) write(dfd, mp->body, n); } }else{ if((mp->type && strncmp(s_to_c(mp->type), "multipart/", 10)==0 ) || fieldcmp(mp->type, "message/rfc822")==0){ //print("*** recur\n"); if (!mkmesgfs(mp, subdir)) return 0; }else mimedisplay(mp, subdir, 0); } free(name); free(subdir); } } if (dfd != -1) close(dfd); return 1; } static char* datedir(void) { static char dname[4+2+1]; Tm* tm; tm = localtime(time(nil)); if (tm == nil) strcpy(dname, "000000"); else seprint(dname, dname+sizeof(dname), "%04d%02d", tm->year + 1900, tm->mon+1); return dname; } int mkmboxfs(Mailbox* mb, char* dir) { Message*m; char* d; int nbfd; char* nbfn; char buf[40]; int nb; char e[50]; int n; char* r; char* digests; char* mdir; close(create(dir, OREAD, DMDIR|0700)); nbfn = smprint("%s/seq", dir); mdir = smprint("%s/%s", dir, datedir()); close(create(mdir, OREAD, DMDIR|0700)); while((nbfd = open(nbfn, ORDWR)) < 0){ if (nbfd >=0) break; rerrstr(e, sizeof(e)); if (strstr(e, "exclusive")) sleep(2000); else break; } if (nbfd < 0) nbfd = create(nbfn, ORDWR, DMEXCL|0664); free(nbfn); if (nbfd < 0){ fprint(2, "can't open seq file"); free(mdir); return 0; } n = read(nbfd, buf, sizeof(buf)-1); if (n < 0) n = 0; buf[n] = 0; nb = (int)strtod(buf, &r); nbfn = smprint("%s/digests", dir); digests = readfstr(nbfn); if (!digests) digests = strdup(""); for(m = mb->root->part; m; m = m->next){ if (strstr(digests, s_to_c(m->sdigest))) continue; else digests = smprint("%s%s\n", digests, s_to_c(m->sdigest)); d=smprint("%s/%d", mdir, nb++); if (!mkmesgfs(m, d)){ free(d); free(nbfn); free(digests); free(mdir); return 0; } free(d); } if (writefstr(nbfn, digests) < 0) createf(nbfn, digests, strlen(digests), 0664); seek(nbfd, 0, 0); fprint(nbfd, "%08d", nb); close(nbfd); free(nbfn); free(digests); free(mdir); return 1; } char* cleanpath(char* file, char* dir) { char* s; char* t; assert(file && file[0]); if (file[1]) file = strdup(file); else { s = file; file = malloc(3); file[0] = s[0]; file[1] = 0; } s = cleanname(file); if (s[0] != '/' && dir != nil && dir[0] != 0){ t = smprint("%s/%s", dir, s); free(s); s = cleanname(t); } return s; } static void usage(void) { fprint(2, "usage: %s [-D] [-n] [-d dir] [mbox]\n", argv0); threadexits("usage"); } int mainstacksize = 32 * 1024; void threadmain(int argc, char*argv[]) { Mailbox* mb; Mlock* lk; char* s; char* mbox; Dir d; char* tmp; char* dir; int fd; int mode; int dry; char wdir[512]; dry = 0; dir = nil; ARGBEGIN{ case 'n': dry++; break; case 'd': dir = EARGF(usage()); break; case 'D': debug++; break; default: usage(); }ARGEND; if (argc == 1){ getwd(wdir, sizeof(wdir)); mbox = cleanpath(argv[0], wdir); if (access(mbox, AREAD) < 0){ free(mbox); mbox = cleanpath(argv[0], smprint("/mail/box/%s", getuser())); } } else { mbox = smprint("/mail/box/%s/mbox", getuser()); if (argc != 0) usage(); } if (dir == nil) dir = smprint("/mail/box/%s/mails", getuser()); /* Avoid interrupts in the middle of the process * when called from programs that scan for new * mail every once in a while */ rfork(RFNOTEG); mb = newmbox(mbox, strrchr(mbox, '/') + 1, 1); if (mb == nil) sysfatal("no mbox"); if (dry){ // Just try to generate the fs; don't update anything readmbox(mb); mkmboxfs(mb, dir); exits(nil); } lk = syslock(mbox); mode = DMAPPEND|DMEXCL|0622; tmp = smprint("%s.tmp", mbox); sysremove(tmp); fd = create(tmp, ORDWR, mode); nulldir(&d); d.mode = mode; dirwstat(tmp, &d); if (fd < 0){ fprint(2, "%s: error in temp file: %r\n", argv0); goto fail; } close(fd); s = readmbox(mb); if (s){ fprint(2, "readmbox: %s", s); goto fail; } if (!mkmboxfs(mb, dir)) goto fail; sysremove(mbox); if (sysrename(tmp, mbox) < 0){ fprint(2, "%s: can't rename temp file: %r\n", argv0); goto fail; } if (lk) sysunlock(lk); exits(nil); fail: if (lk) sysunlock(lk); exits("fail"); }