#include "common.h" #include #include #include #include "dat.h" typedef struct Header Header; struct Header { char *type; uintptr offset; char *(*f)(Message*, Header*, char*, char*); int len; int str; }; /* headers */ static char *ctype(Message*, Header*, char*, char*); static char *cencoding(Message*, Header*, char*, char*); static char *cdisposition(Message*, Header*, char*, char*); static char *from822(Message*, Header*, char*, char*); static char *replace822(Message*, Header*, char*, char*); static char *concat822(Message*, Header*, char*, char*); static char *copy822(Message*, Header*, char*, char*); static char *ref822(Message*, Header*, char*, char*); enum { Mhead = 11, /* offset of first mime header */ }; #define O(x) offsetof(Message, x) static Header head[] = { "date:", O(date822), copy822, 0, 0, "from:", O(from), from822, 0, 1, "to:", O(to), concat822, 0, 1, "sender:", O(sender), replace822, 0, 1, "reply-to:", O(replyto), replace822, 0, 1, "subject:", O(subject), copy822, 0, 1, "cc:", O(cc), concat822, 0, 1, "bcc:", O(bcc), concat822, 0, 1, "in-reply-to:", O(inreplyto), replace822, 0, 1, "message-id:", O(messageid), replace822, 0, 1, "references:", ~0, ref822, 0, 0, [Mhead] "content-type:", ~0, ctype, 0, 0, "content-transfer-encoding:", ~0, cencoding, 0, 0, "content-disposition:", ~0, cdisposition, 0, 0, }; static Mailboxinit *boxinit[] = { imap4mbox, pop3mbox, mdirmbox, // planbmbox, plan9mbox, }; /* * do we want to plumb flag changes? */ char* syncmbox(Mailbox *mb, int doplumb) { char *s; int n, d, y, a; Message *m, *next; if(semacquire(&mb->syncsem, 0) <= 0) return nil; a = mb->root->subname; if(rdidxfile(mb, doplumb) == -2) wridxfile(mb); if(s = mb->sync(mb, doplumb, &n)){ semrelease(&mb->syncsem, 1); return s; } d = 0; y = 0; for(m = mb->root->part; m; m = next){ next = m->next; if(m->cstate & Cidxstale) y++; if(m->deleted == 0 || m->refs > 0) continue; if(mb->delete && m->inmbox && m->deleted & Deleted) mb->delete(mb, m); if(!m->inmbox){ delmessage(mb, m); d++; } } a = mb->root->subname - a; assert(a >= 0); if(n + d + y + a){ iprint("deleted: %d; new %d; stale %d\n", d, n, y); logmsg(nil, "deleted: %d; new %d; stale %d", d, n, y); wridxfile(mb); } if(n + d + y + a){ mb->vers++; henter(PATH(0, Qtop), mb->name, (Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb); } semrelease(&mb->syncsem, 1); return nil; } /* * not entirely clear where the locking should take place, if * it is required. */ char* mboxrename(char *a, char *b, int flags) { char f0[Pathlen + 4], f1[Pathlen + 4], *err, *p0, *p1; Mailbox *mb; snprint(f0, sizeof f0, "%s", a); snprint(f1, sizeof f1, "%s", b); err = newmbox(f0, nil, 0, &mb); dprint("mboxrename %s %s -> %s\n", f0, f1, err); if(!err && !mb->rename) err = "rename not supported"; if(err) goto done; err = mb->rename(mb, f1, flags); if(err) goto done; if(flags & Rtrunc) /* we're comitted, so forget bailing */ err = newmbox(f0, nil, DMcreate, 0); p0 = f0 + strlen(f0); p1 = f1 + strlen(f1); strcat(f0, ".idx"); strcat(f1, ".idx"); rename(f0, f1, 0); *p0 = *p1 = 0; strcat(f0, ".imp"); strcat(f1, ".imp"); rename(f0, f1, 0); snprint(mb->path, sizeof mb->path, "%s", b); hfree(PATH(0, Qtop), mb->name); p0 = strrchr(mb->path, '/') + 1; if(p0 == (char*)1) p0 = mb->path; snprint(mb->name, sizeof mb->name, "%s", p0); henter(PATH(0, Qtop), mb->name, (Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb); done: if(!mb) return err; qunlock(mb); // if(err) // mboxdecref(mb); return err; } static void initheaders(void) { int i; static int already; if(already) return; already = 1; for(i = 0; i < nelem(head); i++) head[i].len = strlen(head[i].type); } char* newmbox(char *path, char *name, int flags, Mailbox **r) { char *p, *rv; int i; Mailbox *mb, **l; initheaders(); mb = emalloc(sizeof *mb); mb->idxsem = 1; mb->syncsem = 1; mb->flags = flags; strncpy(mb->path, path, sizeof mb->path - 1); p = name; if(p == nil){ p = strrchr(path, '/'); if(p == nil) p = path; else p++; if(*p == 0){ free(mb); return "bad mbox name"; } } strncpy(mb->name, p, sizeof mb->name - 1); mb->idxread = genericidxread; mb->idxwrite = genericidxwrite; mb->idxinvalid = genericidxinvalid; /* check for a mailbox type */ rv = Enotme; /* can't happen; shut compiler up */ for(i = 0; i < nelem(boxinit); i++) if((rv = boxinit[i](mb, path)) != Enotme) break; if(rv){ free(mb); return rv; } /* make sure name isn't taken */ qlock(&mbllock); for(l = &mbl; *l != nil; l = &(*l)->next) if(strcmp((*l)->name, mb->name) == 0){ if(strcmp(path, (*l)->path) == 0) rv = nil; else rv = "mbox name in use"; if(mb->close) mb->close(mb); free(mb); qunlock(&mbllock); return rv; } /* always try locking */ mb->dolock = 1; mb->refs = 1; mb->next = nil; mb->id = newid(); mb->root = newmessage(nil); mb->mtree = mkavltree(mtreecmp); *l = mb; qunlock(&mbllock); qlock(mb); henter(PATH(0, Qtop), mb->name, (Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb); if(mb->ctl) henter(PATH(mb->id, Qmbox), "ctl", (Qid){PATH(mb->id, Qmboxctl), 0, QTFILE}, nil, mb); rv = syncmbox(mb, 0); if(r) *r = mb; else qunlock(mb); return rv; } /* close the named mailbox */ void freembox(char *name) { Mailbox **l, *mb; qlock(&mbllock); for(l=&mbl; *l != nil; l=&(*l)->next) if(strcmp(name, (*l)->name) == 0){ mb = *l; *l = mb->next; mboxdecref(mb); break; } hfree(PATH(0, Qtop), name); qunlock(&mbllock); } void syncallmboxes(void) { char *err; Mailbox *m; qlock(&mbllock); for(m = mbl; m != nil; m = m->next) if(err = syncmbox(m, 1)) eprint("syncmbox: %s\n", err); qunlock(&mbllock); } char* removembox(char *name, int flags) { int found; Mailbox **l, *mb; found = 0; qlock(&mbllock); for(l=&mbl; *l != nil; l=&(*l)->next) if(strcmp(name, (*l)->path) == 0){ mb = *l; *l = mb->next; mb->flags |= ORCLOSE; mb->rmflags = flags; mboxdecref(mb); found = 1; break; } hfree(PATH(0, Qtop), name); qunlock(&mbllock); if(found == 0) return "maibox not found"; return 0; } /* * look for the date in the first Received: line. * it's likely to be the right time zone (it's * the local system) and in a convenient format. */ static int rxtotm(Message *m, Tm *tm) { char *p, *q; int r; if(cistrncmp(m->header, "received:", 9)) return -1; q = strchr(m->header, ';'); if(!q) return -1; p = q; while((p = strchr(p, '\n')) != nil){ if(p[1] != ' ' && p[1] != '\t' && p[1] != '\n') break; p++; } if(!p) return -1; *p = '\0'; r = strtotm(q + 1, tm); *p = '\n'; return r; } Message* gettopmsg(Mailbox *mb, Message *m) { while(!Topmsg(mb, m)) m = m->whole; return m; } void datesec(Mailbox *mb, Message *m) { char *s; vlong v; Tm tm; if(m->fileid > 1000000ull<<8) return; if(m->unixfrom && strtotm(m->unixfrom, &tm) >= 0) v = tm2sec(&tm); else if(m->date822 && strtotm(m->date822, &tm) >= 0) v = tm2sec(&tm); else if(rxtotm(m, &tm) >= 0) v = tm2sec(&tm); else{ s = rtab[m->type].s; logmsg(gettopmsg(mb, m), "%s:%s: datasec %s %s\n", mb->path, m->whole? m->whole->name: "?", m->name, s); if(Topmsg(mb, m) || strcmp(s, "message/rfc822") == 0) abort(); v = 0; } m->fileid = v<<8; } /* * parse a message */ extern void sanemsg(Message*); extern void sanembmsg(Mailbox*, Message*); static Message* haschild(Message *m, int i) { for(m = m->part; m && i; i--) m = m->next; if(m) m->mimeflag = 0; return m; } static void parseattachments(Message *m, Mailbox *mb) { char *p, *x; int i; Message *nm, **l; /* if there's a boundary, recurse... */ sanemsg(m); // dprint("parseattachments %p %ld\n", m->start, m->end - m->start); if(m->boundary != nil){ p = m->body; nm = nil; l = &m->part; for(i = 0;;){ sanemsg(m); x = strstr(p, m->boundary); /* two sequential boundaries; ignore nil message */ if(nm && x == p){ p = strchr(x, '\n'); if(p == nil){ nm->rbend = nm->bend = nm->end = x; sanemsg(nm); break; } p = p + 1; continue; } /* no boundary, we're done */ if(x == nil){ if(nm != nil){ nm->rbend = nm->bend = nm->end = m->bend; sanemsg(nm); if(nm->end == nm->start) nm->mimeflag |= Mtrunc; } break; } /* boundary must be at the start of a line */ if(x != m->body && x[-1] != '\n'){ p = x + 1; continue; } if(nm != nil) { nm->rbend = nm->bend = nm->end = x; sanemsg(nm);} x += strlen(m->boundary); /* is this the last part? ignore anything after it */ if(strncmp(x, "--", 2) == 0) break; p = strchr(x, '\n'); if(p == nil) break; if((nm = haschild(m, i++)) == nil){ nm = newmessage(m); *l = nm; l = &nm->next; } nm->start = ++p; assert(nm->ballocd == 0); nm->mheader = nm->header = nm->body = nm->rbody = nm->start; } for(nm = m->part; nm != nil; nm = nm->next){ parse(mb, nm, 0, 1); cachehash(mb, nm); /* botchy place for this */ } return; } /* if we've got an rfc822 message, recurse... */ if(strcmp(rtab[m->type].s, "message/rfc822") == 0){ if((nm = haschild(m, 0)) == nil){ nm = newmessage(m); m->part = nm; } assert(nm->ballocd == 0); nm->start = nm->header = nm->body = nm->rbody = m->body; nm->end = nm->bend = nm->rbend = m->bend; if(nm->end == nm->start) nm->mimeflag |= Mtrunc; parse(mb, nm, 0, 0); cachehash(mb, nm); /* botchy place for this */ } } void parseheaders(Mailbox *mb, Message *m, int addfrom, int justmime) { char *p, *e, *o, *t, *s; int i, i0, n; uintptr a; sanembmsg(mb, m); /* fails with pop but i want this debugging for now */ /* parse mime headers */ p = m->header; i0 = 0; if(justmime) i0 = Mhead; s = emalloc(2048); e = s + 2048 - 1; while((n = hdrlen(p, m->end)) != 0){ if(n > e - s){ s = erealloc(s, n); e = s + n - 1; } rfc2047(s, e, p, n, 1); p += n; for(i = i0; i < nelem(head); i++) if(!cistrncmp(s, head[i].type, head[i].len)){ a = head[i].offset; if(a != ~0){ if(o = *(char**)((char*)m + a)) continue; t = head[i].f(m, head + i, o, s); *(char**)((char*)m + a) = t; }else head[i].f(m, head + i, 0, s); break; } } free(s); sanembmsg(mb, m); /* fails with pop but i want this debugging for now */ /* the blank line isn't really part of the body or header */ if(justmime){ m->mhend = p; m->hend = m->header; } else{ m->hend = p; m->mhend = m->header; } /* * not all attachments have mime headers themselves. */ if(!m->mheader) m->mhend = 0; if(*p == '\n') p++; m->rbody = m->body = p; if(!justmime) datesec(mb, m); /* * only fake header for top-level messages for pop3 and imap4 * clients (those protocols don't include the unix header). * adding the unix header all the time screws up mime-attached * rfc822 messages. */ sanembmsg(mb, m); /* fails with pop but i want this debugging for now */ if(!addfrom && !m->unixfrom) m->unixheader = nil; else if(m->unixheader == nil){ if(m->unixfrom && strcmp(m->unixfrom, "???") != 0) p = m->unixfrom; else if(m->from) p = m->from; else p = "???"; m->unixheader = smprint("From %s %Δ\n", p, m->fileid); } m->cstate |= Cheader; sanembmsg(mb, m); } char* promote(char *s) { return s? strdup(s): nil; } void parsebody(Message *m, Mailbox *mb) { char *s; int l; Message *nm; /* recurse */ s = rtab[m->type].s; l = rtab[m->type].l; if(l >= 10 && strncmp(s, "multipart/", 10) == 0) parseattachments(m, mb); else if(l == 14 && strcmp(s, "message/rfc822") == 0){ decode(m); parseattachments(m, mb); nm = m->part; /* promote headers */ if(m->replyto == nil && m->from == nil && m->sender == nil){ m->from = promote(nm->from); m->to = promote(nm->to); m->date822 = promote(nm->date822); m->sender = promote(nm->sender); m->replyto = promote(nm->replyto); m->subject = promote(nm->subject); } }else if(strncmp(rtab[m->type].s, "text/", 5) == 0) sanemsg(m); m->rawbsize = m->rbend - m->rbody; m->cstate |= Cbody; } void parse(Mailbox *mb, Message *m, int addfrom, int justmime) { sanemsg(m); assert(m->end - m->start > 0 || m->mimeflag&Mtrunc && m->end - m->start == 0); if((m->cstate & Cheader) == 0) parseheaders(mb, m, addfrom, justmime); parsebody(m, mb); sanemsg(m); } static char* skipwhite(char *p) { while(isascii(*p) && isspace(*p)) p++; return p; } static char* skiptosemi(char *p) { while(*p && *p != ';') p++; while(*p == ';' || (isascii(*p) && isspace(*p))) p++; return p; } static char* getstring(char *p, char *s, char *e, int dolower) { int c; p = skipwhite(p); if(*p == '"'){ for(p++; (c = *p) != '"'; p++){ if(c == '\\') c = *++p; /* * 821 says after \ can be anything at all. * we just don't care. */ if(c == 0) break; if(c < ' ') continue; if(dolower && c >= 'A' && c <= 'Z') c += 0x20; s = sputc(s, e, c); } if(*p == '"') p++; }else{ for(; (c = *p) && !isspace(c) && c != ';'; p++){ if(c == '\\') c = *++p; /* * 821 says after \ can be anything at all. * we just don't care. */ if(c == 0) break; if(c < ' ') continue; if(dolower && c >= 'A' && c <= 'Z') c += 0x20; s = sputc(s, e, c); } } *s = 0; return p; } static void setfilename(Message *m, char *p) { char buf[Pathlen]; free(m->filename); getstring(p, buf, buf + sizeof buf - 1, 0); m->filename = smprint("%s", buf); for(p = m->filename; *p; p++) if(*p == ' ' || *p == '\t' || *p == ';') *p = '_'; } static char* rtrim(char *p) { char *e; if(p == 0) return p; e = p + strlen(p) - 1; while(e > p && isascii(*e) && isspace(*e)) *e-- = 0; return p; } static char* addr822(char *p, char **ac) { int n, c, space, incomment, addrdone, inanticomment, quoted; char s[128+1], *ps, *e, *x, *list; list = 0; s[0] = 0; ps = s; e = s + sizeof s; space = quoted = incomment = addrdone = inanticomment = 0; n = 0; for(; c = *p; p++){ if(!inanticomment && !quoted && !space && ps != s && c == ' '){ ps = sputc(ps, e, c); space = 1; continue; } space = 0; if(!quoted && isspace(c) || c == '\r') continue; /* strings are always treated as atoms */ if(!quoted && c == '"'){ if(!addrdone && !incomment && !ac) ps = sputc(ps, e, c); for(p++; c = *p; p++){ if(ac && c == '"') break; if(!addrdone && !incomment) ps = sputc(ps, e, c); if(!quoted && *p == '"') break; if(*p == '\\') quoted = 1; else quoted = 0; } if(c == 0) break; quoted = 0; continue; } /* ignore everything in an expicit comment */ if(!quoted && c == '('){ incomment = 1; continue; } if(incomment){ if(!quoted && c == ')') incomment = 0; quoted = 0; continue; } /* anticomments makes everything outside of them comments */ if(!quoted && c == '<' && !inanticomment){ if(ac){ *ps-- = 0; if(ps > s && *ps == ' ') *ps = 0; if(*ac){ *ac = smprint("%s, %s", x=*ac, s); free(x); }else *ac = smprint("%s", s); } inanticomment = 1; ps = s; continue; } if(!quoted && c == '>' && inanticomment){ addrdone = 1; inanticomment = 0; continue; } /* commas separate addresses */ if(!quoted && c == ',' && !inanticomment){ *ps = 0; addrdone = 0; if(n++ != 0){ list = smprint("%s %s", x=list, s); free(x); }else list = smprint("%s", s); ps = s; continue; } /* what's left is part of the address */ ps = sputc(ps, e, c); /* quoted characters are recognized only as characters */ if(c == '\\') quoted = 1; else quoted = 0; } if(ps > s){ *ps = 0; if(n != 0){ list = smprint("%s %s", x=list, s); free(x); }else list = smprint("%s", s); } return rtrim(list); } /* * per rfc2822 §4.5.3, permit multiple to, cc and bcc headers by * concatenating their values. */ static char* concat822(Message*, Header *h, char *o, char *p) { char *s, *n; p += strlen(h->type); s = addr822(p, 0); if(o){ n = smprint("%s %s", o, s); free(s); }else n = s; return n; } static char* from822(Message *m, Header *h, char*, char *p) { if(m->ffrom) free(m->ffrom); m->from = 0; return addr822(p + h->len, &m->ffrom); } static char* replace822(Message *, Header *h, char*, char *p) { return addr822(p + h->len, 0); } static char* copy822(Message*, Header *h, char*, char *p) { return rtrim(strdup(skipwhite(p + h->len))); } /* * firefox, e.g. doesn't keep references unique */ static int uniqarray(char **a, int n, int allocd) { int i, j; for(i = 0; i < n; i++) for(j = i + 1; j < n; j++) if(strcmp(a[i], a[j]) == 0){ if(allocd) free(a[j]); memmove(a + j, a + j + 1, sizeof *a*(n - (j + 1))); a[--n] = 0; } return n; } static char* ref822(Message *m, Header *h, char*, char *p) { char **a, *s, *f[Nref + 1]; int i, j, k, n; s = strdup(skipwhite(p + h->len)); n = getfields(s, f, nelem(f), 1, "<> \n\t\r,"); if(n > Nref) n = Nref; n = uniqarray(f, n, 0); a = m->references; for(i = 0; i < Nref; i++) if(a[i] == 0) break; /* * if there are too many references, drop from the beginning * of the list. */ j = i + n - Nref; if(j > 0){ if(j > Nref) j = Nref; for(k = 0; k < j; k++) free(a[k]); memmove(a, a + j, sizeof a[0]*(Nref - j)); memset(a + j, 0, Nref - j); i -= j; } for(j = 0; j < n;) a[i++] = strdup(f[j++]); free(s); uniqarray(a, i, 1); return (char*)~0; } static int isattribute(char **pp, char *attr) { char *p; int n; n = strlen(attr); p = *pp; if(cistrncmp(p, attr, n) != 0) return 0; p += n; while(*p == ' ') p++; if(*p++ != '=') return 0; while(*p == ' ') p++; *pp = p; return 1; } static char* ctype(Message *m, Header *h, char*, char *p) { char buf[128], *e; e = buf + sizeof buf - 1; p = getstring(skipwhite(p + h->len), buf, e, 1); m->type = newrefs(buf); for(; *p; p = skiptosemi(p)) if(isattribute(&p, "boundary")){ p = getstring(p, buf, e, 0); free(m->boundary); m->boundary = smprint("--%s", buf); } else if(cistrncmp(p, "multipart", 9) == 0){ /* * the first unbounded part of a multipart message, * the preamble, is not displayed or saved */ } else if(isattribute(&p, "name")){ if(m->filename == nil) setfilename(m, p); } else if(isattribute(&p, "charset")){ p = getstring(p, buf, e, 0); lowercase(buf); m->charset = newrefs(buf); } return (char*)~0; } static char* cencoding(Message *m, Header *h, char*, char *p) { p = skipwhite(p + h->len); if(cistrncmp(p, "base64", 6) == 0) m->encoding = Ebase64; else if(cistrncmp(p, "quoted-printable", 16) == 0) m->encoding = Equoted; return (char*)~0; } static char* cdisposition(Message *m, Header *h, char*, char *p) { for(p = skipwhite(p + h->len); *p; p = skiptosemi(p)) if(cistrncmp(p, "inline", 6) == 0) m->disposition = Dinline; else if(cistrncmp(p, "attachment", 10) == 0) m->disposition = Dfile; else if(cistrncmp(p, "filename=", 9) == 0){ p += 9; setfilename(m, p); } return (char*)~0; } ulong msgallocd; ulong msgfreed; Message* newmessage(Message *parent) { static int id; Message *m; msgallocd++; m = emalloc(sizeof *m); dprint("newmessage %ld %p %p\n", msgallocd, parent, m); m->disposition = Dnone; m->type = newrefs("text/plain"); m->charset = newrefs("iso-8859-1"); m->cstate = Cidxstale; m->flags = Frecent; m->id = newid(); if(parent) snprint(m->name, sizeof m->name, "%d", ++(parent->subname)); if(parent == nil) parent = m; m->whole = parent; m->hlen = -1; return m; } /* delete a message from a mailbox */ void delmessage(Mailbox *mb, Message *m) { Message **l; mb->vers++; msgfreed++; if(m->whole != m){ /* unchain from parent */ for(l = &m->whole->part; *l && *l != m; l = &(*l)->next) ; if(*l != nil) *l = m->next; /* clear out of name lookup hash table */ if(m->whole->whole == m->whole) hfree(PATH(mb->id, Qmbox), m->name); else hfree(PATH(m->whole->id, Qdir), m->name); hfree(PATH(m->id, Qdir), "xxx"); /* sleezy speedup */ } if(Topmsg(mb, m)){ if(m != mb->root) mtreedelete(mb, m); cachefree(mb, m, 1); } delrefs(m->type); delrefs(m->charset); idxfree(m); while(m->part) delmessage(mb, m->part); free(m->unixfrom); free(m->unixheader); free(m->date822); free(m->inreplyto); free(m->boundary); free(m->filename); free(m); } void unnewmessage(Mailbox *mb, Message *parent, Message *m) { assert(parent->subname > 0); m->deleted = Dup; delmessage(mb, m); parent->subname -= 1; } /* mark messages (identified by path) for deletion */ char* delmessages(int ac, char **av) { int i, needwrite; Mailbox *mb; Message *m; qlock(&mbllock); for(mb = mbl; mb != nil; mb = mb->next) if(strcmp(av[0], mb->name) == 0){ qlock(mb); break; } qunlock(&mbllock); if(mb == nil) return "no such mailbox"; needwrite = 0; for(i = 1; i < ac; i++) for(m = mb->root->part; m != nil; m = m->next) if(strcmp(m->name, av[i]) == 0){ if(!m->deleted){ mailplumb(mb, m, 1); needwrite = 1; m->deleted = Deleted; logmsg(m, "deleting"); } break; } if(needwrite) syncmbox(mb, 1); qunlock(mb); return 0; } char* flagmessages(int argc, char **argv) { char *err, *rerr; int i, needwrite; Mailbox *mb; Message *m; if(argc%2) return "bad flags"; qlock(&mbllock); for(mb = mbl; mb; mb = mb->next) if(strcmp(*argv, mb->name) == 0){ qlock(mb); break; } qunlock(&mbllock); if(mb == nil) return "no such mailbox"; needwrite = 0; rerr = 0; for(i = 1; i < argc; i += 2) for(m = mb->root->part; m; m = m->next) if(strcmp(m->name, argv[i]) == 0){ if(err = modflags(mb, m, argv[i + 1])) rerr = err; else needwrite = 1; } if(needwrite) syncmbox(mb, 1); qunlock(mb); return rerr; } /* * the following are called with the mailbox qlocked */ void msgincref(Message *m) { m->refs++; } void msgdecref(Mailbox *mb, Message *m) { assert(m->refs > 0); m->refs--; if(m->refs == 0){ if(m->deleted) syncmbox(mb, 1); else putcache(mb, m); } } /* * the following are called with mbllock'd */ void mboxincref(Mailbox *mb) { assert(mb->refs > 0); mb->refs++; } static void mbrmidx(char *path, int flags) { char buf[Pathlen]; snprint(buf, sizeof buf, "%s.idx", path); vremove(buf); if((flags & Rtrunc) == 0){ snprint(buf, sizeof buf, "%s.imp", path); vremove(buf); } } void mboxdecref(Mailbox *mb) { assert(mb->refs > 0); qlock(mb); mb->refs--; if(mb->refs == 0){ syncmbox(mb, 1); delmessage(mb, mb->root); if(mb->ctl) hfree(PATH(mb->id, Qmbox), "ctl"); if(mb->close) mb->close(mb); if(mb->flags & ORCLOSE && mb->remove) if(mb->remove(mb, mb->rmflags)) rmidx(mb->path, mb->rmflags); free(mb->mtree); free(mb->d); free(mb); } else qunlock(mb); } /* just space over \r. sleezy but necessary for ms email. */ int deccr(char *x, int len) { char *e; e = x + len; for(;;){ x = memchr(x, '\r', e - x); if(x == nil) break; *x = ' '; } return len; } /* * undecode message body */ void decode(Message *m) { int i, len; char *x; if(m->decoded) return; switch(m->encoding){ case Ebase64: len = m->bend - m->body; i = (len*3)/4 + 1; /* room for max chars + null */ x = emalloc(i); len = dec64((uchar*)x, i, m->body, len); if(len == -1){ free(x); break; } if(strncmp(rtab[m->type].s, "text/", 5) == 0) len = deccr(x, len); if(m->ballocd) free(m->body); m->body = x; m->bend = x + len; m->ballocd = 1; break; case Equoted: len = m->bend - m->body; x = emalloc(len + 2); /* room for null and possible extra nl */ len = decquoted(x, m->body, m->bend, 0); if(m->ballocd) free(m->body); m->body = x; m->bend = x + len; m->ballocd = 1; break; default: break; } m->decoded = 1; } /* convert x to utf8 */ void convert(Message *m) { int len; char *x; /* don't convert if we're not a leaf, not text, or already converted */ if(m->converted) return; m->converted = 1; if(m->part != nil || cistrncmp(rtab[m->type].s, "text", 4) != 0) return; len = xtoutf(rtab[m->charset].s, &x, m->body, m->bend); if(len > 0){ if(m->ballocd) free(m->body); m->body = x; m->bend = x + len; m->ballocd = 1; } } static int hex2int(int x) { if(x >= '0' && x <= '9') return x - '0'; if(x >= 'A' && x <= 'F') return x - 'A' + 10; if(x >= 'a' && x <= 'f') return x - 'a' + 10; return -1; } /* * underscores are translated in 2047 headers (uscores=1) * but not in the body (uscores=0) */ static char* decquotedline(char *out, char *in, char *e, int uscores) { int c, soft; /* dump trailing white space */ while(e >= in && (*e == ' ' || *e == '\t' || *e == '\r' || *e == '\n')) e--; /* trailing '=' means no newline */ if(*e == '='){ soft = 1; e--; } else soft = 0; while(in <= e){ c = (*in++) & 0xff; switch(c){ case '_': if(uscores){ *out++ = ' '; break; } default: *out++ = c; break; case '=': c = hex2int(*in++)<<4; c |= hex2int(*in++); if(c != -1) *out++ = c; else{ *out++ = '='; in -= 2; } break; } } if(!soft) *out++ = '\n'; *out = 0; return out; } int decquoted(char *out, char *in, char *e, int uscores) { char *p, *nl; p = out; while((nl = strchr(in, '\n')) != nil && nl < e){ p = decquotedline(p, in, nl, uscores); in = nl + 1; } if(in < e) p = decquotedline(p, in, e - 1, uscores); /* make sure we end with a new line */ if(*(p - 1) != '\n'){ *p++ = '\n'; *p = 0; } return p - out; } char* lowercase(char *p) { char *op; int c; for(op = p; c = *p; p++) if(isupper(c)) *p = tolower(c); return op; } /* translate latin1 directly since it fits neatly in utf */ static int latin1toutf(char **out, char *in, char *e) { int n; char *p; Rune r; n = 0; for(p = in; p < e; p++) if(*p & 0x80) n++; if(n == 0) return 0; n += e - in; *out = p = malloc(n + 1); if(p == nil) return 0; for(; in < e; in++){ r = (uchar)*in; p += runetochar(p, &r); } *p = 0; return p - *out; } /* translate any thing using the tcs program */ int xtoutf(char *charset, char **out, char *in, char *e) { char *av[4], *p; int totcs[2], fromtcs[2], n, len, sofar; /* might not need to convert */ if(cistrcmp(charset, "us-ascii") == 0 || cistrcmp(charset, "utf-8") == 0) return 0; if(cistrcmp(charset, "iso-8859-1") == 0) return latin1toutf(out, in, e); len = e - in + 1; sofar = 0; *out = p = malloc(len + 1); if(p == nil) return 0; av[0] = charset; av[1] = "-f"; av[2] = charset; av[3] = 0; if(pipe(totcs) < 0) goto error; if(pipe(fromtcs) < 0){ close(totcs[0]); close(totcs[1]); goto error; } switch(rfork(RFPROC|RFFDG|RFNOWAIT)){ case -1: close(fromtcs[0]); close(fromtcs[1]); close(totcs[0]); close(totcs[1]); goto error; case 0: close(fromtcs[0]); close(totcs[1]); dup(fromtcs[1], 1); dup(totcs[0], 0); close(fromtcs[1]); close(totcs[0]); dup(open("/dev/null", OWRITE), 2); exec("/bin/tcs", av); _exits(""); default: close(fromtcs[1]); close(totcs[0]); switch(rfork(RFPROC|RFFDG|RFNOWAIT)){ case -1: close(fromtcs[0]); close(totcs[1]); goto error; case 0: close(fromtcs[0]); while(in < e){ n = write(totcs[1], in, e - in); if(n <= 0) break; in += n; } close(totcs[1]); _exits(""); default: close(totcs[1]); for(;;){ n = read(fromtcs[0], &p[sofar], len - sofar); if(n <= 0) break; sofar += n; p[sofar] = 0; if(sofar == len){ len += 1024; p = realloc(p, len + 1); if(p == nil) goto error; *out = p; } } close(fromtcs[0]); break; } break; } if(sofar == 0) goto error; return sofar; error: free(*out); *out = nil; return 0; } void * emalloc(ulong n) { void *p; p = mallocz(n, 1); if(!p) sysfatal("malloc %lud: %r", n); setmalloctag(p, getcallerpc(&n)); return p; } void * erealloc(void *p, ulong n) { if(n == 0) n = 1; p = realloc(p, n); if(!p) sysfatal("realloc %lud: %r", n); setrealloctag(p, getcallerpc(&p)); return p; } int myplumbsend(int fd, Plumbmsg *m) { char *buf; int n; buf = plumbpack(m, &n); if(buf == nil) return -1; n = write(fd, buf, n); free(buf); return n; } void mailplumb(Mailbox *mb, Message *m, int delete) { char buf[256], dbuf[SHA1dlen*2 + 1], len[10], date[30], *from, *subject; int ai, cache; Plumbmsg p; Plumbattr a[7]; static int fd = -1; cache = insurecache(mb, m) == 0; /* living dangerously if deleted */ subject = m->subject; if(subject == nil) subject = ""; if(m->from != nil) from = m->from; else if(m->unixfrom != nil) from = m->unixfrom; else from = ""; sprint(len, "%lud", m->size); if(biffing && !delete) fprint(2, "[ %s / %s / %s ]\n", from, subject, len); if(!plumbing) goto out; if(fd < 0) fd = plumbopen("send", OWRITE); if(fd < 0) goto out; p.src = "mailfs"; p.dst = "seemail"; p.wdir = "/mail/fs"; p.type = "text"; ai = 0; a[ai].name = "filetype"; a[ai].value = "mail"; a[++ai].name = "sender"; a[ai].value = from; a[ai-1].next = &a[ai]; a[++ai].name = "length"; a[ai].value = len; a[ai-1].next = &a[ai]; a[++ai].name = "mailtype"; a[ai].value = delete? "delete": "new"; a[ai-1].next = &a[ai]; snprint(date, sizeof date, "%Δ", m->fileid); a[++ai].name = "date"; a[ai].value = date; a[ai-1].next = &a[ai]; if(m->digest){ snprint(dbuf, sizeof dbuf, "%A", m->digest); a[++ai].name = "digest"; a[ai].value = dbuf; a[ai-1].next = &a[ai]; } a[ai].next = nil; p.attr = a; snprint(buf, sizeof buf, "%s/%s/%s", mntpt, mb->name, m->name); p.ndata = strlen(buf); p.data = buf; myplumbsend(fd, &p); out: if(cache) msgdecref(mb, m); } /* * count the number of lines in the body (for imap4) */ ulong countlines(Message *m) { char *p; ulong i; i = 0; for(p = strchr(m->rbody, '\n'); p != nil && p < m->rbend; p = strchr(p + 1, '\n')) i++; return i; } static char *logf = "fs"; void logmsg(Message *m, char *fmt, ...) { char buf[256], *p, *e; va_list args; if(!lflag) return; e = buf + sizeof buf; p = seprint(buf, e, "%s.%d: ", user, getpid()); if(m) p = seprint(p, e, "from %s digest %A ", m->from, m->digest); va_start(args, fmt); vseprint(p, e, fmt, args); va_end(args); if(Sflag) fprint(2, "%s\n", buf); syslog(Sflag, logf, "%s", buf); } void iprint(char *fmt, ...) { char buf[256], *p, *e; va_list args; if(!iflag) return; e = buf + sizeof buf; p = seprint(buf, e, "%s.%d: ", user, getpid()); va_start(args, fmt); vseprint(p, e, fmt, args); vfprint(2, fmt, args); va_end(args); syslog(Sflag, logf, "%s", buf); } void eprint(char *fmt, ...) { char buf[256], buf2[256], *p, *e; va_list args; e = buf + sizeof buf; p = seprint(buf, e, "%s.%d: ", user, getpid()); va_start(args, fmt); vseprint(p, e, fmt, args); e = buf2 + sizeof buf2; p = seprint(buf2, e, "upas/fs: "); vseprint(p, e, fmt, args); va_end(args); syslog(Sflag, logf, "%s", buf); fprint(2, "%s", buf2); } /* * convert an RFC822 date into a Unix style date * for when the Unix From line isn't there (e.g. POP3). * enough client programs depend on having a Unix date * that it's easiest to write this conversion code once, right here. * * people don't follow RFC822 particularly closely, * so we use strtotm, which is a bunch of heuristics. */ char* date822tounix(Message *, char *s) { char *p, *q; Tm tm; if(strtotm(s, &tm) < 0) return nil; p = asctime(&tm); if(q = strchr(p, '\n')) *q = '\0'; return strdup(p); }