xref: /plan9/sys/src/cmd/upas/fs/mbox.c (revision 34e1172381a9ccb1c38a242015efe880074f0e7d)
17dd7cddfSDavid du Colombier #include "common.h"
27dd7cddfSDavid du Colombier #include <ctype.h>
37dd7cddfSDavid du Colombier #include <plumb.h>
47dd7cddfSDavid du Colombier #include <libsec.h>
57dd7cddfSDavid du Colombier #include "dat.h"
67dd7cddfSDavid du Colombier 
77dd7cddfSDavid du Colombier typedef struct Header Header;
87dd7cddfSDavid du Colombier 
97dd7cddfSDavid du Colombier struct Header {
107dd7cddfSDavid du Colombier 	char *type;
117dd7cddfSDavid du Colombier 	void (*f)(Message*, Header*, char*);
127dd7cddfSDavid du Colombier 	int len;
137dd7cddfSDavid du Colombier };
147dd7cddfSDavid du Colombier 
157dd7cddfSDavid du Colombier /* headers */
167dd7cddfSDavid du Colombier static	void	ctype(Message*, Header*, char*);
177dd7cddfSDavid du Colombier static	void	cencoding(Message*, Header*, char*);
187dd7cddfSDavid du Colombier static	void	cdisposition(Message*, Header*, char*);
197dd7cddfSDavid du Colombier static	void	date822(Message*, Header*, char*);
207dd7cddfSDavid du Colombier static	void	from822(Message*, Header*, char*);
217dd7cddfSDavid du Colombier static	void	to822(Message*, Header*, char*);
227dd7cddfSDavid du Colombier static	void	sender822(Message*, Header*, char*);
237dd7cddfSDavid du Colombier static	void	replyto822(Message*, Header*, char*);
247dd7cddfSDavid du Colombier static	void	subject822(Message*, Header*, char*);
257dd7cddfSDavid du Colombier static	void	inreplyto822(Message*, Header*, char*);
267dd7cddfSDavid du Colombier static	void	cc822(Message*, Header*, char*);
277dd7cddfSDavid du Colombier static	void	bcc822(Message*, Header*, char*);
287dd7cddfSDavid du Colombier static	void	messageid822(Message*, Header*, char*);
297dd7cddfSDavid du Colombier static	void	mimeversion(Message*, Header*, char*);
303ff48bf5SDavid du Colombier static	void	nullsqueeze(Message*);
317dd7cddfSDavid du Colombier enum
327dd7cddfSDavid du Colombier {
337dd7cddfSDavid du Colombier 	Mhead=	11,	/* offset of first mime header */
347dd7cddfSDavid du Colombier };
357dd7cddfSDavid du Colombier 
367dd7cddfSDavid du Colombier Header head[] =
377dd7cddfSDavid du Colombier {
387dd7cddfSDavid du Colombier 	{ "date:", date822, },
397dd7cddfSDavid du Colombier 	{ "from:", from822, },
407dd7cddfSDavid du Colombier 	{ "to:", to822, },
417dd7cddfSDavid du Colombier 	{ "sender:", sender822, },
427dd7cddfSDavid du Colombier 	{ "reply-to:", replyto822, },
437dd7cddfSDavid du Colombier 	{ "subject:", subject822, },
447dd7cddfSDavid du Colombier 	{ "cc:", cc822, },
457dd7cddfSDavid du Colombier 	{ "bcc:", bcc822, },
467dd7cddfSDavid du Colombier 	{ "in-reply-to:", inreplyto822, },
477dd7cddfSDavid du Colombier 	{ "mime-version:", mimeversion, },
487dd7cddfSDavid du Colombier 	{ "message-id:", messageid822, },
497dd7cddfSDavid du Colombier 
507dd7cddfSDavid du Colombier [Mhead]	{ "content-type:", ctype, },
517dd7cddfSDavid du Colombier 	{ "content-transfer-encoding:", cencoding, },
527dd7cddfSDavid du Colombier 	{ "content-disposition:", cdisposition, },
537dd7cddfSDavid du Colombier 	{ 0, },
547dd7cddfSDavid du Colombier };
557dd7cddfSDavid du Colombier 
567dd7cddfSDavid du Colombier static	void	fatal(char *fmt, ...);
577dd7cddfSDavid du Colombier static	void	initquoted(void);
587dd7cddfSDavid du Colombier static	void	startheader(Message*);
597dd7cddfSDavid du Colombier static	void	startbody(Message*);
607dd7cddfSDavid du Colombier static	char*	skipwhite(char*);
617dd7cddfSDavid du Colombier static	char*	skiptosemi(char*);
627dd7cddfSDavid du Colombier static	char*	getstring(char*, String*, int);
637dd7cddfSDavid du Colombier static	void	setfilename(Message*, char*);
647dd7cddfSDavid du Colombier static	char*	lowercase(char*);
657dd7cddfSDavid du Colombier static	int	is8bit(Message*);
667dd7cddfSDavid du Colombier static	int	headerline(char**, String*);
677dd7cddfSDavid du Colombier static	void	initheaders(void);
687dd7cddfSDavid du Colombier static void	parseattachments(Message*, Mailbox*);
697dd7cddfSDavid du Colombier 
707dd7cddfSDavid du Colombier int		debug;
7180ee5cbfSDavid du Colombier 
7280ee5cbfSDavid du Colombier char *Enotme = "path not served by this file server";
737dd7cddfSDavid du Colombier 
747dd7cddfSDavid du Colombier enum
757dd7cddfSDavid du Colombier {
767dd7cddfSDavid du Colombier 	Chunksize = 1024,
777dd7cddfSDavid du Colombier };
787dd7cddfSDavid du Colombier 
7980ee5cbfSDavid du Colombier Mailboxinit *boxinit[] = {
8080ee5cbfSDavid du Colombier 	imap4mbox,
8180ee5cbfSDavid du Colombier 	pop3mbox,
8280ee5cbfSDavid du Colombier 	plan9mbox,
8380ee5cbfSDavid du Colombier };
8480ee5cbfSDavid du Colombier 
8580ee5cbfSDavid du Colombier char*
8680ee5cbfSDavid du Colombier syncmbox(Mailbox *mb, int doplumb)
8780ee5cbfSDavid du Colombier {
8880ee5cbfSDavid du Colombier 	return (*mb->sync)(mb, doplumb);
8980ee5cbfSDavid du Colombier }
9080ee5cbfSDavid du Colombier 
917dd7cddfSDavid du Colombier /* create a new mailbox */
927dd7cddfSDavid du Colombier char*
937dd7cddfSDavid du Colombier newmbox(char *path, char *name, int std)
947dd7cddfSDavid du Colombier {
957dd7cddfSDavid du Colombier 	Mailbox *mb, **l;
967dd7cddfSDavid du Colombier 	char *p, *rv;
9780ee5cbfSDavid du Colombier 	int i;
9880ee5cbfSDavid du Colombier 
9980ee5cbfSDavid du Colombier 	initheaders();
10080ee5cbfSDavid du Colombier 
1017dd7cddfSDavid du Colombier 	mb = emalloc(sizeof(*mb));
1027dd7cddfSDavid du Colombier 	strncpy(mb->path, path, sizeof(mb->path)-1);
1037dd7cddfSDavid du Colombier 	if(name == nil){
1047dd7cddfSDavid du Colombier 		p = strrchr(path, '/');
1057dd7cddfSDavid du Colombier 		if(p == nil)
1067dd7cddfSDavid du Colombier 			p = path;
1077dd7cddfSDavid du Colombier 		else
1087dd7cddfSDavid du Colombier 			p++;
1097dd7cddfSDavid du Colombier 		if(*p == 0){
1107dd7cddfSDavid du Colombier 			free(mb);
1117dd7cddfSDavid du Colombier 			return "bad mbox name";
1127dd7cddfSDavid du Colombier 		}
1137dd7cddfSDavid du Colombier 		strncpy(mb->name, p, sizeof(mb->name)-1);
1147dd7cddfSDavid du Colombier 	} else {
1157dd7cddfSDavid du Colombier 		strncpy(mb->name, name, sizeof(mb->name)-1);
1167dd7cddfSDavid du Colombier 	}
1177dd7cddfSDavid du Colombier 
11880ee5cbfSDavid du Colombier 	rv = nil;
11980ee5cbfSDavid du Colombier 	// check for a mailbox type
12080ee5cbfSDavid du Colombier 	for(i=0; i<nelem(boxinit); i++)
12180ee5cbfSDavid du Colombier 		if((rv = (*boxinit[i])(mb, path)) != Enotme)
12280ee5cbfSDavid du Colombier 			break;
12380ee5cbfSDavid du Colombier 	if(i == nelem(boxinit)){
12480ee5cbfSDavid du Colombier 		free(mb);
12580ee5cbfSDavid du Colombier 		return "bad path";
12680ee5cbfSDavid du Colombier 	}
12780ee5cbfSDavid du Colombier 
12880ee5cbfSDavid du Colombier 	// on error, give up
1295d459b5aSDavid du Colombier 	if(rv){
1305d459b5aSDavid du Colombier 		free(mb);
13180ee5cbfSDavid du Colombier 		return rv;
1325d459b5aSDavid du Colombier 	}
13380ee5cbfSDavid du Colombier 
1347dd7cddfSDavid du Colombier 	// make sure name isn't taken
1357dd7cddfSDavid du Colombier 	qlock(&mbllock);
13659cc4ca5SDavid du Colombier 	for(l = &mbl; *l != nil; l = &(*l)->next){
1377dd7cddfSDavid du Colombier 		if(strcmp((*l)->name, mb->name) == 0){
13859cc4ca5SDavid du Colombier 			if(strcmp(path, (*l)->path) == 0)
1397dd7cddfSDavid du Colombier 				rv = nil;
1407dd7cddfSDavid du Colombier 			else
1417dd7cddfSDavid du Colombier 				rv = "mbox name in use";
14280ee5cbfSDavid du Colombier 			if(mb->close)
14380ee5cbfSDavid du Colombier 				(*mb->close)(mb);
1447dd7cddfSDavid du Colombier 			free(mb);
1457dd7cddfSDavid du Colombier 			qunlock(&mbllock);
1467dd7cddfSDavid du Colombier 			return rv;
1477dd7cddfSDavid du Colombier 		}
14859cc4ca5SDavid du Colombier 	}
14959cc4ca5SDavid du Colombier 
150ed250ae1SDavid du Colombier 	// always try locking
151ed250ae1SDavid du Colombier 	mb->dolock = 1;
15259cc4ca5SDavid du Colombier 
1537dd7cddfSDavid du Colombier 	mb->refs = 1;
1547dd7cddfSDavid du Colombier 	mb->next = nil;
1557dd7cddfSDavid du Colombier 	mb->id = newid();
1567dd7cddfSDavid du Colombier 	mb->root = newmessage(nil);
1577dd7cddfSDavid du Colombier 	mb->std = std;
1587dd7cddfSDavid du Colombier 	*l = mb;
1597dd7cddfSDavid du Colombier 	qunlock(&mbllock);
1607dd7cddfSDavid du Colombier 
1617dd7cddfSDavid du Colombier 	qlock(mb);
16280ee5cbfSDavid du Colombier 	if(mb->ctl){
1639a747e4fSDavid du Colombier 		henter(PATH(mb->id, Qmbox), "ctl",
1649a747e4fSDavid du Colombier 			(Qid){PATH(mb->id, Qmboxctl), 0, QTFILE}, nil, mb);
16580ee5cbfSDavid du Colombier 	}
1667dd7cddfSDavid du Colombier 	rv = syncmbox(mb, 0);
1677dd7cddfSDavid du Colombier 	qunlock(mb);
1687dd7cddfSDavid du Colombier 
1697dd7cddfSDavid du Colombier 	return rv;
1707dd7cddfSDavid du Colombier }
1717dd7cddfSDavid du Colombier 
1727dd7cddfSDavid du Colombier // close the named mailbox
1737dd7cddfSDavid du Colombier void
1747dd7cddfSDavid du Colombier freembox(char *name)
1757dd7cddfSDavid du Colombier {
1765d459b5aSDavid du Colombier 	Mailbox **l, *mb;
1777dd7cddfSDavid du Colombier 
1787dd7cddfSDavid du Colombier 	qlock(&mbllock);
1795d459b5aSDavid du Colombier 	for(l=&mbl; *l != nil; l=&(*l)->next){
1805d459b5aSDavid du Colombier 		if(strcmp(name, (*l)->name) == 0){
1815d459b5aSDavid du Colombier 			mb = *l;
1825d459b5aSDavid du Colombier 			*l = mb->next;
1837dd7cddfSDavid du Colombier 			mboxdecref(mb);
1847dd7cddfSDavid du Colombier 			break;
1857dd7cddfSDavid du Colombier 		}
1865d459b5aSDavid du Colombier 	}
1879a747e4fSDavid du Colombier 	hfree(PATH(0, Qtop), name);
1887dd7cddfSDavid du Colombier 	qunlock(&mbllock);
1897dd7cddfSDavid du Colombier }
1907dd7cddfSDavid du Colombier 
1917dd7cddfSDavid du Colombier static void
1927dd7cddfSDavid du Colombier initheaders(void)
1937dd7cddfSDavid du Colombier {
1947dd7cddfSDavid du Colombier 	Header *h;
1957dd7cddfSDavid du Colombier 	static int already;
1967dd7cddfSDavid du Colombier 
1977dd7cddfSDavid du Colombier 	if(already)
1987dd7cddfSDavid du Colombier 		return;
1997dd7cddfSDavid du Colombier 	already = 1;
2007dd7cddfSDavid du Colombier 
2017dd7cddfSDavid du Colombier 	for(h = head; h->type != nil; h++)
2027dd7cddfSDavid du Colombier 		h->len = strlen(h->type);
2037dd7cddfSDavid du Colombier }
2047dd7cddfSDavid du Colombier 
2057dd7cddfSDavid du Colombier /*
2067dd7cddfSDavid du Colombier  *  parse a Unix style header
2077dd7cddfSDavid du Colombier  */
20880ee5cbfSDavid du Colombier void
2097dd7cddfSDavid du Colombier parseunix(Message *m)
2107dd7cddfSDavid du Colombier {
2117dd7cddfSDavid du Colombier 	char *p;
2127dd7cddfSDavid du Colombier 	String *h;
2137dd7cddfSDavid du Colombier 
2147dd7cddfSDavid du Colombier 	h = s_new();
2157dd7cddfSDavid du Colombier 	for(p = m->start + 5; *p && *p != '\r' && *p != '\n'; p++)
2167dd7cddfSDavid du Colombier 		s_putc(h, *p);
2177dd7cddfSDavid du Colombier 	s_terminate(h);
2187dd7cddfSDavid du Colombier 	s_restart(h);
2197dd7cddfSDavid du Colombier 
2207dd7cddfSDavid du Colombier 	m->unixfrom = s_parse(h, s_reset(m->unixfrom));
2217dd7cddfSDavid du Colombier 	m->unixdate = s_append(s_reset(m->unixdate), h->ptr);
2227dd7cddfSDavid du Colombier 
2237dd7cddfSDavid du Colombier 	s_free(h);
2247dd7cddfSDavid du Colombier }
2257dd7cddfSDavid du Colombier 
2267dd7cddfSDavid du Colombier /*
2277dd7cddfSDavid du Colombier  *  parse a message
2287dd7cddfSDavid du Colombier  */
22980ee5cbfSDavid du Colombier void
2307a02f3c0SDavid du Colombier parseheaders(Message *m, int justmime, Mailbox *mb, int addfrom)
2317dd7cddfSDavid du Colombier {
2327dd7cddfSDavid du Colombier 	String *hl;
2337dd7cddfSDavid du Colombier 	Header *h;
23480ee5cbfSDavid du Colombier 	char *p, *q;
2357dd7cddfSDavid du Colombier 	int i;
2367dd7cddfSDavid du Colombier 
2377dd7cddfSDavid du Colombier 	if(m->whole == m->whole->whole){
2389a747e4fSDavid du Colombier 		henter(PATH(mb->id, Qmbox), m->name,
2399a747e4fSDavid du Colombier 			(Qid){PATH(m->id, Qdir), 0, QTDIR}, m, mb);
2407dd7cddfSDavid du Colombier 	} else {
2419a747e4fSDavid du Colombier 		henter(PATH(m->whole->id, Qdir), m->name,
2429a747e4fSDavid du Colombier 			(Qid){PATH(m->id, Qdir), 0, QTDIR}, m, mb);
2437dd7cddfSDavid du Colombier 	}
2447dd7cddfSDavid du Colombier 	for(i = 0; i < Qmax; i++)
2459a747e4fSDavid du Colombier 		henter(PATH(m->id, Qdir), dirtab[i],
2469a747e4fSDavid du Colombier 			(Qid){PATH(m->id, i), 0, QTFILE}, m, mb);
2477dd7cddfSDavid du Colombier 
2487dd7cddfSDavid du Colombier 	// parse mime headers
2497dd7cddfSDavid du Colombier 	p = m->header;
2507dd7cddfSDavid du Colombier 	hl = s_new();
2517dd7cddfSDavid du Colombier 	while(headerline(&p, hl)){
2527dd7cddfSDavid du Colombier 		if(justmime)
2537dd7cddfSDavid du Colombier 			h = &head[Mhead];
2547dd7cddfSDavid du Colombier 		else
2557dd7cddfSDavid du Colombier 			h = head;
2567dd7cddfSDavid du Colombier 		for(; h->type; h++){
2577dd7cddfSDavid du Colombier 			if(cistrncmp(s_to_c(hl), h->type, h->len) == 0){
2587dd7cddfSDavid du Colombier 				(*h->f)(m, h, s_to_c(hl));
2597dd7cddfSDavid du Colombier 				break;
2607dd7cddfSDavid du Colombier 			}
2617dd7cddfSDavid du Colombier 		}
2627dd7cddfSDavid du Colombier 		s_reset(hl);
2637dd7cddfSDavid du Colombier 	}
2647dd7cddfSDavid du Colombier 	s_free(hl);
2657dd7cddfSDavid du Colombier 
2667dd7cddfSDavid du Colombier 	// the blank line isn't really part of the body or header
2677dd7cddfSDavid du Colombier 	if(justmime){
2687dd7cddfSDavid du Colombier 		m->mhend = p;
2697dd7cddfSDavid du Colombier 		m->hend = m->header;
2707dd7cddfSDavid du Colombier 	} else {
2717dd7cddfSDavid du Colombier 		m->hend = p;
2727dd7cddfSDavid du Colombier 	}
2737dd7cddfSDavid du Colombier 	if(*p == '\n')
2747dd7cddfSDavid du Colombier 		p++;
2757dd7cddfSDavid du Colombier 	m->rbody = m->body = p;
2767dd7cddfSDavid du Colombier 
2773ff48bf5SDavid du Colombier 	// if type is text, get any nulls out of the body.  This is
2783ff48bf5SDavid du Colombier 	// for the two seans and imap clients that get confused.
2793ff48bf5SDavid du Colombier 	if(strncmp(s_to_c(m->type), "text/", 5) == 0)
2803ff48bf5SDavid du Colombier 		nullsqueeze(m);
2813ff48bf5SDavid du Colombier 
28280ee5cbfSDavid du Colombier 	//
28380ee5cbfSDavid du Colombier 	// cobble together Unix-style from line
28480ee5cbfSDavid du Colombier 	// for local mailbox messages, we end up recreating the
28580ee5cbfSDavid du Colombier 	// original header.
28680ee5cbfSDavid du Colombier 	// for pop3 messages, the best we can do is
28780ee5cbfSDavid du Colombier 	// use the From: information and the RFC822 date.
28880ee5cbfSDavid du Colombier 	//
28967031067SDavid du Colombier 	if(m->unixdate == nil || strcmp(s_to_c(m->unixdate), "???") == 0
29067031067SDavid du Colombier 	|| strcmp(s_to_c(m->unixdate), "Thu Jan 1 00:00:00 GMT 1970") == 0){
29167031067SDavid du Colombier 		if(m->unixdate){
29267031067SDavid du Colombier 			s_free(m->unixdate);
29367031067SDavid du Colombier 			m->unixdate = nil;
29467031067SDavid du Colombier 		}
29580ee5cbfSDavid du Colombier 		// look for the date in the first Received: line.
29680ee5cbfSDavid du Colombier 		// it's likely to be the right time zone (it's
29780ee5cbfSDavid du Colombier 	 	// the local system) and in a convenient format.
29880ee5cbfSDavid du Colombier 		if(cistrncmp(m->header, "received:", 9)==0){
299d9306527SDavid du Colombier 			if((q = strchr(m->header, ';')) != nil){
300d9306527SDavid du Colombier 				p = q;
301d9306527SDavid du Colombier 				while((p = strchr(p, '\n')) != nil){
302d9306527SDavid du Colombier 					if(p[1] != ' ' && p[1] != '\t' && p[1] != '\n')
303d9306527SDavid du Colombier 						break;
304d9306527SDavid du Colombier 					p++;
305d9306527SDavid du Colombier 				}
306d9306527SDavid du Colombier 				if(p){
30780ee5cbfSDavid du Colombier 					*p = '\0';
30880ee5cbfSDavid du Colombier 					m->unixdate = date822tounix(q+1);
30980ee5cbfSDavid du Colombier 					*p = '\n';
31080ee5cbfSDavid du Colombier 				}
31180ee5cbfSDavid du Colombier 			}
312d9306527SDavid du Colombier 		}
31380ee5cbfSDavid du Colombier 
31480ee5cbfSDavid du Colombier 		// fall back on the rfc822 date
31580ee5cbfSDavid du Colombier 		if(m->unixdate==nil && m->date822)
31680ee5cbfSDavid du Colombier 			m->unixdate = date822tounix(s_to_c(m->date822));
31780ee5cbfSDavid du Colombier 	}
31880ee5cbfSDavid du Colombier 
3199a747e4fSDavid du Colombier 	if(m->unixheader != nil)
3209a747e4fSDavid du Colombier 		s_free(m->unixheader);
3217a02f3c0SDavid du Colombier 
3227a02f3c0SDavid du Colombier 	// only fake header for top-level messages for pop3 and imap4
3237a02f3c0SDavid du Colombier 	// clients (those protocols don't include the unix header).
3247a02f3c0SDavid du Colombier 	// adding the unix header all the time screws up mime-attached
3257a02f3c0SDavid du Colombier 	// rfc822 messages.
3267a02f3c0SDavid du Colombier 	if(!addfrom && !m->unixfrom){
3277a02f3c0SDavid du Colombier 		m->unixheader = nil;
3287a02f3c0SDavid du Colombier 		return;
3297a02f3c0SDavid du Colombier 	}
3307a02f3c0SDavid du Colombier 
33180ee5cbfSDavid du Colombier 	m->unixheader = s_copy("From ");
33267031067SDavid du Colombier 	if(m->unixfrom && strcmp(s_to_c(m->unixfrom), "???") != 0)
33380ee5cbfSDavid du Colombier 		s_append(m->unixheader, s_to_c(m->unixfrom));
33480ee5cbfSDavid du Colombier 	else if(m->from822)
33580ee5cbfSDavid du Colombier 		s_append(m->unixheader, s_to_c(m->from822));
33680ee5cbfSDavid du Colombier 	else
33780ee5cbfSDavid du Colombier 		s_append(m->unixheader, "???");
33880ee5cbfSDavid du Colombier 
33980ee5cbfSDavid du Colombier 	s_append(m->unixheader, " ");
34080ee5cbfSDavid du Colombier 	if(m->unixdate)
34180ee5cbfSDavid du Colombier 		s_append(m->unixheader, s_to_c(m->unixdate));
34280ee5cbfSDavid du Colombier 	else
34367031067SDavid du Colombier 		s_append(m->unixheader, "Thu Jan  1 00:00:00 GMT 1970");
34480ee5cbfSDavid du Colombier 
34580ee5cbfSDavid du Colombier 	s_append(m->unixheader, "\n");
34680ee5cbfSDavid du Colombier }
34780ee5cbfSDavid du Colombier 
3489a747e4fSDavid du Colombier String*
3499a747e4fSDavid du Colombier promote(String **sp)
3509a747e4fSDavid du Colombier {
3519a747e4fSDavid du Colombier 	String *s;
3529a747e4fSDavid du Colombier 
3539a747e4fSDavid du Colombier 	if(*sp != nil)
3549a747e4fSDavid du Colombier 		s = s_clone(*sp);
3559a747e4fSDavid du Colombier 	else
3569a747e4fSDavid du Colombier 		s = nil;
3579a747e4fSDavid du Colombier 	return s;
3589a747e4fSDavid du Colombier }
3599a747e4fSDavid du Colombier 
36080ee5cbfSDavid du Colombier void
36180ee5cbfSDavid du Colombier parsebody(Message *m, Mailbox *mb)
36280ee5cbfSDavid du Colombier {
3639a747e4fSDavid du Colombier 	Message *nm;
3649a747e4fSDavid du Colombier 
3657dd7cddfSDavid du Colombier 	// recurse
3667dd7cddfSDavid du Colombier 	if(strncmp(s_to_c(m->type), "multipart/", 10) == 0){
3677dd7cddfSDavid du Colombier 		parseattachments(m, mb);
3687dd7cddfSDavid du Colombier 	} else if(strcmp(s_to_c(m->type), "message/rfc822") == 0){
3697dd7cddfSDavid du Colombier 		decode(m);
3707dd7cddfSDavid du Colombier 		parseattachments(m, mb);
3719a747e4fSDavid du Colombier 		nm = m->part;
3729a747e4fSDavid du Colombier 
3739a747e4fSDavid du Colombier 		// promote headers
3745d459b5aSDavid du Colombier 		if(m->replyto822 == nil && m->from822 == nil && m->sender822 == nil){
3759a747e4fSDavid du Colombier 			m->from822 = promote(&nm->from822);
3769a747e4fSDavid du Colombier 			m->to822 = promote(&nm->to822);
3779a747e4fSDavid du Colombier 			m->date822 = promote(&nm->date822);
3789a747e4fSDavid du Colombier 			m->sender822 = promote(&nm->sender822);
3799a747e4fSDavid du Colombier 			m->replyto822 = promote(&nm->replyto822);
3809a747e4fSDavid du Colombier 			m->subject822 = promote(&nm->subject822);
3819a747e4fSDavid du Colombier 			m->unixdate = promote(&nm->unixdate);
3827dd7cddfSDavid du Colombier 		}
3837dd7cddfSDavid du Colombier 	}
3845d459b5aSDavid du Colombier }
3857dd7cddfSDavid du Colombier 
38680ee5cbfSDavid du Colombier void
3877a02f3c0SDavid du Colombier parse(Message *m, int justmime, Mailbox *mb, int addfrom)
38880ee5cbfSDavid du Colombier {
3897a02f3c0SDavid du Colombier 	parseheaders(m, justmime, mb, addfrom);
39080ee5cbfSDavid du Colombier 	parsebody(m, mb);
39180ee5cbfSDavid du Colombier }
39280ee5cbfSDavid du Colombier 
3937dd7cddfSDavid du Colombier static void
3947dd7cddfSDavid du Colombier parseattachments(Message *m, Mailbox *mb)
3957dd7cddfSDavid du Colombier {
3967dd7cddfSDavid du Colombier 	Message *nm, **l;
3977dd7cddfSDavid du Colombier 	char *p, *x;
3987dd7cddfSDavid du Colombier 
3997dd7cddfSDavid du Colombier 	// if there's a boundary, recurse...
4007dd7cddfSDavid du Colombier 	if(m->boundary != nil){
4017dd7cddfSDavid du Colombier 		p = m->body;
4027dd7cddfSDavid du Colombier 		nm = nil;
4037dd7cddfSDavid du Colombier 		l = &m->part;
4047dd7cddfSDavid du Colombier 		for(;;){
4057dd7cddfSDavid du Colombier 			x = strstr(p, s_to_c(m->boundary));
4063ff48bf5SDavid du Colombier 
4073ff48bf5SDavid du Colombier 			/* no boundary, we're done */
4083ff48bf5SDavid du Colombier 			if(x == nil){
4097dd7cddfSDavid du Colombier 				if(nm != nil)
4107dd7cddfSDavid du Colombier 					nm->rbend = nm->bend = nm->end = m->bend;
4117dd7cddfSDavid du Colombier 				break;
4127dd7cddfSDavid du Colombier 			}
4133ff48bf5SDavid du Colombier 
4143ff48bf5SDavid du Colombier 			/* boundary must be at the start of a line */
4153ff48bf5SDavid du Colombier 			if(x != m->body && *(x-1) != '\n'){
4163ff48bf5SDavid du Colombier 				p = x+1;
4173ff48bf5SDavid du Colombier 				continue;
4183ff48bf5SDavid du Colombier 			}
4193ff48bf5SDavid du Colombier 
4207dd7cddfSDavid du Colombier 			if(nm != nil)
4217dd7cddfSDavid du Colombier 				nm->rbend = nm->bend = nm->end = x;
4227dd7cddfSDavid du Colombier 			x += strlen(s_to_c(m->boundary));
4237dd7cddfSDavid du Colombier 
4247dd7cddfSDavid du Colombier 			/* is this the last part? ignore anything after it */
4257dd7cddfSDavid du Colombier 			if(strncmp(x, "--", 2) == 0)
4267dd7cddfSDavid du Colombier 				break;
4277dd7cddfSDavid du Colombier 
4287dd7cddfSDavid du Colombier 			p = strchr(x, '\n');
4297dd7cddfSDavid du Colombier 			if(p == nil)
4307dd7cddfSDavid du Colombier 				break;
4317dd7cddfSDavid du Colombier 			nm = newmessage(m);
4327dd7cddfSDavid du Colombier 			nm->start = nm->header = nm->body = nm->rbody = ++p;
4337dd7cddfSDavid du Colombier 			nm->mheader = nm->header;
4347dd7cddfSDavid du Colombier 			*l = nm;
4357dd7cddfSDavid du Colombier 			l = &nm->next;
4367dd7cddfSDavid du Colombier 		}
4377dd7cddfSDavid du Colombier 		for(nm = m->part; nm != nil; nm = nm->next)
4387a02f3c0SDavid du Colombier 			parse(nm, 1, mb, 0);
4399a747e4fSDavid du Colombier 		return;
4407dd7cddfSDavid du Colombier 	}
4419a747e4fSDavid du Colombier 
4429a747e4fSDavid du Colombier 	// if we've got an rfc822 message, recurse...
4439a747e4fSDavid du Colombier 	if(strcmp(s_to_c(m->type), "message/rfc822") == 0){
4447dd7cddfSDavid du Colombier 		nm = newmessage(m);
4457dd7cddfSDavid du Colombier 		m->part = nm;
4467dd7cddfSDavid du Colombier 		nm->start = nm->header = nm->body = nm->rbody = m->body;
4477dd7cddfSDavid du Colombier 		nm->end = nm->bend = nm->rbend = m->bend;
4487a02f3c0SDavid du Colombier 		parse(nm, 0, mb, 0);
4497dd7cddfSDavid du Colombier 	}
4507dd7cddfSDavid du Colombier }
4517dd7cddfSDavid du Colombier 
4527dd7cddfSDavid du Colombier /*
4537dd7cddfSDavid du Colombier  *  pick up a header line
4547dd7cddfSDavid du Colombier  */
4557dd7cddfSDavid du Colombier static int
4567dd7cddfSDavid du Colombier headerline(char **pp, String *hl)
4577dd7cddfSDavid du Colombier {
4587dd7cddfSDavid du Colombier 	char *p, *x;
4597dd7cddfSDavid du Colombier 
4607dd7cddfSDavid du Colombier 	s_reset(hl);
4617dd7cddfSDavid du Colombier 	p = *pp;
4627dd7cddfSDavid du Colombier 	x = strpbrk(p, ":\n");
4637dd7cddfSDavid du Colombier 	if(x == nil || *x == '\n')
4647dd7cddfSDavid du Colombier 		return 0;
4657dd7cddfSDavid du Colombier 	for(;;){
4667dd7cddfSDavid du Colombier 		x = strchr(p, '\n');
4677dd7cddfSDavid du Colombier 		if(x == nil)
4687dd7cddfSDavid du Colombier 			x = p + strlen(p);
4697dd7cddfSDavid du Colombier 		s_nappend(hl, p, x-p);
4707dd7cddfSDavid du Colombier 		p = x;
471223a736eSDavid du Colombier 		if(*p != '\n' || *++p != ' ' && *p != '\t')
4727dd7cddfSDavid du Colombier 			break;
473223a736eSDavid du Colombier 		while(*p == ' ' || *p == '\t')
474223a736eSDavid du Colombier 			p++;
475223a736eSDavid du Colombier 		s_putc(hl, ' ');
4767dd7cddfSDavid du Colombier 	}
4777dd7cddfSDavid du Colombier 	*pp = p;
4787dd7cddfSDavid du Colombier 	return 1;
4797dd7cddfSDavid du Colombier }
4807dd7cddfSDavid du Colombier 
481*34e11723SDavid du Colombier /* returns nil iff there are no addressees */
4827dd7cddfSDavid du Colombier static String*
4837dd7cddfSDavid du Colombier addr822(char *p)
4847dd7cddfSDavid du Colombier {
4857dd7cddfSDavid du Colombier 	String *s, *list;
4867dd7cddfSDavid du Colombier 	int incomment, addrdone, inanticomment, quoted;
4877dd7cddfSDavid du Colombier 	int n;
4887dd7cddfSDavid du Colombier 	int c;
4897dd7cddfSDavid du Colombier 
4907dd7cddfSDavid du Colombier 	list = s_new();
4917dd7cddfSDavid du Colombier 	s = s_new();
4927dd7cddfSDavid du Colombier 	quoted = incomment = addrdone = inanticomment = 0;
4937dd7cddfSDavid du Colombier 	n = 0;
4947dd7cddfSDavid du Colombier 	for(; *p; p++){
4957dd7cddfSDavid du Colombier 		c = *p;
4967dd7cddfSDavid du Colombier 
4977dd7cddfSDavid du Colombier 		// whitespace is ignored
4987dd7cddfSDavid du Colombier 		if(!quoted && isspace(c) || c == '\r')
4997dd7cddfSDavid du Colombier 			continue;
5007dd7cddfSDavid du Colombier 
5017dd7cddfSDavid du Colombier 		// strings are always treated as atoms
5027dd7cddfSDavid du Colombier 		if(!quoted && c == '"'){
5037dd7cddfSDavid du Colombier 			if(!addrdone && !incomment)
5047dd7cddfSDavid du Colombier 				s_putc(s, c);
5057dd7cddfSDavid du Colombier 			for(p++; *p; p++){
5067dd7cddfSDavid du Colombier 				if(!addrdone && !incomment)
5077dd7cddfSDavid du Colombier 					s_putc(s, *p);
5087dd7cddfSDavid du Colombier 				if(!quoted && *p == '"')
5097dd7cddfSDavid du Colombier 					break;
5107dd7cddfSDavid du Colombier 				if(*p == '\\')
5117dd7cddfSDavid du Colombier 					quoted = 1;
5127dd7cddfSDavid du Colombier 				else
5137dd7cddfSDavid du Colombier 					quoted = 0;
5147dd7cddfSDavid du Colombier 			}
5157dd7cddfSDavid du Colombier 			if(*p == 0)
5167dd7cddfSDavid du Colombier 				break;
5177dd7cddfSDavid du Colombier 			quoted = 0;
5187dd7cddfSDavid du Colombier 			continue;
5197dd7cddfSDavid du Colombier 		}
5207dd7cddfSDavid du Colombier 
5217dd7cddfSDavid du Colombier 		// ignore everything in an expicit comment
5227dd7cddfSDavid du Colombier 		if(!quoted && c == '('){
5237dd7cddfSDavid du Colombier 			incomment = 1;
5247dd7cddfSDavid du Colombier 			continue;
5257dd7cddfSDavid du Colombier 		}
5267dd7cddfSDavid du Colombier 		if(incomment){
5277dd7cddfSDavid du Colombier 			if(!quoted && c == ')')
5287dd7cddfSDavid du Colombier 				incomment = 0;
5297dd7cddfSDavid du Colombier 			quoted = 0;
5307dd7cddfSDavid du Colombier 			continue;
5317dd7cddfSDavid du Colombier 		}
5327dd7cddfSDavid du Colombier 
5337dd7cddfSDavid du Colombier 		// anticomments makes everything outside of them comments
5347dd7cddfSDavid du Colombier 		if(!quoted && c == '<' && !inanticomment){
5357dd7cddfSDavid du Colombier 			inanticomment = 1;
5367dd7cddfSDavid du Colombier 			s = s_reset(s);
5377dd7cddfSDavid du Colombier 			continue;
5387dd7cddfSDavid du Colombier 		}
5397dd7cddfSDavid du Colombier 		if(!quoted && c == '>' && inanticomment){
5407dd7cddfSDavid du Colombier 			addrdone = 1;
5417dd7cddfSDavid du Colombier 			inanticomment = 0;
5427dd7cddfSDavid du Colombier 			continue;
5437dd7cddfSDavid du Colombier 		}
5447dd7cddfSDavid du Colombier 
5457dd7cddfSDavid du Colombier 		// commas separate addresses
5467dd7cddfSDavid du Colombier 		if(!quoted && c == ',' && !inanticomment){
5477dd7cddfSDavid du Colombier 			s_terminate(s);
5487dd7cddfSDavid du Colombier 			addrdone = 0;
5497dd7cddfSDavid du Colombier 			if(n++ != 0)
5507dd7cddfSDavid du Colombier 				s_append(list, " ");
5517dd7cddfSDavid du Colombier 			s_append(list, s_to_c(s));
5527dd7cddfSDavid du Colombier 			s = s_reset(s);
5537dd7cddfSDavid du Colombier 			continue;
5547dd7cddfSDavid du Colombier 		}
5557dd7cddfSDavid du Colombier 
5567dd7cddfSDavid du Colombier 		// what's left is part of the address
5577dd7cddfSDavid du Colombier 		s_putc(s, c);
5587dd7cddfSDavid du Colombier 
5597dd7cddfSDavid du Colombier 		// quoted characters are recognized only as characters
5607dd7cddfSDavid du Colombier 		if(c == '\\')
5617dd7cddfSDavid du Colombier 			quoted = 1;
5627dd7cddfSDavid du Colombier 		else
5637dd7cddfSDavid du Colombier 			quoted = 0;
5647dd7cddfSDavid du Colombier 
5657dd7cddfSDavid du Colombier 	}
5667dd7cddfSDavid du Colombier 
5677dd7cddfSDavid du Colombier 	if(*s_to_c(s) != 0){
5687dd7cddfSDavid du Colombier 		s_terminate(s);
5697dd7cddfSDavid du Colombier 		if(n++ != 0)
5707dd7cddfSDavid du Colombier 			s_append(list, " ");
5717dd7cddfSDavid du Colombier 		s_append(list, s_to_c(s));
5727dd7cddfSDavid du Colombier 	}
5737dd7cddfSDavid du Colombier 	s_free(s);
5747dd7cddfSDavid du Colombier 
575*34e11723SDavid du Colombier 	if(n == 0){		/* no addressees given, just the keyword */
5767dd7cddfSDavid du Colombier 		s_free(list);
5777dd7cddfSDavid du Colombier 		return nil;
5787dd7cddfSDavid du Colombier 	}
5797dd7cddfSDavid du Colombier 	return list;
5807dd7cddfSDavid du Colombier }
5817dd7cddfSDavid du Colombier 
5823e748dfcSDavid du Colombier /*
5833e748dfcSDavid du Colombier  * per rfc2822 §4.5.3, permit multiple to, cc and bcc headers by
5843e748dfcSDavid du Colombier  * concatenating their values.
5853e748dfcSDavid du Colombier  */
5863e748dfcSDavid du Colombier 
5877dd7cddfSDavid du Colombier static void
5887dd7cddfSDavid du Colombier to822(Message *m, Header *h, char *p)
5897dd7cddfSDavid du Colombier {
5903e748dfcSDavid du Colombier 	String *s;
5913e748dfcSDavid du Colombier 
5927dd7cddfSDavid du Colombier 	p += strlen(h->type);
5933e748dfcSDavid du Colombier 	s = addr822(p);
5943e748dfcSDavid du Colombier 	if (m->to822 == nil)
5953e748dfcSDavid du Colombier 		m->to822 = s;
596*34e11723SDavid du Colombier 	else if (s != nil) {
5973e748dfcSDavid du Colombier 		s_append(m->to822, " ");
5983e748dfcSDavid du Colombier 		s_append(m->to822, s_to_c(s));
5993e748dfcSDavid du Colombier 		s_free(s);
6003e748dfcSDavid du Colombier 	}
6017dd7cddfSDavid du Colombier }
6027dd7cddfSDavid du Colombier 
6037dd7cddfSDavid du Colombier static void
6047dd7cddfSDavid du Colombier cc822(Message *m, Header *h, char *p)
6057dd7cddfSDavid du Colombier {
6063e748dfcSDavid du Colombier 	String *s;
6073e748dfcSDavid du Colombier 
6087dd7cddfSDavid du Colombier 	p += strlen(h->type);
6093e748dfcSDavid du Colombier 	s = addr822(p);
6103e748dfcSDavid du Colombier 	if (m->cc822 == nil)
6113e748dfcSDavid du Colombier 		m->cc822 = s;
612*34e11723SDavid du Colombier 	else if (s != nil) {
6133e748dfcSDavid du Colombier 		s_append(m->cc822, " ");
6143e748dfcSDavid du Colombier 		s_append(m->cc822, s_to_c(s));
6153e748dfcSDavid du Colombier 		s_free(s);
6163e748dfcSDavid du Colombier 	}
6177dd7cddfSDavid du Colombier }
6187dd7cddfSDavid du Colombier 
6197dd7cddfSDavid du Colombier static void
6207dd7cddfSDavid du Colombier bcc822(Message *m, Header *h, char *p)
6217dd7cddfSDavid du Colombier {
6223e748dfcSDavid du Colombier 	String *s;
6233e748dfcSDavid du Colombier 
6247dd7cddfSDavid du Colombier 	p += strlen(h->type);
6253e748dfcSDavid du Colombier 	s = addr822(p);
6263e748dfcSDavid du Colombier 	if (m->bcc822 == nil)
6273e748dfcSDavid du Colombier 		m->bcc822 = s;
628*34e11723SDavid du Colombier 	else if (s != nil) {
6293e748dfcSDavid du Colombier 		s_append(m->bcc822, " ");
6303e748dfcSDavid du Colombier 		s_append(m->bcc822, s_to_c(s));
6313e748dfcSDavid du Colombier 		s_free(s);
6323e748dfcSDavid du Colombier 	}
6337dd7cddfSDavid du Colombier }
6347dd7cddfSDavid du Colombier 
6357dd7cddfSDavid du Colombier static void
6367dd7cddfSDavid du Colombier from822(Message *m, Header *h, char *p)
6377dd7cddfSDavid du Colombier {
6387dd7cddfSDavid du Colombier 	p += strlen(h->type);
6397dd7cddfSDavid du Colombier 	s_free(m->from822);
6407dd7cddfSDavid du Colombier 	m->from822 = addr822(p);
6417dd7cddfSDavid du Colombier }
6427dd7cddfSDavid du Colombier 
6437dd7cddfSDavid du Colombier static void
6447dd7cddfSDavid du Colombier sender822(Message *m, Header *h, char *p)
6457dd7cddfSDavid du Colombier {
6467dd7cddfSDavid du Colombier 	p += strlen(h->type);
6477dd7cddfSDavid du Colombier 	s_free(m->sender822);
6487dd7cddfSDavid du Colombier 	m->sender822 = addr822(p);
6497dd7cddfSDavid du Colombier }
6507dd7cddfSDavid du Colombier 
6517dd7cddfSDavid du Colombier static void
6527dd7cddfSDavid du Colombier replyto822(Message *m, Header *h, char *p)
6537dd7cddfSDavid du Colombier {
6547dd7cddfSDavid du Colombier 	p += strlen(h->type);
6557dd7cddfSDavid du Colombier 	s_free(m->replyto822);
6567dd7cddfSDavid du Colombier 	m->replyto822 = addr822(p);
6577dd7cddfSDavid du Colombier }
6587dd7cddfSDavid du Colombier 
6597dd7cddfSDavid du Colombier static void
6607dd7cddfSDavid du Colombier mimeversion(Message *m, Header *h, char *p)
6617dd7cddfSDavid du Colombier {
6627dd7cddfSDavid du Colombier 	p += strlen(h->type);
6637dd7cddfSDavid du Colombier 	s_free(m->mimeversion);
6647dd7cddfSDavid du Colombier 	m->mimeversion = addr822(p);
6657dd7cddfSDavid du Colombier }
6667dd7cddfSDavid du Colombier 
6677dd7cddfSDavid du Colombier static void
6687dd7cddfSDavid du Colombier killtrailingwhite(char *p)
6697dd7cddfSDavid du Colombier {
6707dd7cddfSDavid du Colombier 	char *e;
6717dd7cddfSDavid du Colombier 
6727dd7cddfSDavid du Colombier 	e = p + strlen(p) - 1;
6737dd7cddfSDavid du Colombier 	while(e > p && isspace(*e))
6747dd7cddfSDavid du Colombier 		*e-- = 0;
6757dd7cddfSDavid du Colombier }
6767dd7cddfSDavid du Colombier 
6777dd7cddfSDavid du Colombier static void
6787dd7cddfSDavid du Colombier date822(Message *m, Header *h, char *p)
6797dd7cddfSDavid du Colombier {
6807dd7cddfSDavid du Colombier 	p += strlen(h->type);
6817dd7cddfSDavid du Colombier 	p = skipwhite(p);
6827dd7cddfSDavid du Colombier 	s_free(m->date822);
6837dd7cddfSDavid du Colombier 	m->date822 = s_copy(p);
6847dd7cddfSDavid du Colombier 	p = s_to_c(m->date822);
6857dd7cddfSDavid du Colombier 	killtrailingwhite(p);
6867dd7cddfSDavid du Colombier }
6877dd7cddfSDavid du Colombier 
6887dd7cddfSDavid du Colombier static void
6897dd7cddfSDavid du Colombier subject822(Message *m, Header *h, char *p)
6907dd7cddfSDavid du Colombier {
6917dd7cddfSDavid du Colombier 	p += strlen(h->type);
6927dd7cddfSDavid du Colombier 	p = skipwhite(p);
6937dd7cddfSDavid du Colombier 	s_free(m->subject822);
6947dd7cddfSDavid du Colombier 	m->subject822 = s_copy(p);
6957dd7cddfSDavid du Colombier 	p = s_to_c(m->subject822);
6967dd7cddfSDavid du Colombier 	killtrailingwhite(p);
6977dd7cddfSDavid du Colombier }
6987dd7cddfSDavid du Colombier 
6997dd7cddfSDavid du Colombier static void
7007dd7cddfSDavid du Colombier inreplyto822(Message *m, Header *h, char *p)
7017dd7cddfSDavid du Colombier {
7027dd7cddfSDavid du Colombier 	p += strlen(h->type);
7037dd7cddfSDavid du Colombier 	p = skipwhite(p);
7047dd7cddfSDavid du Colombier 	s_free(m->inreplyto822);
7057dd7cddfSDavid du Colombier 	m->inreplyto822 = s_copy(p);
7067dd7cddfSDavid du Colombier 	p = s_to_c(m->inreplyto822);
7077dd7cddfSDavid du Colombier 	killtrailingwhite(p);
7087dd7cddfSDavid du Colombier }
7097dd7cddfSDavid du Colombier 
7107dd7cddfSDavid du Colombier static void
7117dd7cddfSDavid du Colombier messageid822(Message *m, Header *h, char *p)
7127dd7cddfSDavid du Colombier {
7137dd7cddfSDavid du Colombier 	p += strlen(h->type);
7147dd7cddfSDavid du Colombier 	p = skipwhite(p);
7157dd7cddfSDavid du Colombier 	s_free(m->messageid822);
7167dd7cddfSDavid du Colombier 	m->messageid822 = s_copy(p);
7177dd7cddfSDavid du Colombier 	p = s_to_c(m->messageid822);
7187dd7cddfSDavid du Colombier 	killtrailingwhite(p);
7197dd7cddfSDavid du Colombier }
7207dd7cddfSDavid du Colombier 
7217dd7cddfSDavid du Colombier static int
7227dd7cddfSDavid du Colombier isattribute(char **pp, char *attr)
7237dd7cddfSDavid du Colombier {
7247dd7cddfSDavid du Colombier 	char *p;
7257dd7cddfSDavid du Colombier 	int n;
7267dd7cddfSDavid du Colombier 
7277dd7cddfSDavid du Colombier 	n = strlen(attr);
7287dd7cddfSDavid du Colombier 	p = *pp;
7297dd7cddfSDavid du Colombier 	if(cistrncmp(p, attr, n) != 0)
7307dd7cddfSDavid du Colombier 		return 0;
7317dd7cddfSDavid du Colombier 	p += n;
7327dd7cddfSDavid du Colombier 	while(*p == ' ')
7337dd7cddfSDavid du Colombier 		p++;
7347dd7cddfSDavid du Colombier 	if(*p++ != '=')
7357dd7cddfSDavid du Colombier 		return 0;
7367dd7cddfSDavid du Colombier 	while(*p == ' ')
7377dd7cddfSDavid du Colombier 		p++;
7387dd7cddfSDavid du Colombier 	*pp = p;
7397dd7cddfSDavid du Colombier 	return 1;
7407dd7cddfSDavid du Colombier }
7417dd7cddfSDavid du Colombier 
7427dd7cddfSDavid du Colombier static void
7437dd7cddfSDavid du Colombier ctype(Message *m, Header *h, char *p)
7447dd7cddfSDavid du Colombier {
7457dd7cddfSDavid du Colombier 	String *s;
7467dd7cddfSDavid du Colombier 
7477dd7cddfSDavid du Colombier 	p += h->len;
7487dd7cddfSDavid du Colombier 	p = skipwhite(p);
7497dd7cddfSDavid du Colombier 
7507dd7cddfSDavid du Colombier 	p = getstring(p, m->type, 1);
7517dd7cddfSDavid du Colombier 
7527dd7cddfSDavid du Colombier 	while(*p){
7537dd7cddfSDavid du Colombier 		if(isattribute(&p, "boundary")){
7547dd7cddfSDavid du Colombier 			s = s_new();
7557dd7cddfSDavid du Colombier 			p = getstring(p, s, 0);
7567dd7cddfSDavid du Colombier 			m->boundary = s_reset(m->boundary);
7577dd7cddfSDavid du Colombier 			s_append(m->boundary, "--");
7587dd7cddfSDavid du Colombier 			s_append(m->boundary, s_to_c(s));
7597dd7cddfSDavid du Colombier 			s_free(s);
7607dd7cddfSDavid du Colombier 		} else if(cistrncmp(p, "multipart", 9) == 0){
7617dd7cddfSDavid du Colombier 			/*
7627dd7cddfSDavid du Colombier 			 *  the first unbounded part of a multipart message,
7637dd7cddfSDavid du Colombier 			 *  the preamble, is not displayed or saved
7647dd7cddfSDavid du Colombier 			 */
7657dd7cddfSDavid du Colombier 		} else if(isattribute(&p, "name")){
7667dd7cddfSDavid du Colombier 			if(m->filename == nil)
7677dd7cddfSDavid du Colombier 				setfilename(m, p);
7687dd7cddfSDavid du Colombier 		} else if(isattribute(&p, "charset")){
7697dd7cddfSDavid du Colombier 			p = getstring(p, s_reset(m->charset), 0);
7707dd7cddfSDavid du Colombier 		}
7717dd7cddfSDavid du Colombier 
7727dd7cddfSDavid du Colombier 		p = skiptosemi(p);
7737dd7cddfSDavid du Colombier 	}
7747dd7cddfSDavid du Colombier }
7757dd7cddfSDavid du Colombier 
7767dd7cddfSDavid du Colombier static void
7777dd7cddfSDavid du Colombier cencoding(Message *m, Header *h, char *p)
7787dd7cddfSDavid du Colombier {
7797dd7cddfSDavid du Colombier 	p += h->len;
7807dd7cddfSDavid du Colombier 	p = skipwhite(p);
7817dd7cddfSDavid du Colombier 	if(cistrncmp(p, "base64", 6) == 0)
7827dd7cddfSDavid du Colombier 		m->encoding = Ebase64;
7837dd7cddfSDavid du Colombier 	else if(cistrncmp(p, "quoted-printable", 16) == 0)
7847dd7cddfSDavid du Colombier 		m->encoding = Equoted;
7857dd7cddfSDavid du Colombier }
7867dd7cddfSDavid du Colombier 
7877dd7cddfSDavid du Colombier static void
7887dd7cddfSDavid du Colombier cdisposition(Message *m, Header *h, char *p)
7897dd7cddfSDavid du Colombier {
7907dd7cddfSDavid du Colombier 	p += h->len;
7917dd7cddfSDavid du Colombier 	p = skipwhite(p);
7927dd7cddfSDavid du Colombier 	while(*p){
7937dd7cddfSDavid du Colombier 		if(cistrncmp(p, "inline", 6) == 0){
7947dd7cddfSDavid du Colombier 			m->disposition = Dinline;
7957dd7cddfSDavid du Colombier 		} else if(cistrncmp(p, "attachment", 10) == 0){
7967dd7cddfSDavid du Colombier 			m->disposition = Dfile;
7977dd7cddfSDavid du Colombier 		} else if(cistrncmp(p, "filename=", 9) == 0){
7987dd7cddfSDavid du Colombier 			p += 9;
7997dd7cddfSDavid du Colombier 			setfilename(m, p);
8007dd7cddfSDavid du Colombier 		}
8017dd7cddfSDavid du Colombier 		p = skiptosemi(p);
8027dd7cddfSDavid du Colombier 	}
8037dd7cddfSDavid du Colombier 
8047dd7cddfSDavid du Colombier }
8057dd7cddfSDavid du Colombier 
8067dd7cddfSDavid du Colombier ulong msgallocd, msgfreed;
8077dd7cddfSDavid du Colombier 
8087dd7cddfSDavid du Colombier Message*
8097dd7cddfSDavid du Colombier newmessage(Message *parent)
8107dd7cddfSDavid du Colombier {
8117dd7cddfSDavid du Colombier 	static int id;
8127dd7cddfSDavid du Colombier 	Message *m;
8137dd7cddfSDavid du Colombier 
8147dd7cddfSDavid du Colombier 	msgallocd++;
8157dd7cddfSDavid du Colombier 
8167dd7cddfSDavid du Colombier 	m = emalloc(sizeof(*m));
8177dd7cddfSDavid du Colombier 	memset(m, 0, sizeof(*m));
8187dd7cddfSDavid du Colombier 	m->disposition = Dnone;
8197dd7cddfSDavid du Colombier 	m->type = s_copy("text/plain");
8207dd7cddfSDavid du Colombier 	m->charset = s_copy("iso-8859-1");
8217dd7cddfSDavid du Colombier 	m->id = newid();
8227dd7cddfSDavid du Colombier 	if(parent)
8237dd7cddfSDavid du Colombier 		sprint(m->name, "%d", ++(parent->subname));
8247dd7cddfSDavid du Colombier 	if(parent == nil)
8257dd7cddfSDavid du Colombier 		parent = m;
8267dd7cddfSDavid du Colombier 	m->whole = parent;
8277dd7cddfSDavid du Colombier 	m->hlen = -1;
8287dd7cddfSDavid du Colombier 	return m;
8297dd7cddfSDavid du Colombier }
8307dd7cddfSDavid du Colombier 
8317dd7cddfSDavid du Colombier // delete a message from a mailbox
8327dd7cddfSDavid du Colombier void
8337dd7cddfSDavid du Colombier delmessage(Mailbox *mb, Message *m)
8347dd7cddfSDavid du Colombier {
8357dd7cddfSDavid du Colombier 	Message **l;
8367dd7cddfSDavid du Colombier 	int i;
8377dd7cddfSDavid du Colombier 
8387dd7cddfSDavid du Colombier 	mb->vers++;
8397dd7cddfSDavid du Colombier 	msgfreed++;
8407dd7cddfSDavid du Colombier 
8417dd7cddfSDavid du Colombier 	if(m->whole != m){
8427dd7cddfSDavid du Colombier 		// unchain from parent
8437dd7cddfSDavid du Colombier 		for(l = &m->whole->part; *l && *l != m; l = &(*l)->next)
8447dd7cddfSDavid du Colombier 			;
8457dd7cddfSDavid du Colombier 		if(*l != nil)
8467dd7cddfSDavid du Colombier 			*l = m->next;
8477dd7cddfSDavid du Colombier 
8487dd7cddfSDavid du Colombier 		// clear out of name lookup hash table
8497dd7cddfSDavid du Colombier 		if(m->whole->whole == m->whole)
8509a747e4fSDavid du Colombier 			hfree(PATH(mb->id, Qmbox), m->name);
8517dd7cddfSDavid du Colombier 		else
8529a747e4fSDavid du Colombier 			hfree(PATH(m->whole->id, Qdir), m->name);
8537dd7cddfSDavid du Colombier 		for(i = 0; i < Qmax; i++)
8549a747e4fSDavid du Colombier 			hfree(PATH(m->id, Qdir), dirtab[i]);
8557dd7cddfSDavid du Colombier 	}
8567dd7cddfSDavid du Colombier 
8577dd7cddfSDavid du Colombier 	/* recurse through sub-parts */
8587dd7cddfSDavid du Colombier 	while(m->part)
8597dd7cddfSDavid du Colombier 		delmessage(mb, m->part);
8607dd7cddfSDavid du Colombier 
8617dd7cddfSDavid du Colombier 	/* free memory */
8627dd7cddfSDavid du Colombier 	if(m->mallocd)
8637dd7cddfSDavid du Colombier 		free(m->start);
8647dd7cddfSDavid du Colombier 	if(m->hallocd)
8657dd7cddfSDavid du Colombier 		free(m->header);
8667dd7cddfSDavid du Colombier 	if(m->ballocd)
8677dd7cddfSDavid du Colombier 		free(m->body);
8687dd7cddfSDavid du Colombier 	s_free(m->unixfrom);
8697dd7cddfSDavid du Colombier 	s_free(m->unixdate);
8709a747e4fSDavid du Colombier 	s_free(m->unixheader);
8717dd7cddfSDavid du Colombier 	s_free(m->from822);
8727dd7cddfSDavid du Colombier 	s_free(m->sender822);
8737dd7cddfSDavid du Colombier 	s_free(m->to822);
8747dd7cddfSDavid du Colombier 	s_free(m->bcc822);
87559cc4ca5SDavid du Colombier 	s_free(m->cc822);
8767dd7cddfSDavid du Colombier 	s_free(m->replyto822);
8777dd7cddfSDavid du Colombier 	s_free(m->date822);
8787dd7cddfSDavid du Colombier 	s_free(m->inreplyto822);
87959cc4ca5SDavid du Colombier 	s_free(m->subject822);
8807dd7cddfSDavid du Colombier 	s_free(m->messageid822);
8817dd7cddfSDavid du Colombier 	s_free(m->addrs);
8827dd7cddfSDavid du Colombier 	s_free(m->mimeversion);
88359cc4ca5SDavid du Colombier 	s_free(m->sdigest);
8847dd7cddfSDavid du Colombier 	s_free(m->boundary);
8857dd7cddfSDavid du Colombier 	s_free(m->type);
8867dd7cddfSDavid du Colombier 	s_free(m->charset);
8877dd7cddfSDavid du Colombier 	s_free(m->filename);
8887dd7cddfSDavid du Colombier 
8897dd7cddfSDavid du Colombier 	free(m);
8907dd7cddfSDavid du Colombier }
8917dd7cddfSDavid du Colombier 
8927dd7cddfSDavid du Colombier // mark messages (identified by path) for deletion
8937dd7cddfSDavid du Colombier void
8947dd7cddfSDavid du Colombier delmessages(int ac, char **av)
8957dd7cddfSDavid du Colombier {
8967dd7cddfSDavid du Colombier 	Mailbox *mb;
8977dd7cddfSDavid du Colombier 	Message *m;
8987dd7cddfSDavid du Colombier 	int i, needwrite;
8997dd7cddfSDavid du Colombier 
9007dd7cddfSDavid du Colombier 	qlock(&mbllock);
9017dd7cddfSDavid du Colombier 	for(mb = mbl; mb != nil; mb = mb->next)
9027dd7cddfSDavid du Colombier 		if(strcmp(av[0], mb->name) == 0){
9037dd7cddfSDavid du Colombier 			qlock(mb);
9047dd7cddfSDavid du Colombier 			break;
9057dd7cddfSDavid du Colombier 		}
9067dd7cddfSDavid du Colombier 	qunlock(&mbllock);
9077dd7cddfSDavid du Colombier 	if(mb == nil)
9087dd7cddfSDavid du Colombier 		return;
9097dd7cddfSDavid du Colombier 
9107dd7cddfSDavid du Colombier 	needwrite = 0;
9117dd7cddfSDavid du Colombier 	for(i = 1; i < ac; i++){
9127dd7cddfSDavid du Colombier 		for(m = mb->root->part; m != nil; m = m->next)
9137dd7cddfSDavid du Colombier 			if(strcmp(m->name, av[i]) == 0){
9147dd7cddfSDavid du Colombier 				if(!m->deleted){
9157dd7cddfSDavid du Colombier 					mailplumb(mb, m, 1);
9167dd7cddfSDavid du Colombier 					needwrite = 1;
9177dd7cddfSDavid du Colombier 					m->deleted = 1;
91859cc4ca5SDavid du Colombier 					logmsg("deleting", m);
9197dd7cddfSDavid du Colombier 				}
9207dd7cddfSDavid du Colombier 				break;
9217dd7cddfSDavid du Colombier 			}
9227dd7cddfSDavid du Colombier 	}
9237dd7cddfSDavid du Colombier 	if(needwrite)
9247dd7cddfSDavid du Colombier 		syncmbox(mb, 1);
9257dd7cddfSDavid du Colombier 	qunlock(mb);
9267dd7cddfSDavid du Colombier }
9277dd7cddfSDavid du Colombier 
9287dd7cddfSDavid du Colombier /*
9297dd7cddfSDavid du Colombier  *  the following are called with the mailbox qlocked
9307dd7cddfSDavid du Colombier  */
9317dd7cddfSDavid du Colombier void
9327dd7cddfSDavid du Colombier msgincref(Message *m)
9337dd7cddfSDavid du Colombier {
9347dd7cddfSDavid du Colombier 	m->refs++;
9357dd7cddfSDavid du Colombier }
9367dd7cddfSDavid du Colombier void
9377dd7cddfSDavid du Colombier msgdecref(Mailbox *mb, Message *m)
9387dd7cddfSDavid du Colombier {
9397dd7cddfSDavid du Colombier 	m->refs--;
9407dd7cddfSDavid du Colombier 	if(m->refs == 0 && m->deleted)
9417dd7cddfSDavid du Colombier 		syncmbox(mb, 1);
9427dd7cddfSDavid du Colombier }
9437dd7cddfSDavid du Colombier 
9447dd7cddfSDavid du Colombier /*
9457dd7cddfSDavid du Colombier  *  the following are called with mbllock'd
9467dd7cddfSDavid du Colombier  */
9477dd7cddfSDavid du Colombier void
9487dd7cddfSDavid du Colombier mboxincref(Mailbox *mb)
9497dd7cddfSDavid du Colombier {
9509a747e4fSDavid du Colombier 	assert(mb->refs > 0);
9517dd7cddfSDavid du Colombier 	mb->refs++;
9527dd7cddfSDavid du Colombier }
9537dd7cddfSDavid du Colombier void
9547dd7cddfSDavid du Colombier mboxdecref(Mailbox *mb)
9557dd7cddfSDavid du Colombier {
9569a747e4fSDavid du Colombier 	assert(mb->refs > 0);
9577dd7cddfSDavid du Colombier 	qlock(mb);
9587dd7cddfSDavid du Colombier 	mb->refs--;
9597dd7cddfSDavid du Colombier 	if(mb->refs == 0){
9607dd7cddfSDavid du Colombier 		delmessage(mb, mb->root);
96180ee5cbfSDavid du Colombier 		if(mb->ctl)
9629a747e4fSDavid du Colombier 			hfree(PATH(mb->id, Qmbox), "ctl");
96380ee5cbfSDavid du Colombier 		if(mb->close)
96480ee5cbfSDavid du Colombier 			(*mb->close)(mb);
9657dd7cddfSDavid du Colombier 		free(mb);
9667dd7cddfSDavid du Colombier 	} else
9677dd7cddfSDavid du Colombier 		qunlock(mb);
9687dd7cddfSDavid du Colombier }
9697dd7cddfSDavid du Colombier 
9707dd7cddfSDavid du Colombier int
9717dd7cddfSDavid du Colombier cistrncmp(char *a, char *b, int n)
9727dd7cddfSDavid du Colombier {
9737dd7cddfSDavid du Colombier 	while(n-- > 0){
9747dd7cddfSDavid du Colombier 		if(tolower(*a++) != tolower(*b++))
9757dd7cddfSDavid du Colombier 			return -1;
9767dd7cddfSDavid du Colombier 	}
9777dd7cddfSDavid du Colombier 	return 0;
9787dd7cddfSDavid du Colombier }
9797dd7cddfSDavid du Colombier 
9807dd7cddfSDavid du Colombier int
9817dd7cddfSDavid du Colombier cistrcmp(char *a, char *b)
9827dd7cddfSDavid du Colombier {
9837dd7cddfSDavid du Colombier 	for(;;){
9847dd7cddfSDavid du Colombier 		if(tolower(*a) != tolower(*b++))
9857dd7cddfSDavid du Colombier 			return -1;
9867dd7cddfSDavid du Colombier 		if(*a++ == 0)
9877dd7cddfSDavid du Colombier 			break;
9887dd7cddfSDavid du Colombier 	}
9897dd7cddfSDavid du Colombier 	return 0;
9907dd7cddfSDavid du Colombier }
9917dd7cddfSDavid du Colombier 
9927dd7cddfSDavid du Colombier static char*
9937dd7cddfSDavid du Colombier skipwhite(char *p)
9947dd7cddfSDavid du Colombier {
9957dd7cddfSDavid du Colombier 	while(isspace(*p))
9967dd7cddfSDavid du Colombier 		p++;
9977dd7cddfSDavid du Colombier 	return p;
9987dd7cddfSDavid du Colombier }
9997dd7cddfSDavid du Colombier 
10007dd7cddfSDavid du Colombier static char*
10017dd7cddfSDavid du Colombier skiptosemi(char *p)
10027dd7cddfSDavid du Colombier {
10037dd7cddfSDavid du Colombier 	while(*p && *p != ';')
10047dd7cddfSDavid du Colombier 		p++;
10057dd7cddfSDavid du Colombier 	while(*p == ';' || isspace(*p))
10067dd7cddfSDavid du Colombier 		p++;
10077dd7cddfSDavid du Colombier 	return p;
10087dd7cddfSDavid du Colombier }
10097dd7cddfSDavid du Colombier 
10107dd7cddfSDavid du Colombier static char*
10117dd7cddfSDavid du Colombier getstring(char *p, String *s, int dolower)
10127dd7cddfSDavid du Colombier {
10137dd7cddfSDavid du Colombier 	s = s_reset(s);
10147dd7cddfSDavid du Colombier 	p = skipwhite(p);
10157dd7cddfSDavid du Colombier 	if(*p == '"'){
10167dd7cddfSDavid du Colombier 		p++;
10177dd7cddfSDavid du Colombier 		for(;*p && *p != '"'; p++)
10187dd7cddfSDavid du Colombier 			if(dolower)
10197dd7cddfSDavid du Colombier 				s_putc(s, tolower(*p));
10207dd7cddfSDavid du Colombier 			else
10217dd7cddfSDavid du Colombier 				s_putc(s, *p);
10227dd7cddfSDavid du Colombier 		if(*p == '"')
10237dd7cddfSDavid du Colombier 			p++;
10247dd7cddfSDavid du Colombier 		s_terminate(s);
10257dd7cddfSDavid du Colombier 
10267dd7cddfSDavid du Colombier 		return p;
10277dd7cddfSDavid du Colombier 	}
10287dd7cddfSDavid du Colombier 
10299a747e4fSDavid du Colombier 	for(; *p && !isspace(*p) && *p != ';'; p++)
10307dd7cddfSDavid du Colombier 		if(dolower)
10317dd7cddfSDavid du Colombier 			s_putc(s, tolower(*p));
10327dd7cddfSDavid du Colombier 		else
10337dd7cddfSDavid du Colombier 			s_putc(s, *p);
10347dd7cddfSDavid du Colombier 	s_terminate(s);
10357dd7cddfSDavid du Colombier 
10367dd7cddfSDavid du Colombier 	return p;
10377dd7cddfSDavid du Colombier }
10387dd7cddfSDavid du Colombier 
10397dd7cddfSDavid du Colombier static void
10407dd7cddfSDavid du Colombier setfilename(Message *m, char *p)
10417dd7cddfSDavid du Colombier {
10427dd7cddfSDavid du Colombier 	m->filename = s_reset(m->filename);
10437dd7cddfSDavid du Colombier 	getstring(p, m->filename, 0);
10447dd7cddfSDavid du Colombier 	for(p = s_to_c(m->filename); *p; p++)
10457dd7cddfSDavid du Colombier 		if(*p == ' ' || *p == '\t' || *p == ';')
10467dd7cddfSDavid du Colombier 			*p = '_';
10477dd7cddfSDavid du Colombier }
10487dd7cddfSDavid du Colombier 
10497dd7cddfSDavid du Colombier //
10507dd7cddfSDavid du Colombier // undecode message body
10517dd7cddfSDavid du Colombier //
10527dd7cddfSDavid du Colombier void
10537dd7cddfSDavid du Colombier decode(Message *m)
10547dd7cddfSDavid du Colombier {
10557dd7cddfSDavid du Colombier 	int i, len;
10567dd7cddfSDavid du Colombier 	char *x;
10577dd7cddfSDavid du Colombier 
10587dd7cddfSDavid du Colombier 	if(m->decoded)
10597dd7cddfSDavid du Colombier 		return;
10607dd7cddfSDavid du Colombier 	switch(m->encoding){
10617dd7cddfSDavid du Colombier 	case Ebase64:
10627dd7cddfSDavid du Colombier 		len = m->bend - m->body;
10637dd7cddfSDavid du Colombier 		i = (len*3)/4+1;	// room for max chars + null
10647dd7cddfSDavid du Colombier 		x = emalloc(i);
10657dd7cddfSDavid du Colombier 		len = dec64((uchar*)x, i, m->body, len);
10667dd7cddfSDavid du Colombier 		if(m->ballocd)
10677dd7cddfSDavid du Colombier 			free(m->body);
10687dd7cddfSDavid du Colombier 		m->body = x;
10697dd7cddfSDavid du Colombier 		m->bend = x + len;
10707dd7cddfSDavid du Colombier 		m->ballocd = 1;
10717dd7cddfSDavid du Colombier 		break;
10727dd7cddfSDavid du Colombier 	case Equoted:
10737dd7cddfSDavid du Colombier 		len = m->bend - m->body;
10747dd7cddfSDavid du Colombier 		x = emalloc(len+2);	// room for null and possible extra nl
107517dd33a2SDavid du Colombier 		len = decquoted(x, m->body, m->bend, 0);
10767dd7cddfSDavid du Colombier 		if(m->ballocd)
10777dd7cddfSDavid du Colombier 			free(m->body);
10787dd7cddfSDavid du Colombier 		m->body = x;
10797dd7cddfSDavid du Colombier 		m->bend = x + len;
10807dd7cddfSDavid du Colombier 		m->ballocd = 1;
10817dd7cddfSDavid du Colombier 		break;
10827dd7cddfSDavid du Colombier 	default:
10837dd7cddfSDavid du Colombier 		break;
10847dd7cddfSDavid du Colombier 	}
10857dd7cddfSDavid du Colombier 	m->decoded = 1;
10867dd7cddfSDavid du Colombier }
10877dd7cddfSDavid du Colombier 
10887dd7cddfSDavid du Colombier // convert latin1 to utf
10897dd7cddfSDavid du Colombier void
10907dd7cddfSDavid du Colombier convert(Message *m)
10917dd7cddfSDavid du Colombier {
10927dd7cddfSDavid du Colombier 	int len;
10937dd7cddfSDavid du Colombier 	char *x;
10947dd7cddfSDavid du Colombier 
10957dd7cddfSDavid du Colombier 	// don't convert if we're not a leaf, not text, or already converted
10967dd7cddfSDavid du Colombier 	if(m->converted)
10977dd7cddfSDavid du Colombier 		return;
10987dd7cddfSDavid du Colombier 	if(m->part != nil)
10997dd7cddfSDavid du Colombier 		return;
11007dd7cddfSDavid du Colombier 	if(cistrncmp(s_to_c(m->type), "text", 4) != 0)
11017dd7cddfSDavid du Colombier 		return;
11027dd7cddfSDavid du Colombier 
1103816336a7SDavid du Colombier 	len = xtoutf(s_to_c(m->charset), &x, m->body, m->bend);
11047dd7cddfSDavid du Colombier 	if(len > 0){
11057dd7cddfSDavid du Colombier 		if(m->ballocd)
11067dd7cddfSDavid du Colombier 			free(m->body);
11077dd7cddfSDavid du Colombier 		m->body = x;
11087dd7cddfSDavid du Colombier 		m->bend = x + len;
11097dd7cddfSDavid du Colombier 		m->ballocd = 1;
11107dd7cddfSDavid du Colombier 	}
11117dd7cddfSDavid du Colombier 	m->converted = 1;
11127dd7cddfSDavid du Colombier }
11137dd7cddfSDavid du Colombier 
11147dd7cddfSDavid du Colombier static int
11157dd7cddfSDavid du Colombier hex2int(int x)
11167dd7cddfSDavid du Colombier {
11177dd7cddfSDavid du Colombier 	if(x >= '0' && x <= '9')
11187dd7cddfSDavid du Colombier 		return x - '0';
11197dd7cddfSDavid du Colombier 	if(x >= 'A' && x <= 'F')
11207dd7cddfSDavid du Colombier 		return (x - 'A') + 10;
11217dd7cddfSDavid du Colombier 	if(x >= 'a' && x <= 'f')
11227dd7cddfSDavid du Colombier 		return (x - 'a') + 10;
11237dd7cddfSDavid du Colombier 	return 0;
11247dd7cddfSDavid du Colombier }
11257dd7cddfSDavid du Colombier 
1126816336a7SDavid du Colombier // underscores are translated in 2047 headers (uscores=1)
1127816336a7SDavid du Colombier // but not in the body (uscores=0)
11287dd7cddfSDavid du Colombier static char*
112917dd33a2SDavid du Colombier decquotedline(char *out, char *in, char *e, int uscores)
11307dd7cddfSDavid du Colombier {
11317dd7cddfSDavid du Colombier 	int c, soft;
11327dd7cddfSDavid du Colombier 
11337dd7cddfSDavid du Colombier 	/* dump trailing white space */
11347dd7cddfSDavid du Colombier 	while(e >= in && (*e == ' ' || *e == '\t' || *e == '\r' || *e == '\n'))
11357dd7cddfSDavid du Colombier 		e--;
11367dd7cddfSDavid du Colombier 
11377dd7cddfSDavid du Colombier 	/* trailing '=' means no newline */
11387dd7cddfSDavid du Colombier 	if(*e == '='){
11397dd7cddfSDavid du Colombier 		soft = 1;
11407dd7cddfSDavid du Colombier 		e--;
11417dd7cddfSDavid du Colombier 	} else
11427dd7cddfSDavid du Colombier 		soft = 0;
11437dd7cddfSDavid du Colombier 
11447dd7cddfSDavid du Colombier 	while(in <= e){
11457dd7cddfSDavid du Colombier 		c = (*in++) & 0xff;
1146816336a7SDavid du Colombier 		switch(c){
1147816336a7SDavid du Colombier 		case '_':
1148816336a7SDavid du Colombier 			if(uscores){
1149816336a7SDavid du Colombier 				*out++ = ' ';
1150816336a7SDavid du Colombier 				break;
1151816336a7SDavid du Colombier 			}
1152816336a7SDavid du Colombier 		default:
11537dd7cddfSDavid du Colombier 			*out++ = c;
11547dd7cddfSDavid du Colombier 			break;
1155816336a7SDavid du Colombier 		case '=':
11567dd7cddfSDavid du Colombier 			c = hex2int(*in++)<<4;
11577dd7cddfSDavid du Colombier 			c |= hex2int(*in++);
11587dd7cddfSDavid du Colombier 			*out++ = c;
11597dd7cddfSDavid du Colombier 			break;
11607dd7cddfSDavid du Colombier 		}
11617dd7cddfSDavid du Colombier 	}
11627dd7cddfSDavid du Colombier 	if(!soft)
11637dd7cddfSDavid du Colombier 		*out++ = '\n';
11647dd7cddfSDavid du Colombier 	*out = 0;
11657dd7cddfSDavid du Colombier 
11667dd7cddfSDavid du Colombier 	return out;
11677dd7cddfSDavid du Colombier }
11687dd7cddfSDavid du Colombier 
11697dd7cddfSDavid du Colombier int
117017dd33a2SDavid du Colombier decquoted(char *out, char *in, char *e, int uscores)
11717dd7cddfSDavid du Colombier {
11727dd7cddfSDavid du Colombier 	char *p, *nl;
11737dd7cddfSDavid du Colombier 
11747dd7cddfSDavid du Colombier 	p = out;
11757dd7cddfSDavid du Colombier 	while((nl = strchr(in, '\n')) != nil && nl < e){
117617dd33a2SDavid du Colombier 		p = decquotedline(p, in, nl, uscores);
11777dd7cddfSDavid du Colombier 		in = nl + 1;
11787dd7cddfSDavid du Colombier 	}
11797dd7cddfSDavid du Colombier 	if(in < e)
118017dd33a2SDavid du Colombier 		p = decquotedline(p, in, e-1, uscores);
11817dd7cddfSDavid du Colombier 
11827dd7cddfSDavid du Colombier 	// make sure we end with a new line
11837dd7cddfSDavid du Colombier 	if(*(p-1) != '\n'){
11847dd7cddfSDavid du Colombier 		*p++ = '\n';
11857dd7cddfSDavid du Colombier 		*p = 0;
11867dd7cddfSDavid du Colombier 	}
11877dd7cddfSDavid du Colombier 
11887dd7cddfSDavid du Colombier 	return p - out;
11897dd7cddfSDavid du Colombier }
11907dd7cddfSDavid du Colombier 
11917dd7cddfSDavid du Colombier static char*
11927dd7cddfSDavid du Colombier lowercase(char *p)
11937dd7cddfSDavid du Colombier {
11947dd7cddfSDavid du Colombier 	char *op;
11957dd7cddfSDavid du Colombier 	int c;
11967dd7cddfSDavid du Colombier 
11977dd7cddfSDavid du Colombier 	for(op = p; c = *p; p++)
11987dd7cddfSDavid du Colombier 		if(isupper(c))
11997dd7cddfSDavid du Colombier 			*p = tolower(c);
12007dd7cddfSDavid du Colombier 	return op;
12017dd7cddfSDavid du Colombier }
12027dd7cddfSDavid du Colombier 
12037dd7cddfSDavid du Colombier // translate latin1 directly since it fits neatly in utf
1204816336a7SDavid du Colombier static int
1205816336a7SDavid du Colombier latin1toutf(char **out, char *in, char *e)
12067dd7cddfSDavid du Colombier {
1207816336a7SDavid du Colombier 	int n;
12087dd7cddfSDavid du Colombier 	char *p;
1209816336a7SDavid du Colombier 	Rune r;
12107dd7cddfSDavid du Colombier 
1211816336a7SDavid du Colombier 	n = 0;
1212816336a7SDavid du Colombier 	for(p = in; p < e; p++)
1213816336a7SDavid du Colombier 		if(*p & 0x80)
1214816336a7SDavid du Colombier 			n++;
1215816336a7SDavid du Colombier 	if(n == 0)
1216816336a7SDavid du Colombier 		return 0;
1217816336a7SDavid du Colombier 
1218816336a7SDavid du Colombier 	n += e-in;
1219816336a7SDavid du Colombier 	*out = p = malloc(n+1);
1220816336a7SDavid du Colombier 	if(p == nil)
1221816336a7SDavid du Colombier 		return 0;
1222816336a7SDavid du Colombier 
12237dd7cddfSDavid du Colombier 	for(; in < e; in++){
1224816336a7SDavid du Colombier 		r = (uchar)*in;
12257dd7cddfSDavid du Colombier 		p += runetochar(p, &r);
12267dd7cddfSDavid du Colombier 	}
12277dd7cddfSDavid du Colombier 	*p = 0;
1228816336a7SDavid du Colombier 	return p - *out;
12297dd7cddfSDavid du Colombier }
12307dd7cddfSDavid du Colombier 
1231816336a7SDavid du Colombier // translate any thing using the tcs program
12327dd7cddfSDavid du Colombier int
12337dd7cddfSDavid du Colombier xtoutf(char *charset, char **out, char *in, char *e)
12347dd7cddfSDavid du Colombier {
12357dd7cddfSDavid du Colombier 	char *av[4];
12367dd7cddfSDavid du Colombier 	int totcs[2];
12377dd7cddfSDavid du Colombier 	int fromtcs[2];
12387dd7cddfSDavid du Colombier 	int n, len, sofar;
12397dd7cddfSDavid du Colombier 	char *p;
12407dd7cddfSDavid du Colombier 
1241816336a7SDavid du Colombier 	// might not need to convert
1242816336a7SDavid du Colombier 	if(cistrcmp(charset, "us-ascii") == 0 || cistrcmp(charset, "utf-8") == 0)
1243816336a7SDavid du Colombier 		return 0;
1244816336a7SDavid du Colombier 	if(cistrcmp(charset, "iso-8859-1") == 0)
1245816336a7SDavid du Colombier 		return latin1toutf(out, in, e);
1246816336a7SDavid du Colombier 
12477dd7cddfSDavid du Colombier 	len = e-in+1;
12487dd7cddfSDavid du Colombier 	sofar = 0;
12497dd7cddfSDavid du Colombier 	*out = p = malloc(len+1);
12507dd7cddfSDavid du Colombier 	if(p == nil)
12517dd7cddfSDavid du Colombier 		return 0;
12527dd7cddfSDavid du Colombier 
12537dd7cddfSDavid du Colombier 	av[0] = charset;
12547dd7cddfSDavid du Colombier 	av[1] = "-f";
12557dd7cddfSDavid du Colombier 	av[2] = charset;
12567dd7cddfSDavid du Colombier 	av[3] = 0;
12577dd7cddfSDavid du Colombier 	if(pipe(totcs) < 0)
1258816336a7SDavid du Colombier 		goto error;
12597dd7cddfSDavid du Colombier 	if(pipe(fromtcs) < 0){
12607dd7cddfSDavid du Colombier 		close(totcs[0]); close(totcs[1]);
1261816336a7SDavid du Colombier 		goto error;
12627dd7cddfSDavid du Colombier 	}
12637dd7cddfSDavid du Colombier 	switch(rfork(RFPROC|RFFDG|RFNOWAIT)){
12647dd7cddfSDavid du Colombier 	case -1:
12657dd7cddfSDavid du Colombier 		close(fromtcs[0]); close(fromtcs[1]);
12667dd7cddfSDavid du Colombier 		close(totcs[0]); close(totcs[1]);
1267816336a7SDavid du Colombier 		goto error;
12687dd7cddfSDavid du Colombier 	case 0:
12697dd7cddfSDavid du Colombier 		close(fromtcs[0]); close(totcs[1]);
12707dd7cddfSDavid du Colombier 		dup(fromtcs[1], 1);
12717dd7cddfSDavid du Colombier 		dup(totcs[0], 0);
12727dd7cddfSDavid du Colombier 		close(fromtcs[1]); close(totcs[0]);
12739a747e4fSDavid du Colombier 		dup(open("/dev/null", OWRITE), 2);
12747dd7cddfSDavid du Colombier 		exec("/bin/tcs", av);
12757dd7cddfSDavid du Colombier 		_exits(0);
12767dd7cddfSDavid du Colombier 	default:
12777dd7cddfSDavid du Colombier 		close(fromtcs[1]); close(totcs[0]);
12787dd7cddfSDavid du Colombier 		switch(rfork(RFPROC|RFFDG|RFNOWAIT)){
12797dd7cddfSDavid du Colombier 		case -1:
12807dd7cddfSDavid du Colombier 			close(fromtcs[0]); close(totcs[1]);
1281816336a7SDavid du Colombier 			goto error;
12827dd7cddfSDavid du Colombier 		case 0:
12837dd7cddfSDavid du Colombier 			close(fromtcs[0]);
12847dd7cddfSDavid du Colombier 			while(in < e){
12857dd7cddfSDavid du Colombier 				n = write(totcs[1], in, e-in);
12867dd7cddfSDavid du Colombier 				if(n <= 0)
12877dd7cddfSDavid du Colombier 					break;
12887dd7cddfSDavid du Colombier 				in += n;
12897dd7cddfSDavid du Colombier 			}
12907dd7cddfSDavid du Colombier 			close(totcs[1]);
12917dd7cddfSDavid du Colombier 			_exits(0);
12927dd7cddfSDavid du Colombier 		default:
12937dd7cddfSDavid du Colombier 			close(totcs[1]);
12947dd7cddfSDavid du Colombier 			for(;;){
12957dd7cddfSDavid du Colombier 				n = read(fromtcs[0], &p[sofar], len-sofar);
12967dd7cddfSDavid du Colombier 				if(n <= 0)
12977dd7cddfSDavid du Colombier 					break;
12987dd7cddfSDavid du Colombier 				sofar += n;
12997dd7cddfSDavid du Colombier 				p[sofar] = 0;
13007dd7cddfSDavid du Colombier 				if(sofar == len){
13017dd7cddfSDavid du Colombier 					len += 1024;
1302816336a7SDavid du Colombier 					p = realloc(p, len+1);
13037dd7cddfSDavid du Colombier 					if(p == nil)
1304816336a7SDavid du Colombier 						goto error;
1305816336a7SDavid du Colombier 					*out = p;
13067dd7cddfSDavid du Colombier 				}
13077dd7cddfSDavid du Colombier 			}
13087dd7cddfSDavid du Colombier 			close(fromtcs[0]);
13097dd7cddfSDavid du Colombier 			break;
13107dd7cddfSDavid du Colombier 		}
13117dd7cddfSDavid du Colombier 		break;
13127dd7cddfSDavid du Colombier 	}
1313816336a7SDavid du Colombier 	if(sofar == 0)
1314816336a7SDavid du Colombier 		goto error;
13157dd7cddfSDavid du Colombier 	return sofar;
13167dd7cddfSDavid du Colombier 
1317816336a7SDavid du Colombier error:
1318816336a7SDavid du Colombier 	free(*out);
1319816336a7SDavid du Colombier 	*out = nil;
1320816336a7SDavid du Colombier 	return 0;
13217dd7cddfSDavid du Colombier }
13227dd7cddfSDavid du Colombier 
13237dd7cddfSDavid du Colombier void *
13247dd7cddfSDavid du Colombier emalloc(ulong n)
13257dd7cddfSDavid du Colombier {
13267dd7cddfSDavid du Colombier 	void *p;
13277dd7cddfSDavid du Colombier 
13287dd7cddfSDavid du Colombier 	p = mallocz(n, 1);
13297dd7cddfSDavid du Colombier 	if(!p){
133080ee5cbfSDavid du Colombier 		fprint(2, "%s: out of memory alloc %lud\n", argv0, n);
13317dd7cddfSDavid du Colombier 		exits("out of memory");
13327dd7cddfSDavid du Colombier 	}
133380ee5cbfSDavid du Colombier 	setmalloctag(p, getcallerpc(&n));
13347dd7cddfSDavid du Colombier 	return p;
13357dd7cddfSDavid du Colombier }
13367dd7cddfSDavid du Colombier 
13377dd7cddfSDavid du Colombier void *
13387dd7cddfSDavid du Colombier erealloc(void *p, ulong n)
13397dd7cddfSDavid du Colombier {
13403ff48bf5SDavid du Colombier 	if(n == 0)
13413ff48bf5SDavid du Colombier 		n = 1;
13427dd7cddfSDavid du Colombier 	p = realloc(p, n);
13437dd7cddfSDavid du Colombier 	if(!p){
134480ee5cbfSDavid du Colombier 		fprint(2, "%s: out of memory realloc %lud\n", argv0, n);
13457dd7cddfSDavid du Colombier 		exits("out of memory");
13467dd7cddfSDavid du Colombier 	}
134780ee5cbfSDavid du Colombier 	setrealloctag(p, getcallerpc(&p));
13487dd7cddfSDavid du Colombier 	return p;
13497dd7cddfSDavid du Colombier }
13507dd7cddfSDavid du Colombier 
13517dd7cddfSDavid du Colombier void
13527dd7cddfSDavid du Colombier mailplumb(Mailbox *mb, Message *m, int delete)
13537dd7cddfSDavid du Colombier {
13547dd7cddfSDavid du Colombier 	Plumbmsg p;
13557dd7cddfSDavid du Colombier 	Plumbattr a[7];
13567dd7cddfSDavid du Colombier 	char buf[256];
13577dd7cddfSDavid du Colombier 	int ai;
13587dd7cddfSDavid du Colombier 	char lenstr[10], *from, *subject, *date;
13597dd7cddfSDavid du Colombier 	static int fd = -1;
13607dd7cddfSDavid du Colombier 
13617dd7cddfSDavid du Colombier 	if(m->subject822 == nil)
13627dd7cddfSDavid du Colombier 		subject = "";
13637dd7cddfSDavid du Colombier 	else
13647dd7cddfSDavid du Colombier 		subject = s_to_c(m->subject822);
13657dd7cddfSDavid du Colombier 
13667dd7cddfSDavid du Colombier 	if(m->from822 != nil)
13677dd7cddfSDavid du Colombier 		from = s_to_c(m->from822);
13687dd7cddfSDavid du Colombier 	else if(m->unixfrom != nil)
13697dd7cddfSDavid du Colombier 		from = s_to_c(m->unixfrom);
13707dd7cddfSDavid du Colombier 	else
13717dd7cddfSDavid du Colombier 		from = "";
13727dd7cddfSDavid du Colombier 
13737dd7cddfSDavid du Colombier 	if(m->unixdate != nil)
13747dd7cddfSDavid du Colombier 		date = s_to_c(m->unixdate);
13757dd7cddfSDavid du Colombier 	else
13767dd7cddfSDavid du Colombier 		date = "";
13777dd7cddfSDavid du Colombier 
13787dd7cddfSDavid du Colombier 	sprint(lenstr, "%ld", m->end-m->start);
13797dd7cddfSDavid du Colombier 
13807dd7cddfSDavid du Colombier 	if(biffing && !delete)
13817dd7cddfSDavid du Colombier 		print("[ %s / %s / %s ]\n", from, subject, lenstr);
13827dd7cddfSDavid du Colombier 
13837dd7cddfSDavid du Colombier 	if(!plumbing)
13847dd7cddfSDavid du Colombier 		return;
13857dd7cddfSDavid du Colombier 
13867dd7cddfSDavid du Colombier 	if(fd < 0)
13877dd7cddfSDavid du Colombier 		fd = plumbopen("send", OWRITE);
13887dd7cddfSDavid du Colombier 	if(fd < 0)
13897dd7cddfSDavid du Colombier 		return;
13907dd7cddfSDavid du Colombier 
13917dd7cddfSDavid du Colombier 	p.src = "mailfs";
13927dd7cddfSDavid du Colombier 	p.dst = "seemail";
13937dd7cddfSDavid du Colombier 	p.wdir = "/mail/fs";
13947dd7cddfSDavid du Colombier 	p.type = "text";
13957dd7cddfSDavid du Colombier 
13967dd7cddfSDavid du Colombier 	ai = 0;
13977dd7cddfSDavid du Colombier 	a[ai].name = "filetype";
13987dd7cddfSDavid du Colombier 	a[ai].value = "mail";
13997dd7cddfSDavid du Colombier 
14007dd7cddfSDavid du Colombier 	a[++ai].name = "sender";
14017dd7cddfSDavid du Colombier 	a[ai].value = from;
14027dd7cddfSDavid du Colombier 	a[ai-1].next = &a[ai];
14037dd7cddfSDavid du Colombier 
14047dd7cddfSDavid du Colombier 	a[++ai].name = "length";
14057dd7cddfSDavid du Colombier 	a[ai].value = lenstr;
14067dd7cddfSDavid du Colombier 	a[ai-1].next = &a[ai];
14077dd7cddfSDavid du Colombier 
14087dd7cddfSDavid du Colombier 	a[++ai].name = "mailtype";
14097dd7cddfSDavid du Colombier 	a[ai].value = delete?"delete":"new";
14107dd7cddfSDavid du Colombier 	a[ai-1].next = &a[ai];
14117dd7cddfSDavid du Colombier 
14127dd7cddfSDavid du Colombier 	a[++ai].name = "date";
14137dd7cddfSDavid du Colombier 	a[ai].value = date;
14147dd7cddfSDavid du Colombier 	a[ai-1].next = &a[ai];
14157dd7cddfSDavid du Colombier 
14169a747e4fSDavid du Colombier 	if(m->sdigest){
14177dd7cddfSDavid du Colombier 		a[++ai].name = "digest";
14187dd7cddfSDavid du Colombier 		a[ai].value = s_to_c(m->sdigest);
14197dd7cddfSDavid du Colombier 		a[ai-1].next = &a[ai];
14209a747e4fSDavid du Colombier 	}
14217dd7cddfSDavid du Colombier 
14227dd7cddfSDavid du Colombier 	a[ai].next = nil;
14237dd7cddfSDavid du Colombier 
14247dd7cddfSDavid du Colombier 	p.attr = a;
14257dd7cddfSDavid du Colombier 	snprint(buf, sizeof(buf), "%s/%s/%s",
14267dd7cddfSDavid du Colombier 		mntpt, mb->name, m->name);
14277dd7cddfSDavid du Colombier 	p.ndata = strlen(buf);
14287dd7cddfSDavid du Colombier 	p.data = buf;
14297dd7cddfSDavid du Colombier 
14307dd7cddfSDavid du Colombier 	plumbsend(fd, &p);
14317dd7cddfSDavid du Colombier }
14327dd7cddfSDavid du Colombier 
14337dd7cddfSDavid du Colombier //
14347dd7cddfSDavid du Colombier // count the number of lines in the body (for imap4)
14357dd7cddfSDavid du Colombier //
14367dd7cddfSDavid du Colombier void
14377dd7cddfSDavid du Colombier countlines(Message *m)
14387dd7cddfSDavid du Colombier {
14397dd7cddfSDavid du Colombier 	int i;
14407dd7cddfSDavid du Colombier 	char *p;
14417dd7cddfSDavid du Colombier 
14427dd7cddfSDavid du Colombier 	i = 0;
14437dd7cddfSDavid du Colombier 	for(p = strchr(m->rbody, '\n'); p != nil && p < m->rbend; p = strchr(p+1, '\n'))
14447dd7cddfSDavid du Colombier 		i++;
14457dd7cddfSDavid du Colombier 	sprint(m->lines, "%d", i);
14467dd7cddfSDavid du Colombier }
144759cc4ca5SDavid du Colombier 
144859cc4ca5SDavid du Colombier char *LOG = "fs";
144959cc4ca5SDavid du Colombier 
145059cc4ca5SDavid du Colombier void
145159cc4ca5SDavid du Colombier logmsg(char *s, Message *m)
145259cc4ca5SDavid du Colombier {
145359cc4ca5SDavid du Colombier 	int pid;
145459cc4ca5SDavid du Colombier 
145559cc4ca5SDavid du Colombier 	if(!logging)
145659cc4ca5SDavid du Colombier 		return;
145759cc4ca5SDavid du Colombier 	pid = getpid();
145859cc4ca5SDavid du Colombier 	if(m == nil)
145959cc4ca5SDavid du Colombier 		syslog(0, LOG, "%s.%d: %s", user, pid, s);
146059cc4ca5SDavid du Colombier 	else
146159cc4ca5SDavid du Colombier 		syslog(0, LOG, "%s.%d: %s msg from %s digest %s",
146259cc4ca5SDavid du Colombier 			user, pid, s,
146359cc4ca5SDavid du Colombier 			m->from822 ? s_to_c(m->from822) : "?",
146459cc4ca5SDavid du Colombier 			s_to_c(m->sdigest));
146559cc4ca5SDavid du Colombier }
146680ee5cbfSDavid du Colombier 
14673ff48bf5SDavid du Colombier /*
14683ff48bf5SDavid du Colombier  *  squeeze nulls out of the body
14693ff48bf5SDavid du Colombier  */
14703ff48bf5SDavid du Colombier static void
14713ff48bf5SDavid du Colombier nullsqueeze(Message *m)
14723ff48bf5SDavid du Colombier {
14733ff48bf5SDavid du Colombier 	char *p, *q;
14743ff48bf5SDavid du Colombier 
14757a02f3c0SDavid du Colombier 	q = memchr(m->body, 0, m->end-m->body);
14763ff48bf5SDavid du Colombier 	if(q == nil)
14777a02f3c0SDavid du Colombier 		return;
14787a02f3c0SDavid du Colombier 
14797a02f3c0SDavid du Colombier 	for(p = m->body; q < m->end; q++){
14807a02f3c0SDavid du Colombier 		if(*q == 0)
14817a02f3c0SDavid du Colombier 			continue;
14827a02f3c0SDavid du Colombier 		*p++ = *q;
14833ff48bf5SDavid du Colombier 	}
14847a02f3c0SDavid du Colombier 	m->bend = m->rbend = m->end = p;
14853ff48bf5SDavid du Colombier }
14863ff48bf5SDavid du Colombier 
14873ff48bf5SDavid du Colombier 
148880ee5cbfSDavid du Colombier //
148980ee5cbfSDavid du Colombier // convert an RFC822 date into a Unix style date
149080ee5cbfSDavid du Colombier // for when the Unix From line isn't there (e.g. POP3).
149180ee5cbfSDavid du Colombier // enough client programs depend on having a Unix date
149280ee5cbfSDavid du Colombier // that it's easiest to write this conversion code once, right here.
149380ee5cbfSDavid du Colombier //
149480ee5cbfSDavid du Colombier // people don't follow RFC822 particularly closely,
149580ee5cbfSDavid du Colombier // so we use strtotm, which is a bunch of heuristics.
149680ee5cbfSDavid du Colombier //
149780ee5cbfSDavid du Colombier 
149880ee5cbfSDavid du Colombier extern int strtotm(char*, Tm*);
149980ee5cbfSDavid du Colombier String*
150080ee5cbfSDavid du Colombier date822tounix(char *s)
150180ee5cbfSDavid du Colombier {
150280ee5cbfSDavid du Colombier 	char *p, *q;
150380ee5cbfSDavid du Colombier 	Tm tm;
150480ee5cbfSDavid du Colombier 
150580ee5cbfSDavid du Colombier 	if(strtotm(s, &tm) < 0)
150680ee5cbfSDavid du Colombier 		return nil;
150780ee5cbfSDavid du Colombier 
150880ee5cbfSDavid du Colombier 	p = asctime(&tm);
150980ee5cbfSDavid du Colombier 	if(q = strchr(p, '\n'))
151080ee5cbfSDavid du Colombier 		*q = '\0';
151180ee5cbfSDavid du Colombier 	return s_copy(p);
151280ee5cbfSDavid du Colombier }
151380ee5cbfSDavid du Colombier 
1514