aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/upas/fs/mbox.c
diff options
context:
space:
mode:
authorrsc <devnull@localhost>2005-10-29 16:26:44 +0000
committerrsc <devnull@localhost>2005-10-29 16:26:44 +0000
commit5cdb17983ae6e6367ad7a940cb219eab247a9304 (patch)
tree8ca1ef49af2a96e7daebe624d91fdf679814a057 /src/cmd/upas/fs/mbox.c
parentcd3745196389579fb78b9b01ef1daefb5a57aa71 (diff)
downloadplan9port-5cdb17983ae6e6367ad7a940cb219eab247a9304.tar.gz
plan9port-5cdb17983ae6e6367ad7a940cb219eab247a9304.tar.bz2
plan9port-5cdb17983ae6e6367ad7a940cb219eab247a9304.zip
Thanks to John Cummings.
Diffstat (limited to 'src/cmd/upas/fs/mbox.c')
-rw-r--r--src/cmd/upas/fs/mbox.c1601
1 files changed, 1601 insertions, 0 deletions
diff --git a/src/cmd/upas/fs/mbox.c b/src/cmd/upas/fs/mbox.c
new file mode 100644
index 00000000..c539df8d
--- /dev/null
+++ b/src/cmd/upas/fs/mbox.c
@@ -0,0 +1,1601 @@
+#include "common.h"
+#include <ctype.h>
+#include <plumb.h>
+#include <libsec.h>
+#include <thread.h>
+#include "dat.h"
+
+extern char* dirtab[]; /* jpc */
+
+typedef struct Header Header;
+
+struct Header {
+ char *type;
+ void (*f)(Message*, Header*, char*);
+ int len;
+};
+
+/* headers */
+static void ctype(Message*, Header*, char*);
+static void cencoding(Message*, Header*, char*);
+static void cdisposition(Message*, Header*, char*);
+static void date822(Message*, Header*, char*);
+static void from822(Message*, Header*, char*);
+static void to822(Message*, Header*, char*);
+static void sender822(Message*, Header*, char*);
+static void replyto822(Message*, Header*, char*);
+static void subject822(Message*, Header*, char*);
+static void inreplyto822(Message*, Header*, char*);
+static void cc822(Message*, Header*, char*);
+static void bcc822(Message*, Header*, char*);
+static void messageid822(Message*, Header*, char*);
+static void mimeversion(Message*, Header*, char*);
+static void nullsqueeze(Message*);
+enum
+{
+ Mhead= 11, /* offset of first mime header */
+};
+
+Header head[] =
+{
+ { "date:", date822, },
+ { "from:", from822, },
+ { "to:", to822, },
+ { "sender:", sender822, },
+ { "reply-to:", replyto822, },
+ { "subject:", subject822, },
+ { "cc:", cc822, },
+ { "bcc:", bcc822, },
+ { "in-reply-to:", inreplyto822, },
+ { "mime-version:", mimeversion, },
+ { "message-id:", messageid822, },
+
+[Mhead] { "content-type:", ctype, },
+ { "content-transfer-encoding:", cencoding, },
+ { "content-disposition:", cdisposition, },
+ { 0, },
+};
+
+/* static void fatal(char *fmt, ...); jpc */
+static void initquoted(void);
+/* static void startheader(Message*);
+static void startbody(Message*); jpc */
+static char* skipwhite(char*);
+static char* skiptosemi(char*);
+static char* getstring(char*, String*, int);
+static void setfilename(Message*, char*);
+/* static char* lowercase(char*); jpc */
+static int is8bit(Message*);
+static int headerline(char**, String*);
+static void initheaders(void);
+static void parseattachments(Message*, Mailbox*);
+
+int debug;
+
+char *Enotme = "path not served by this file server";
+
+enum
+{
+ Chunksize = 1024,
+};
+
+Mailboxinit *boxinit[] = {
+ imap4mbox,
+ pop3mbox,
+ plan9mbox,
+};
+
+char*
+syncmbox(Mailbox *mb, int doplumb)
+{
+ return (*mb->sync)(mb, doplumb);
+}
+
+/* create a new mailbox */
+char*
+newmbox(char *path, char *name, int std)
+{
+ Mailbox *mb, **l;
+ char *p, *rv;
+ int i;
+
+ initheaders();
+
+ mb = emalloc(sizeof(*mb));
+ strncpy(mb->path, path, sizeof(mb->path)-1);
+ if(name == 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);
+ } else {
+ strncpy(mb->name, name, sizeof(mb->name)-1);
+ }
+
+ rv = nil;
+ // check for a mailbox type
+ for(i=0; i<nelem(boxinit); i++)
+ if((rv = (*boxinit[i])(mb, path)) != Enotme)
+ break;
+ if(i == nelem(boxinit)){
+ free(mb);
+ return "bad path";
+ }
+
+ // on error, give up
+ 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->std = std;
+ *l = mb;
+ qunlock(&mbllock);
+
+ qlock(&mb->ql);
+ if(mb->ctl){
+ henter(PATH(mb->id, Qmbox), "ctl",
+ (Qid){PATH(mb->id, Qmboxctl), 0, QTFILE}, nil, mb);
+ }
+ rv = syncmbox(mb, 0);
+ qunlock(&mb->ql);
+
+ 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);
+}
+
+static void
+initheaders(void)
+{
+ Header *h;
+ static int already;
+
+ if(already)
+ return;
+ already = 1;
+
+ for(h = head; h->type != nil; h++)
+ h->len = strlen(h->type);
+}
+
+/*
+ * parse a Unix style header
+ */
+void
+parseunix(Message *m)
+{
+ char *p;
+ String *h;
+
+ h = s_new();
+ for(p = m->start + 5; *p && *p != '\r' && *p != '\n'; p++)
+ s_putc(h, *p);
+ s_terminate(h);
+ s_restart(h);
+
+ m->unixfrom = s_parse(h, s_reset(m->unixfrom));
+ m->unixdate = s_append(s_reset(m->unixdate), h->ptr);
+
+ s_free(h);
+}
+
+/*
+ * parse a message
+ */
+void
+parseheaders(Message *m, int justmime, Mailbox *mb, int addfrom)
+{
+ String *hl;
+ Header *h;
+ char *p, *q;
+ int i;
+
+ if(m->whole == m->whole->whole){
+ henter(PATH(mb->id, Qmbox), m->name,
+ (Qid){PATH(m->id, Qdir), 0, QTDIR}, m, mb);
+ } else {
+ henter(PATH(m->whole->id, Qdir), m->name,
+ (Qid){PATH(m->id, Qdir), 0, QTDIR}, m, mb);
+ }
+ for(i = 0; i < Qmax; i++)
+ henter(PATH(m->id, Qdir), dirtab[i],
+ (Qid){PATH(m->id, i), 0, QTFILE}, m, mb);
+
+ // parse mime headers
+ p = m->header;
+ hl = s_new();
+ while(headerline(&p, hl)){
+ if(justmime)
+ h = &head[Mhead];
+ else
+ h = head;
+ for(; h->type; h++){
+ if(cistrncmp(s_to_c(hl), h->type, h->len) == 0){
+ (*h->f)(m, h, s_to_c(hl));
+ break;
+ }
+ }
+ s_reset(hl);
+ }
+ s_free(hl);
+
+ // 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;
+ }
+ if(*p == '\n')
+ p++;
+ m->rbody = m->body = p;
+
+ // if type is text, get any nulls out of the body. This is
+ // for the two seans and imap clients that get confused.
+ if(strncmp(s_to_c(m->type), "text/", 5) == 0)
+ nullsqueeze(m);
+
+ //
+ // cobble together Unix-style from line
+ // for local mailbox messages, we end up recreating the
+ // original header.
+ // for pop3 messages, the best we can do is
+ // use the From: information and the RFC822 date.
+ //
+ if(m->unixdate == nil || strcmp(s_to_c(m->unixdate), "???") == 0
+ || strcmp(s_to_c(m->unixdate), "Thu Jan 1 00:00:00 GMT 1970") == 0){
+ if(m->unixdate){
+ s_free(m->unixdate);
+ m->unixdate = nil;
+ }
+ // 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.
+ if(cistrncmp(m->header, "received:", 9)==0){
+ if((q = strchr(m->header, ';')) != nil){
+ p = q;
+ while((p = strchr(p, '\n')) != nil){
+ if(p[1] != ' ' && p[1] != '\t' && p[1] != '\n')
+ break;
+ p++;
+ }
+ if(p){
+ *p = '\0';
+ m->unixdate = date822tounix(q+1);
+ *p = '\n';
+ }
+ }
+ }
+
+ // fall back on the rfc822 date
+ if(m->unixdate==nil && m->date822)
+ m->unixdate = date822tounix(s_to_c(m->date822));
+ }
+
+ if(m->unixheader != nil)
+ s_free(m->unixheader);
+
+ // 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.
+ if(!addfrom && !m->unixfrom){
+ m->unixheader = nil;
+ return;
+ }
+
+ m->unixheader = s_copy("From ");
+ if(m->unixfrom && strcmp(s_to_c(m->unixfrom), "???") != 0)
+ s_append(m->unixheader, s_to_c(m->unixfrom));
+ else if(m->from822)
+ s_append(m->unixheader, s_to_c(m->from822));
+ else
+ s_append(m->unixheader, "???");
+
+ s_append(m->unixheader, " ");
+ if(m->unixdate)
+ s_append(m->unixheader, s_to_c(m->unixdate));
+ else
+ s_append(m->unixheader, "Thu Jan 1 00:00:00 GMT 1970");
+
+ s_append(m->unixheader, "\n");
+}
+
+String*
+promote(String **sp)
+{
+ String *s;
+
+ if(*sp != nil)
+ s = s_clone(*sp);
+ else
+ s = nil;
+ return s;
+}
+
+void
+parsebody(Message *m, Mailbox *mb)
+{
+ Message *nm;
+
+ // recurse
+ if(strncmp(s_to_c(m->type), "multipart/", 10) == 0){
+ parseattachments(m, mb);
+ } else if(strcmp(s_to_c(m->type), "message/rfc822") == 0){
+ decode(m);
+ parseattachments(m, mb);
+ nm = m->part;
+
+ // promote headers
+ if(m->replyto822 == nil && m->from822 == nil && m->sender822 == nil){
+ m->from822 = promote(&nm->from822);
+ m->to822 = promote(&nm->to822);
+ m->date822 = promote(&nm->date822);
+ m->sender822 = promote(&nm->sender822);
+ m->replyto822 = promote(&nm->replyto822);
+ m->subject822 = promote(&nm->subject822);
+ m->unixdate = promote(&nm->unixdate);
+ }
+ }
+}
+
+void
+parse(Message *m, int justmime, Mailbox *mb, int addfrom)
+{
+ parseheaders(m, justmime, mb, addfrom);
+ parsebody(m, mb);
+}
+
+static void
+parseattachments(Message *m, Mailbox *mb)
+{
+ Message *nm, **l;
+ char *p, *x;
+
+ // if there's a boundary, recurse...
+ if(m->boundary != nil){
+ p = m->body;
+ nm = nil;
+ l = &m->part;
+ for(;;){
+ x = strstr(p, s_to_c(m->boundary));
+
+ /* no boundary, we're done */
+ if(x == nil){
+ if(nm != nil)
+ nm->rbend = nm->bend = nm->end = m->bend;
+ 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;
+ x += strlen(s_to_c(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;
+ nm = newmessage(m);
+ nm->start = nm->header = nm->body = nm->rbody = ++p;
+ nm->mheader = nm->header;
+ *l = nm;
+ l = &nm->next;
+ }
+ for(nm = m->part; nm != nil; nm = nm->next)
+ parse(nm, 1, mb, 0);
+ return;
+ }
+
+ // if we've got an rfc822 message, recurse...
+ if(strcmp(s_to_c(m->type), "message/rfc822") == 0){
+ nm = newmessage(m);
+ m->part = nm;
+ nm->start = nm->header = nm->body = nm->rbody = m->body;
+ nm->end = nm->bend = nm->rbend = m->bend;
+ parse(nm, 0, mb, 0);
+ }
+}
+
+/*
+ * pick up a header line
+ */
+static int
+headerline(char **pp, String *hl)
+{
+ char *p, *x;
+
+ s_reset(hl);
+ p = *pp;
+ x = strpbrk(p, ":\n");
+ if(x == nil || *x == '\n')
+ return 0;
+ for(;;){
+ x = strchr(p, '\n');
+ if(x == nil)
+ x = p + strlen(p);
+ s_nappend(hl, p, x-p);
+ p = x;
+ if(*p != '\n' || *++p != ' ' && *p != '\t')
+ break;
+ while(*p == ' ' || *p == '\t')
+ p++;
+ s_putc(hl, ' ');
+ }
+ *pp = p;
+ return 1;
+}
+
+static String*
+addr822(char *p)
+{
+ String *s, *list;
+ int incomment, addrdone, inanticomment, quoted;
+ int n;
+ int c;
+
+ list = s_new();
+ s = s_new();
+ quoted = incomment = addrdone = inanticomment = 0;
+ n = 0;
+ for(; *p; p++){
+ c = *p;
+
+ // whitespace is ignored
+ if(!quoted && isspace(c) || c == '\r')
+ continue;
+
+ // strings are always treated as atoms
+ if(!quoted && c == '"'){
+ if(!addrdone && !incomment)
+ s_putc(s, c);
+ for(p++; *p; p++){
+ if(!addrdone && !incomment)
+ s_putc(s, *p);
+ if(!quoted && *p == '"')
+ break;
+ if(*p == '\\')
+ quoted = 1;
+ else
+ quoted = 0;
+ }
+ if(*p == 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){
+ inanticomment = 1;
+ s = s_reset(s);
+ continue;
+ }
+ if(!quoted && c == '>' && inanticomment){
+ addrdone = 1;
+ inanticomment = 0;
+ continue;
+ }
+
+ // commas separate addresses
+ if(!quoted && c == ',' && !inanticomment){
+ s_terminate(s);
+ addrdone = 0;
+ if(n++ != 0)
+ s_append(list, " ");
+ s_append(list, s_to_c(s));
+ s = s_reset(s);
+ continue;
+ }
+
+ // what's left is part of the address
+ s_putc(s, c);
+
+ // quoted characters are recognized only as characters
+ if(c == '\\')
+ quoted = 1;
+ else
+ quoted = 0;
+
+ }
+
+ if(*s_to_c(s) != 0){
+ s_terminate(s);
+ if(n++ != 0)
+ s_append(list, " ");
+ s_append(list, s_to_c(s));
+ }
+ s_free(s);
+
+ if(n == 0){
+ s_free(list);
+ return nil;
+ }
+ return list;
+}
+
+static void
+to822(Message *m, Header *h, char *p)
+{
+ p += strlen(h->type);
+ s_free(m->to822);
+ m->to822 = addr822(p);
+}
+
+static void
+cc822(Message *m, Header *h, char *p)
+{
+ p += strlen(h->type);
+ s_free(m->cc822);
+ m->cc822 = addr822(p);
+}
+
+static void
+bcc822(Message *m, Header *h, char *p)
+{
+ p += strlen(h->type);
+ s_free(m->bcc822);
+ m->bcc822 = addr822(p);
+}
+
+static void
+from822(Message *m, Header *h, char *p)
+{
+ p += strlen(h->type);
+ s_free(m->from822);
+ m->from822 = addr822(p);
+}
+
+static void
+sender822(Message *m, Header *h, char *p)
+{
+ p += strlen(h->type);
+ s_free(m->sender822);
+ m->sender822 = addr822(p);
+}
+
+static void
+replyto822(Message *m, Header *h, char *p)
+{
+ p += strlen(h->type);
+ s_free(m->replyto822);
+ m->replyto822 = addr822(p);
+}
+
+static void
+mimeversion(Message *m, Header *h, char *p)
+{
+ p += strlen(h->type);
+ s_free(m->mimeversion);
+ m->mimeversion = addr822(p);
+}
+
+static void
+killtrailingwhite(char *p)
+{
+ char *e;
+
+ e = p + strlen(p) - 1;
+ while(e > p && isspace(*e))
+ *e-- = 0;
+}
+
+static void
+date822(Message *m, Header *h, char *p)
+{
+ p += strlen(h->type);
+ p = skipwhite(p);
+ s_free(m->date822);
+ m->date822 = s_copy(p);
+ p = s_to_c(m->date822);
+ killtrailingwhite(p);
+}
+
+static void
+subject822(Message *m, Header *h, char *p)
+{
+ p += strlen(h->type);
+ p = skipwhite(p);
+ s_free(m->subject822);
+ m->subject822 = s_copy(p);
+ p = s_to_c(m->subject822);
+ killtrailingwhite(p);
+}
+
+static void
+inreplyto822(Message *m, Header *h, char *p)
+{
+ p += strlen(h->type);
+ p = skipwhite(p);
+ s_free(m->inreplyto822);
+ m->inreplyto822 = s_copy(p);
+ p = s_to_c(m->inreplyto822);
+ killtrailingwhite(p);
+}
+
+static void
+messageid822(Message *m, Header *h, char *p)
+{
+ p += strlen(h->type);
+ p = skipwhite(p);
+ s_free(m->messageid822);
+ m->messageid822 = s_copy(p);
+ p = s_to_c(m->messageid822);
+ killtrailingwhite(p);
+}
+
+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 void
+ctype(Message *m, Header *h, char *p)
+{
+ String *s;
+
+ p += h->len;
+ p = skipwhite(p);
+
+ p = getstring(p, m->type, 1);
+
+ while(*p){
+ if(isattribute(&p, "boundary")){
+ s = s_new();
+ p = getstring(p, s, 0);
+ m->boundary = s_reset(m->boundary);
+ s_append(m->boundary, "--");
+ s_append(m->boundary, s_to_c(s));
+ s_free(s);
+ } 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, s_reset(m->charset), 0);
+ }
+
+ p = skiptosemi(p);
+ }
+}
+
+static void
+cencoding(Message *m, Header *h, char *p)
+{
+ p += h->len;
+ p = skipwhite(p);
+ if(cistrncmp(p, "base64", 6) == 0)
+ m->encoding = Ebase64;
+ else if(cistrncmp(p, "quoted-printable", 16) == 0)
+ m->encoding = Equoted;
+}
+
+static void
+cdisposition(Message *m, Header *h, char *p)
+{
+ p += h->len;
+ p = skipwhite(p);
+ while(*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);
+ }
+ p = skiptosemi(p);
+ }
+
+}
+
+ulong msgallocd, msgfreed;
+
+Message*
+newmessage(Message *parent)
+{
+ /* static int id; jpc */
+ Message *m;
+
+ msgallocd++;
+
+ m = emalloc(sizeof(*m));
+ memset(m, 0, sizeof(*m));
+ m->disposition = Dnone;
+ m->type = s_copy("text/plain");
+ m->charset = s_copy("iso-8859-1");
+ m->id = newid();
+ if(parent)
+ sprint(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;
+ int i;
+
+ 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);
+ for(i = 0; i < Qmax; i++)
+ hfree(PATH(m->id, Qdir), dirtab[i]);
+ }
+
+ /* recurse through sub-parts */
+ while(m->part)
+ delmessage(mb, m->part);
+
+ /* free memory */
+ if(m->mallocd)
+ free(m->start);
+ if(m->hallocd)
+ free(m->header);
+ if(m->ballocd)
+ free(m->body);
+ s_free(m->unixfrom);
+ s_free(m->unixdate);
+ s_free(m->unixheader);
+ s_free(m->from822);
+ s_free(m->sender822);
+ s_free(m->to822);
+ s_free(m->bcc822);
+ s_free(m->cc822);
+ s_free(m->replyto822);
+ s_free(m->date822);
+ s_free(m->inreplyto822);
+ s_free(m->subject822);
+ s_free(m->messageid822);
+ s_free(m->addrs);
+ s_free(m->mimeversion);
+ s_free(m->sdigest);
+ s_free(m->boundary);
+ s_free(m->type);
+ s_free(m->charset);
+ s_free(m->filename);
+
+ free(m);
+}
+
+// mark messages (identified by path) for deletion
+void
+delmessages(int ac, char **av)
+{
+ Mailbox *mb;
+ Message *m;
+ int i, needwrite;
+
+ qlock(&mbllock);
+ for(mb = mbl; mb != nil; mb = mb->next)
+ if(strcmp(av[0], mb->name) == 0){
+ qlock(&mb->ql);
+ break;
+ }
+ qunlock(&mbllock);
+ if(mb == nil)
+ return;
+
+ 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 = 1;
+ logmsg("deleting", m);
+ }
+ break;
+ }
+ }
+ if(needwrite)
+ syncmbox(mb, 1);
+ qunlock(&mb->ql);
+}
+
+/*
+ * the following are called with the mailbox qlocked
+ */
+void
+msgincref(Message *m)
+{
+ m->refs++;
+}
+void
+msgdecref(Mailbox *mb, Message *m)
+{
+ m->refs--;
+ if(m->refs == 0 && m->deleted)
+ syncmbox(mb, 1);
+}
+
+/*
+ * the following are called with mbllock'd
+ */
+void
+mboxincref(Mailbox *mb)
+{
+ assert(mb->refs > 0);
+ mb->refs++;
+}
+void
+mboxdecref(Mailbox *mb)
+{
+ assert(mb->refs > 0);
+ qlock(&mb->ql);
+ mb->refs--;
+ if(mb->refs == 0){
+ delmessage(mb, mb->root);
+ if(mb->ctl)
+ hfree(PATH(mb->id, Qmbox), "ctl");
+ if(mb->close)
+ (*mb->close)(mb);
+ free(mb);
+ } else
+ qunlock(&mb->ql);
+}
+
+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;
+}
+
+static char*
+skipwhite(char *p)
+{
+ while(isspace(*p))
+ p++;
+ return p;
+}
+
+static char*
+skiptosemi(char *p)
+{
+ while(*p && *p != ';')
+ p++;
+ while(*p == ';' || isspace(*p))
+ p++;
+ return p;
+}
+
+static char*
+getstring(char *p, String *s, int dolower)
+{
+ s = s_reset(s);
+ p = skipwhite(p);
+ if(*p == '"'){
+ p++;
+ for(;*p && *p != '"'; p++)
+ if(dolower)
+ s_putc(s, tolower(*p));
+ else
+ s_putc(s, *p);
+ if(*p == '"')
+ p++;
+ s_terminate(s);
+
+ return p;
+ }
+
+ for(; *p && !isspace(*p) && *p != ';'; p++)
+ if(dolower)
+ s_putc(s, tolower(*p));
+ else
+ s_putc(s, *p);
+ s_terminate(s);
+
+ return p;
+}
+
+static void
+setfilename(Message *m, char *p)
+{
+ m->filename = s_reset(m->filename);
+ getstring(p, m->filename, 0);
+ for(p = s_to_c(m->filename); *p; p++)
+ if(*p == ' ' || *p == '\t' || *p == ';')
+ *p = '_';
+}
+
+//
+// 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(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);
+ if(m->ballocd)
+ free(m->body);
+ m->body = x;
+ m->bend = x + len;
+ m->ballocd = 1;
+ break;
+ default:
+ break;
+ }
+ m->decoded = 1;
+}
+
+// convert latin1 to utf
+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;
+ if(m->part != nil)
+ return;
+ if(cistrncmp(s_to_c(m->type), "text", 4) != 0)
+ return;
+
+ if(cistrcmp(s_to_c(m->charset), "us-ascii") == 0 ||
+ cistrcmp(s_to_c(m->charset), "iso-8859-1") == 0){
+ len = is8bit(m);
+ if(len > 0){
+ len = 2*len + m->bend - m->body + 1;
+ x = emalloc(len);
+ len = latin1toutf(x, m->body, m->bend);
+ if(m->ballocd)
+ free(m->body);
+ m->body = x;
+ m->bend = x + len;
+ m->ballocd = 1;
+ }
+ } else if(cistrcmp(s_to_c(m->charset), "iso-8859-2") == 0){
+ len = xtoutf("8859-2", &x, m->body, m->bend);
+ if(len != 0){
+ if(m->ballocd)
+ free(m->body);
+ m->body = x;
+ m->bend = x + len;
+ m->ballocd = 1;
+ }
+ } else if(cistrcmp(s_to_c(m->charset), "iso-8859-15") == 0){
+ len = xtoutf("8859-15", &x, m->body, m->bend);
+ if(len != 0){
+ if(m->ballocd)
+ free(m->body);
+ m->body = x;
+ m->bend = x + len;
+ m->ballocd = 1;
+ }
+ } else if(cistrcmp(s_to_c(m->charset), "big5") == 0){
+ len = xtoutf("big5", &x, m->body, m->bend);
+ if(len != 0){
+ if(m->ballocd)
+ free(m->body);
+ m->body = x;
+ m->bend = x + len;
+ m->ballocd = 1;
+ }
+ } else if(cistrcmp(s_to_c(m->charset), "iso-2022-jp") == 0){
+ len = xtoutf("jis", &x, m->body, m->bend);
+ if(len != 0){
+ if(m->ballocd)
+ free(m->body);
+ m->body = x;
+ m->bend = x + len;
+ m->ballocd = 1;
+ }
+ } else if(cistrcmp(s_to_c(m->charset), "windows-1257") == 0
+ || cistrcmp(s_to_c(m->charset), "windows-1252") == 0){
+ len = is8bit(m);
+ if(len > 0){
+ len = 2*len + m->bend - m->body + 1;
+ x = emalloc(len);
+ len = windows1257toutf(x, m->body, m->bend);
+ if(m->ballocd)
+ free(m->body);
+ m->body = x;
+ m->bend = x + len;
+ m->ballocd = 1;
+ }
+ } else if(cistrcmp(s_to_c(m->charset), "windows-1251") == 0){
+ len = xtoutf("cp1251", &x, m->body, m->bend);
+ if(len != 0){
+ if(m->ballocd)
+ free(m->body);
+ m->body = x;
+ m->bend = x + len;
+ m->ballocd = 1;
+ }
+ } else if(cistrcmp(s_to_c(m->charset), "koi8-r") == 0){
+ len = xtoutf("koi8", &x, m->body, m->bend);
+ if(len != 0){
+ if(m->ballocd)
+ free(m->body);
+ m->body = x;
+ m->bend = x + len;
+ m->ballocd = 1;
+ }
+ }
+
+ m->converted = 1;
+}
+
+enum
+{
+ Self= 1,
+ Hex= 2,
+};
+uchar tableqp[256];
+
+static void
+initquoted(void)
+{
+ int c;
+
+ memset(tableqp, 0, 256);
+ for(c = ' '; c <= '<'; c++)
+ tableqp[c] = Self;
+ for(c = '>'; c <= '~'; c++)
+ tableqp[c] = Self;
+ tableqp['\t'] = Self;
+ tableqp['='] = Hex;
+}
+
+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 0;
+}
+
+static char*
+decquotedline(char *out, char *in, char *e)
+{
+ 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(tableqp[c]){
+ case Self:
+ *out++ = c;
+ break;
+ case Hex:
+ c = hex2int(*in++)<<4;
+ c |= hex2int(*in++);
+ *out++ = c;
+ break;
+ }
+ }
+ if(!soft)
+ *out++ = '\n';
+ *out = 0;
+
+ return out;
+}
+
+int
+decquoted(char *out, char *in, char *e)
+{
+ char *p, *nl;
+
+ if(tableqp[' '] == 0)
+ initquoted();
+
+ p = out;
+ while((nl = strchr(in, '\n')) != nil && nl < e){
+ p = decquotedline(p, in, nl);
+ in = nl + 1;
+ }
+ if(in < e)
+ p = decquotedline(p, in, e-1);
+
+ // make sure we end with a new line
+ if(*(p-1) != '\n'){
+ *p++ = '\n';
+ *p = 0;
+ }
+
+ return p - out;
+}
+
+#if 0 /* jpc */
+static char*
+lowercase(char *p)
+{
+ char *op;
+ int c;
+
+ for(op = p; c = *p; p++)
+ if(isupper(c))
+ *p = tolower(c);
+ return op;
+}
+#endif
+
+/*
+ * return number of 8 bit characters
+ */
+static int
+is8bit(Message *m)
+{
+ int count = 0;
+ char *p;
+
+ for(p = m->body; p < m->bend; p++)
+ if(*p & 0x80)
+ count++;
+ return count;
+}
+
+// translate latin1 directly since it fits neatly in utf
+int
+latin1toutf(char *out, char *in, char *e)
+{
+ Rune r;
+ char *p;
+
+ p = out;
+ for(; in < e; in++){
+ r = (*in) & 0xff;
+ p += runetochar(p, &r);
+ }
+ *p = 0;
+ return p - out;
+}
+
+// translate any thing else using the tcs program
+int
+xtoutf(char *charset, char **out, char *in, char *e)
+{
+ char *av[4];
+ int totcs[2];
+ int fromtcs[2];
+ int n, len, sofar;
+ char *p;
+
+ 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)
+ return 0;
+ if(pipe(fromtcs) < 0){
+ close(totcs[0]); close(totcs[1]);
+ return 0;
+ }
+ switch(rfork(RFPROC|RFFDG|RFNOWAIT)){
+ case -1:
+ close(fromtcs[0]); close(fromtcs[1]);
+ close(totcs[0]); close(totcs[1]);
+ return 0;
+ 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);
+ //jpc exec("/bin/tcs", av);
+ exec(unsharp("#9/bin/tcs"), av);
+ /* _exits(0); */
+ threadexits(nil);
+ default:
+ close(fromtcs[1]); close(totcs[0]);
+ switch(rfork(RFPROC|RFFDG|RFNOWAIT)){
+ case -1:
+ close(fromtcs[0]); close(totcs[1]);
+ return 0;
+ 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(0); */
+ threadexits(nil);
+ 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;
+ *out = p = realloc(p, len+1);
+ if(p == nil)
+ return 0;
+ }
+ }
+ close(fromtcs[0]);
+ break;
+ }
+ break;
+ }
+ return sofar;
+}
+
+enum {
+ Winstart= 0x7f,
+ Winend= 0x9f,
+};
+
+Rune winchars[] = {
+ L'•',
+ L'•', L'•', L'‚', L'ƒ', L'„', L'…', L'†', L'‡',
+ L'ˆ', L'‰', L'Š', L'‹', L'Œ', L'•', L'•', L'•',
+ L'•', L'‘', L'’', L'“', L'”', L'•', L'–', L'—',
+ L'˜', L'™', L'š', L'›', L'œ', L'•', L'•', L'Ÿ',
+};
+
+int
+windows1257toutf(char *out, char *in, char *e)
+{
+ Rune r;
+ char *p;
+
+ p = out;
+ for(; in < e; in++){
+ r = (*in) & 0xff;
+ if(r >= 0x7f && r <= 0x9f)
+ r = winchars[r-0x7f];
+ p += runetochar(p, &r);
+ }
+ *p = 0;
+ return p - out;
+}
+
+void *
+emalloc(ulong n)
+{
+ void *p;
+
+ p = mallocz(n, 1);
+ if(!p){
+ fprint(2, "%s: out of memory alloc %lud\n", argv0, n);
+ threadexits("out of memory");
+ }
+ setmalloctag(p, getcallerpc(&n));
+ return p;
+}
+
+void *
+erealloc(void *p, ulong n)
+{
+ if(n == 0)
+ n = 1;
+ p = realloc(p, n);
+ if(!p){
+ fprint(2, "%s: out of memory realloc %lud\n", argv0, n);
+ threadexits("out of memory");
+ }
+ setrealloctag(p, getcallerpc(&p));
+ return p;
+}
+
+void
+mailplumb(Mailbox *mb, Message *m, int delete)
+{
+ Plumbmsg p;
+ Plumbattr a[7];
+ char buf[256];
+ int ai;
+ char lenstr[10], *from, *subject, *date;
+ static int fd = -1;
+
+ if(m->subject822 == nil)
+ subject = "";
+ else
+ subject = s_to_c(m->subject822);
+
+ if(m->from822 != nil)
+ from = s_to_c(m->from822);
+ else if(m->unixfrom != nil)
+ from = s_to_c(m->unixfrom);
+ else
+ from = "";
+
+ if(m->unixdate != nil)
+ date = s_to_c(m->unixdate);
+ else
+ date = "";
+
+ sprint(lenstr, "%ld", m->end-m->start);
+
+ if(biffing && !delete)
+ print("[ %s / %s / %s ]\n", from, subject, lenstr);
+
+ if(!plumbing)
+ return;
+
+ if(fd < 0)
+ fd = plumbopen("send", OWRITE);
+ if(fd < 0)
+ return;
+
+ 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 = lenstr;
+ a[ai-1].next = &a[ai];
+
+ a[++ai].name = "mailtype";
+ a[ai].value = delete?"delete":"new";
+ a[ai-1].next = &a[ai];
+
+ a[++ai].name = "date";
+ a[ai].value = date;
+ a[ai-1].next = &a[ai];
+
+ if(m->sdigest){
+ a[++ai].name = "digest";
+ a[ai].value = s_to_c(m->sdigest);
+ 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;
+
+ plumbsend(fd, &p);
+}
+
+//
+// count the number of lines in the body (for imap4)
+//
+void
+countlines(Message *m)
+{
+ int i;
+ char *p;
+
+ i = 0;
+ for(p = strchr(m->rbody, '\n'); p != nil && p < m->rbend; p = strchr(p+1, '\n'))
+ i++;
+ sprint(m->lines, "%d", i);
+}
+
+char *LOG = "fs";
+
+void
+logmsg(char *s, Message *m)
+{
+ int pid;
+
+ if(!logging)
+ return;
+ pid = getpid();
+ if(m == nil)
+ syslog(0, LOG, "%s.%d: %s", user, pid, s);
+ else
+ syslog(0, LOG, "%s.%d: %s msg from %s digest %s",
+ user, pid, s,
+ m->from822 ? s_to_c(m->from822) : "?",
+ s_to_c(m->sdigest));
+}
+
+/*
+ * squeeze nulls out of the body
+ */
+static void
+nullsqueeze(Message *m)
+{
+ char *p, *q;
+
+ q = memchr(m->body, 0, m->end-m->body);
+ if(q == nil)
+ return;
+
+ for(p = m->body; q < m->end; q++){
+ if(*q == 0)
+ continue;
+ *p++ = *q;
+ }
+ m->bend = m->rbend = m->end = p;
+}
+
+
+//
+// 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.
+//
+
+extern int strtotm(char*, Tm*);
+String*
+date822tounix(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 s_copy(p);
+}
+