|  | #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); | 
|  | } | 
|  |  |