plan9port

fork of plan9port with libvec, libstr and libsdb
Log | Files | Refs | README | LICENSE

mbox.c (29364B)


      1 #include "common.h"
      2 #include <ctype.h>
      3 #include <plumb.h>
      4 #include <libsec.h>
      5 #include <thread.h>
      6 #include "dat.h"
      7 
      8 extern char* dirtab[]; /* jpc */
      9 
     10 typedef struct Header Header;
     11 
     12 struct Header {
     13 	char *type;
     14 	void (*f)(Message*, Header*, char*);
     15 	int len;
     16 };
     17 
     18 /* headers */
     19 static	void	ctype(Message*, Header*, char*);
     20 static	void	cencoding(Message*, Header*, char*);
     21 static	void	cdisposition(Message*, Header*, char*);
     22 static	void	date822(Message*, Header*, char*);
     23 static	void	from822(Message*, Header*, char*);
     24 static	void	to822(Message*, Header*, char*);
     25 static	void	sender822(Message*, Header*, char*);
     26 static	void	replyto822(Message*, Header*, char*);
     27 static	void	subject822(Message*, Header*, char*);
     28 static	void	inreplyto822(Message*, Header*, char*);
     29 static	void	cc822(Message*, Header*, char*);
     30 static	void	bcc822(Message*, Header*, char*);
     31 static	void	messageid822(Message*, Header*, char*);
     32 static	void	mimeversion(Message*, Header*, char*);
     33 static	void	nullsqueeze(Message*);
     34 enum
     35 {
     36 	Mhead=	11,	/* offset of first mime header */
     37 };
     38 
     39 Header head[] =
     40 {
     41 	{ "date:", date822, },
     42 	{ "from:", from822, },
     43 	{ "to:", to822, },
     44 	{ "sender:", sender822, },
     45 	{ "reply-to:", replyto822, },
     46 	{ "subject:", subject822, },
     47 	{ "cc:", cc822, },
     48 	{ "bcc:", bcc822, },
     49 	{ "in-reply-to:", inreplyto822, },
     50 	{ "mime-version:", mimeversion, },
     51 	{ "message-id:", messageid822, },
     52 
     53 [Mhead]	{ "content-type:", ctype, },
     54 	{ "content-transfer-encoding:", cencoding, },
     55 	{ "content-disposition:", cdisposition, },
     56 	{ 0, }
     57 };
     58 
     59 /* static	void	fatal(char *fmt, ...); jpc */
     60 static	void	initquoted(void);
     61 /* static	void	startheader(Message*);
     62 static	void	startbody(Message*); jpc */
     63 static	char*	skipwhite(char*);
     64 static	char*	skiptosemi(char*);
     65 static	char*	getstring(char*, String*, int);
     66 static	void	setfilename(Message*, char*);
     67 /* static	char*	lowercase(char*); jpc */
     68 static	int	is8bit(Message*);
     69 static	int	headerline(char**, String*);
     70 static	void	initheaders(void);
     71 static void	parseattachments(Message*, Mailbox*);
     72 
     73 int		debug;
     74 
     75 char *Enotme = "path not served by this file server";
     76 
     77 enum
     78 {
     79 	Chunksize = 1024
     80 };
     81 
     82 Mailboxinit *boxinit[] = {
     83 	imap4mbox,
     84 	pop3mbox,
     85 	plan9mbox
     86 };
     87 
     88 char*
     89 syncmbox(Mailbox *mb, int doplumb)
     90 {
     91 	return (*mb->sync)(mb, doplumb);
     92 }
     93 
     94 /* create a new mailbox */
     95 char*
     96 newmbox(char *path, char *name, int std)
     97 {
     98 	Mailbox *mb, **l;
     99 	char *p, *rv;
    100 	int i;
    101 
    102 	initheaders();
    103 
    104 	mb = emalloc(sizeof(*mb));
    105 	strncpy(mb->path, path, sizeof(mb->path)-1);
    106 	if(name == nil){
    107 		p = strrchr(path, '/');
    108 		if(p == nil)
    109 			p = path;
    110 		else
    111 			p++;
    112 		if(*p == 0){
    113 			free(mb);
    114 			return "bad mbox name";
    115 		}
    116 		strncpy(mb->name, p, sizeof(mb->name)-1);
    117 	} else {
    118 		strncpy(mb->name, name, sizeof(mb->name)-1);
    119 	}
    120 
    121 	rv = nil;
    122 	/* check for a mailbox type */
    123 	for(i=0; i<nelem(boxinit); i++)
    124 		if((rv = (*boxinit[i])(mb, path)) != Enotme)
    125 			break;
    126 	if(i == nelem(boxinit)){
    127 		free(mb);
    128 		return "bad path";
    129 	}
    130 
    131 	/* on error, give up */
    132 	if(rv){
    133 		free(mb);
    134 		return rv;
    135 	}
    136 
    137 	/* make sure name isn't taken */
    138 	qlock(&mbllock);
    139 	for(l = &mbl; *l != nil; l = &(*l)->next){
    140 		if(strcmp((*l)->name, mb->name) == 0){
    141 			if(strcmp(path, (*l)->path) == 0)
    142 				rv = nil;
    143 			else
    144 				rv = "mbox name in use";
    145 			if(mb->close)
    146 				(*mb->close)(mb);
    147 			free(mb);
    148 			qunlock(&mbllock);
    149 			return rv;
    150 		}
    151 	}
    152 
    153 	/* always try locking */
    154 	mb->dolock = 1;
    155 
    156 	mb->refs = 1;
    157 	mb->next = nil;
    158 	mb->id = newid();
    159 	mb->root = newmessage(nil);
    160 	mb->std = std;
    161 	*l = mb;
    162 	qunlock(&mbllock);
    163 
    164 	qlock(&mb->ql);
    165 	if(mb->ctl){
    166 		henter(PATH(mb->id, Qmbox), "ctl",
    167 			(Qid){PATH(mb->id, Qmboxctl), 0, QTFILE}, nil, mb);
    168 	}
    169 	rv = syncmbox(mb, 0);
    170 	qunlock(&mb->ql);
    171 
    172 	return rv;
    173 }
    174 
    175 /* close the named mailbox */
    176 void
    177 freembox(char *name)
    178 {
    179 	Mailbox **l, *mb;
    180 
    181 	qlock(&mbllock);
    182 	for(l=&mbl; *l != nil; l=&(*l)->next){
    183 		if(strcmp(name, (*l)->name) == 0){
    184 			mb = *l;
    185 			*l = mb->next;
    186 			mboxdecref(mb);
    187 			break;
    188 		}
    189 	}
    190 	hfree(PATH(0, Qtop), name);
    191 	qunlock(&mbllock);
    192 }
    193 
    194 static void
    195 initheaders(void)
    196 {
    197 	Header *h;
    198 	static int already;
    199 
    200 	if(already)
    201 		return;
    202 	already = 1;
    203 
    204 	for(h = head; h->type != nil; h++)
    205 		h->len = strlen(h->type);
    206 }
    207 
    208 /*
    209  *  parse a Unix style header
    210  */
    211 void
    212 parseunix(Message *m)
    213 {
    214 	char *p;
    215 	String *h;
    216 
    217 	h = s_new();
    218 	for(p = m->start + 5; *p && *p != '\r' && *p != '\n'; p++)
    219 		s_putc(h, *p);
    220 	s_terminate(h);
    221 	s_restart(h);
    222 
    223 	m->unixfrom = s_parse(h, s_reset(m->unixfrom));
    224 	m->unixdate = s_append(s_reset(m->unixdate), h->ptr);
    225 
    226 	s_free(h);
    227 }
    228 
    229 /*
    230  *  parse a message
    231  */
    232 void
    233 parseheaders(Message *m, int justmime, Mailbox *mb, int addfrom)
    234 {
    235 	String *hl;
    236 	Header *h;
    237 	char *p, *q;
    238 	int i;
    239 
    240 	if(m->whole == m->whole->whole){
    241 		henter(PATH(mb->id, Qmbox), m->name,
    242 			(Qid){PATH(m->id, Qdir), 0, QTDIR}, m, mb);
    243 	} else {
    244 		henter(PATH(m->whole->id, Qdir), m->name,
    245 			(Qid){PATH(m->id, Qdir), 0, QTDIR}, m, mb);
    246 	}
    247 	for(i = 0; i < Qmax; i++)
    248 		henter(PATH(m->id, Qdir), dirtab[i],
    249 			(Qid){PATH(m->id, i), 0, QTFILE}, m, mb);
    250 
    251 	/* parse mime headers */
    252 	p = m->header;
    253 	hl = s_new();
    254 	while(headerline(&p, hl)){
    255 		if(justmime)
    256 			h = &head[Mhead];
    257 		else
    258 			h = head;
    259 		for(; h->type; h++){
    260 			if(cistrncmp(s_to_c(hl), h->type, h->len) == 0){
    261 				(*h->f)(m, h, s_to_c(hl));
    262 				break;
    263 			}
    264 		}
    265 		s_reset(hl);
    266 	}
    267 	s_free(hl);
    268 
    269 	/* the blank line isn't really part of the body or header */
    270 	if(justmime){
    271 		m->mhend = p;
    272 		m->hend = m->header;
    273 	} else {
    274 		m->hend = p;
    275 	}
    276 	if(*p == '\n')
    277 		p++;
    278 	m->rbody = m->body = p;
    279 
    280 	/* if type is text, get any nulls out of the body.  This is */
    281 	/* for the two seans and imap clients that get confused. */
    282 	if(strncmp(s_to_c(m->type), "text/", 5) == 0)
    283 		nullsqueeze(m);
    284 
    285 	/* */
    286 	/* cobble together Unix-style from line */
    287 	/* for local mailbox messages, we end up recreating the */
    288 	/* original header. */
    289 	/* for pop3 messages, the best we can do is  */
    290 	/* use the From: information and the RFC822 date. */
    291 	/* */
    292 	if(m->unixdate == nil || strcmp(s_to_c(m->unixdate), "???") == 0
    293 	|| strcmp(s_to_c(m->unixdate), "Thu Jan 1 00:00:00 GMT 1970") == 0){
    294 		if(m->unixdate){
    295 			s_free(m->unixdate);
    296 			m->unixdate = nil;
    297 		}
    298 		/* look for the date in the first Received: line. */
    299 		/* it's likely to be the right time zone (it's */
    300 	 	/* the local system) and in a convenient format. */
    301 		if(cistrncmp(m->header, "received:", 9)==0){
    302 			if((q = strchr(m->header, ';')) != nil){
    303 				p = q;
    304 				while((p = strchr(p, '\n')) != nil){
    305 					if(p[1] != ' ' && p[1] != '\t' && p[1] != '\n')
    306 						break;
    307 					p++;
    308 				}
    309 				if(p){
    310 					*p = '\0';
    311 					m->unixdate = date822tounix(q+1);
    312 					*p = '\n';
    313 				}
    314 			}
    315 		}
    316 
    317 		/* fall back on the rfc822 date	 */
    318 		if(m->unixdate==nil && m->date822)
    319 			m->unixdate = date822tounix(s_to_c(m->date822));
    320 	}
    321 
    322 	if(m->unixheader != nil)
    323 		s_free(m->unixheader);
    324 
    325 	/* only fake header for top-level messages for pop3 and imap4 */
    326 	/* clients (those protocols don't include the unix header). */
    327 	/* adding the unix header all the time screws up mime-attached */
    328 	/* rfc822 messages. */
    329 	if(!addfrom && !m->unixfrom){
    330 		m->unixheader = nil;
    331 		return;
    332 	}
    333 
    334 	m->unixheader = s_copy("From ");
    335 	if(m->unixfrom && strcmp(s_to_c(m->unixfrom), "???") != 0)
    336 		s_append(m->unixheader, s_to_c(m->unixfrom));
    337 	else if(m->from822)
    338 		s_append(m->unixheader, s_to_c(m->from822));
    339 	else
    340 		s_append(m->unixheader, "???");
    341 
    342 	s_append(m->unixheader, " ");
    343 	if(m->unixdate)
    344 		s_append(m->unixheader, s_to_c(m->unixdate));
    345 	else
    346 		s_append(m->unixheader, "Thu Jan  1 00:00:00 GMT 1970");
    347 
    348 	s_append(m->unixheader, "\n");
    349 }
    350 
    351 String*
    352 promote(String **sp)
    353 {
    354 	String *s;
    355 
    356 	if(*sp != nil)
    357 		s = s_clone(*sp);
    358 	else
    359 		s = nil;
    360 	return s;
    361 }
    362 
    363 void
    364 parsebody(Message *m, Mailbox *mb)
    365 {
    366 	Message *nm;
    367 
    368 	/* recurse */
    369 	if(strncmp(s_to_c(m->type), "multipart/", 10) == 0){
    370 		parseattachments(m, mb);
    371 	} else if(strcmp(s_to_c(m->type), "message/rfc822") == 0){
    372 		decode(m);
    373 		parseattachments(m, mb);
    374 		nm = m->part;
    375 
    376 		/* promote headers */
    377 		if(m->replyto822 == nil && m->from822 == nil && m->sender822 == nil){
    378 			m->from822 = promote(&nm->from822);
    379 			m->to822 = promote(&nm->to822);
    380 			m->date822 = promote(&nm->date822);
    381 			m->sender822 = promote(&nm->sender822);
    382 			m->replyto822 = promote(&nm->replyto822);
    383 			m->subject822 = promote(&nm->subject822);
    384 			m->unixdate = promote(&nm->unixdate);
    385 		}
    386 	}
    387 }
    388 
    389 void
    390 parse(Message *m, int justmime, Mailbox *mb, int addfrom)
    391 {
    392 	parseheaders(m, justmime, mb, addfrom);
    393 	parsebody(m, mb);
    394 }
    395 
    396 static void
    397 parseattachments(Message *m, Mailbox *mb)
    398 {
    399 	Message *nm, **l;
    400 	char *p, *x;
    401 
    402 	/* if there's a boundary, recurse... */
    403 	if(m->boundary != nil){
    404 		p = m->body;
    405 		nm = nil;
    406 		l = &m->part;
    407 		for(;;){
    408 			x = strstr(p, s_to_c(m->boundary));
    409 
    410 			/* no boundary, we're done */
    411 			if(x == nil){
    412 				if(nm != nil)
    413 					nm->rbend = nm->bend = nm->end = m->bend;
    414 				break;
    415 			}
    416 
    417 			/* boundary must be at the start of a line */
    418 			if(x != m->body && *(x-1) != '\n'){
    419 				p = x+1;
    420 				continue;
    421 			}
    422 
    423 			if(nm != nil)
    424 				nm->rbend = nm->bend = nm->end = x;
    425 			x += strlen(s_to_c(m->boundary));
    426 
    427 			/* is this the last part? ignore anything after it */
    428 			if(strncmp(x, "--", 2) == 0)
    429 				break;
    430 
    431 			p = strchr(x, '\n');
    432 			if(p == nil)
    433 				break;
    434 			nm = newmessage(m);
    435 			nm->start = nm->header = nm->body = nm->rbody = ++p;
    436 			nm->mheader = nm->header;
    437 			*l = nm;
    438 			l = &nm->next;
    439 		}
    440 		for(nm = m->part; nm != nil; nm = nm->next)
    441 			parse(nm, 1, mb, 0);
    442 		return;
    443 	}
    444 
    445 	/* if we've got an rfc822 message, recurse... */
    446 	if(strcmp(s_to_c(m->type), "message/rfc822") == 0){
    447 		nm = newmessage(m);
    448 		m->part = nm;
    449 		nm->start = nm->header = nm->body = nm->rbody = m->body;
    450 		nm->end = nm->bend = nm->rbend = m->bend;
    451 		parse(nm, 0, mb, 0);
    452 	}
    453 }
    454 
    455 /*
    456  *  pick up a header line
    457  */
    458 static int
    459 headerline(char **pp, String *hl)
    460 {
    461 	char *p, *x;
    462 
    463 	s_reset(hl);
    464 	p = *pp;
    465 	x = strpbrk(p, ":\n");
    466 	if(x == nil || *x == '\n')
    467 		return 0;
    468 	for(;;){
    469 		x = strchr(p, '\n');
    470 		if(x == nil)
    471 			x = p + strlen(p);
    472 		s_nappend(hl, p, x-p);
    473 		p = x;
    474 		if(*p != '\n' || *++p != ' ' && *p != '\t')
    475 			break;
    476 		while(*p == ' ' || *p == '\t')
    477 			p++;
    478 		s_putc(hl, ' ');
    479 	}
    480 	*pp = p;
    481 	return 1;
    482 }
    483 
    484 static String*
    485 addr822(char *p)
    486 {
    487 	String *s, *list;
    488 	int incomment, addrdone, inanticomment, quoted;
    489 	int n;
    490 	int c;
    491 
    492 	list = s_new();
    493 	s = s_new();
    494 	quoted = incomment = addrdone = inanticomment = 0;
    495 	n = 0;
    496 	for(; *p; p++){
    497 		c = *p;
    498 
    499 		/* whitespace is ignored */
    500 		if(!quoted && isspace(c) || c == '\r')
    501 			continue;
    502 
    503 		/* strings are always treated as atoms */
    504 		if(!quoted && c == '"'){
    505 			if(!addrdone && !incomment)
    506 				s_putc(s, c);
    507 			for(p++; *p; p++){
    508 				if(!addrdone && !incomment)
    509 					s_putc(s, *p);
    510 				if(!quoted && *p == '"')
    511 					break;
    512 				if(*p == '\\')
    513 					quoted = 1;
    514 				else
    515 					quoted = 0;
    516 			}
    517 			if(*p == 0)
    518 				break;
    519 			quoted = 0;
    520 			continue;
    521 		}
    522 
    523 		/* ignore everything in an expicit comment */
    524 		if(!quoted && c == '('){
    525 			incomment = 1;
    526 			continue;
    527 		}
    528 		if(incomment){
    529 			if(!quoted && c == ')')
    530 				incomment = 0;
    531 			quoted = 0;
    532 			continue;
    533 		}
    534 
    535 		/* anticomments makes everything outside of them comments */
    536 		if(!quoted && c == '<' && !inanticomment){
    537 			inanticomment = 1;
    538 			s = s_reset(s);
    539 			continue;
    540 		}
    541 		if(!quoted && c == '>' && inanticomment){
    542 			addrdone = 1;
    543 			inanticomment = 0;
    544 			continue;
    545 		}
    546 
    547 		/* commas separate addresses */
    548 		if(!quoted && c == ',' && !inanticomment){
    549 			s_terminate(s);
    550 			addrdone = 0;
    551 			if(n++ != 0)
    552 				s_append(list, " ");
    553 			s_append(list, s_to_c(s));
    554 			s = s_reset(s);
    555 			continue;
    556 		}
    557 
    558 		/* what's left is part of the address */
    559 		s_putc(s, c);
    560 
    561 		/* quoted characters are recognized only as characters */
    562 		if(c == '\\')
    563 			quoted = 1;
    564 		else
    565 			quoted = 0;
    566 
    567 	}
    568 
    569 	if(*s_to_c(s) != 0){
    570 		s_terminate(s);
    571 		if(n++ != 0)
    572 			s_append(list, " ");
    573 		s_append(list, s_to_c(s));
    574 	}
    575 	s_free(s);
    576 
    577 	if(n == 0){
    578 		s_free(list);
    579 		return nil;
    580 	}
    581 	return list;
    582 }
    583 
    584 static void
    585 to822(Message *m, Header *h, char *p)
    586 {
    587 	p += strlen(h->type);
    588 	s_free(m->to822);
    589 	m->to822 = addr822(p);
    590 }
    591 
    592 static void
    593 cc822(Message *m, Header *h, char *p)
    594 {
    595 	p += strlen(h->type);
    596 	s_free(m->cc822);
    597 	m->cc822 = addr822(p);
    598 }
    599 
    600 static void
    601 bcc822(Message *m, Header *h, char *p)
    602 {
    603 	p += strlen(h->type);
    604 	s_free(m->bcc822);
    605 	m->bcc822 = addr822(p);
    606 }
    607 
    608 static void
    609 from822(Message *m, Header *h, char *p)
    610 {
    611 	p += strlen(h->type);
    612 	s_free(m->from822);
    613 	m->from822 = addr822(p);
    614 }
    615 
    616 static void
    617 sender822(Message *m, Header *h, char *p)
    618 {
    619 	p += strlen(h->type);
    620 	s_free(m->sender822);
    621 	m->sender822 = addr822(p);
    622 }
    623 
    624 static void
    625 replyto822(Message *m, Header *h, char *p)
    626 {
    627 	p += strlen(h->type);
    628 	s_free(m->replyto822);
    629 	m->replyto822 = addr822(p);
    630 }
    631 
    632 static void
    633 mimeversion(Message *m, Header *h, char *p)
    634 {
    635 	p += strlen(h->type);
    636 	s_free(m->mimeversion);
    637 	m->mimeversion = addr822(p);
    638 }
    639 
    640 static void
    641 killtrailingwhite(char *p)
    642 {
    643 	char *e;
    644 
    645 	e = p + strlen(p) - 1;
    646 	while(e > p && isspace(*e))
    647 		*e-- = 0;
    648 }
    649 
    650 static void
    651 date822(Message *m, Header *h, char *p)
    652 {
    653 	p += strlen(h->type);
    654 	p = skipwhite(p);
    655 	s_free(m->date822);
    656 	m->date822 = s_copy(p);
    657 	p = s_to_c(m->date822);
    658 	killtrailingwhite(p);
    659 }
    660 
    661 static void
    662 subject822(Message *m, Header *h, char *p)
    663 {
    664 	p += strlen(h->type);
    665 	p = skipwhite(p);
    666 	s_free(m->subject822);
    667 	m->subject822 = s_copy(p);
    668 	p = s_to_c(m->subject822);
    669 	killtrailingwhite(p);
    670 }
    671 
    672 static void
    673 inreplyto822(Message *m, Header *h, char *p)
    674 {
    675 	p += strlen(h->type);
    676 	p = skipwhite(p);
    677 	s_free(m->inreplyto822);
    678 	m->inreplyto822 = s_copy(p);
    679 	p = s_to_c(m->inreplyto822);
    680 	killtrailingwhite(p);
    681 }
    682 
    683 static void
    684 messageid822(Message *m, Header *h, char *p)
    685 {
    686 	p += strlen(h->type);
    687 	p = skipwhite(p);
    688 	s_free(m->messageid822);
    689 	m->messageid822 = s_copy(p);
    690 	p = s_to_c(m->messageid822);
    691 	killtrailingwhite(p);
    692 }
    693 
    694 static int
    695 isattribute(char **pp, char *attr)
    696 {
    697 	char *p;
    698 	int n;
    699 
    700 	n = strlen(attr);
    701 	p = *pp;
    702 	if(cistrncmp(p, attr, n) != 0)
    703 		return 0;
    704 	p += n;
    705 	while(*p == ' ')
    706 		p++;
    707 	if(*p++ != '=')
    708 		return 0;
    709 	while(*p == ' ')
    710 		p++;
    711 	*pp = p;
    712 	return 1;
    713 }
    714 
    715 static void
    716 ctype(Message *m, Header *h, char *p)
    717 {
    718 	String *s;
    719 
    720 	p += h->len;
    721 	p = skipwhite(p);
    722 
    723 	p = getstring(p, m->type, 1);
    724 
    725 	while(*p){
    726 		if(isattribute(&p, "boundary")){
    727 			s = s_new();
    728 			p = getstring(p, s, 0);
    729 			m->boundary = s_reset(m->boundary);
    730 			s_append(m->boundary, "--");
    731 			s_append(m->boundary, s_to_c(s));
    732 			s_free(s);
    733 		} else if(cistrncmp(p, "multipart", 9) == 0){
    734 			/*
    735 			 *  the first unbounded part of a multipart message,
    736 			 *  the preamble, is not displayed or saved
    737 			 */
    738 		} else if(isattribute(&p, "name")){
    739 			if(m->filename == nil)
    740 				setfilename(m, p);
    741 		} else if(isattribute(&p, "charset")){
    742 			p = getstring(p, s_reset(m->charset), 0);
    743 		}
    744 
    745 		p = skiptosemi(p);
    746 	}
    747 }
    748 
    749 static void
    750 cencoding(Message *m, Header *h, char *p)
    751 {
    752 	p += h->len;
    753 	p = skipwhite(p);
    754 	if(cistrncmp(p, "base64", 6) == 0)
    755 		m->encoding = Ebase64;
    756 	else if(cistrncmp(p, "quoted-printable", 16) == 0)
    757 		m->encoding = Equoted;
    758 }
    759 
    760 static void
    761 cdisposition(Message *m, Header *h, char *p)
    762 {
    763 	p += h->len;
    764 	p = skipwhite(p);
    765 	while(*p){
    766 		if(cistrncmp(p, "inline", 6) == 0){
    767 			m->disposition = Dinline;
    768 		} else if(cistrncmp(p, "attachment", 10) == 0){
    769 			m->disposition = Dfile;
    770 		} else if(cistrncmp(p, "filename=", 9) == 0){
    771 			p += 9;
    772 			setfilename(m, p);
    773 		}
    774 		p = skiptosemi(p);
    775 	}
    776 
    777 }
    778 
    779 ulong msgallocd, msgfreed;
    780 
    781 Message*
    782 newmessage(Message *parent)
    783 {
    784 	/* static int id; jpc */
    785 	Message *m;
    786 
    787 	msgallocd++;
    788 
    789 	m = emalloc(sizeof(*m));
    790 	memset(m, 0, sizeof(*m));
    791 	m->disposition = Dnone;
    792 	m->type = s_copy("text/plain");
    793 	m->charset = s_copy("iso-8859-1");
    794 	m->id = newid();
    795 	if(parent)
    796 		sprint(m->name, "%d", ++(parent->subname));
    797 	if(parent == nil)
    798 		parent = m;
    799 	m->whole = parent;
    800 	m->hlen = -1;
    801 	return m;
    802 }
    803 
    804 /* delete a message from a mailbox */
    805 void
    806 delmessage(Mailbox *mb, Message *m)
    807 {
    808 	Message **l;
    809 	int i;
    810 
    811 	mb->vers++;
    812 	msgfreed++;
    813 
    814 	if(m->whole != m){
    815 		/* unchain from parent */
    816 		for(l = &m->whole->part; *l && *l != m; l = &(*l)->next)
    817 			;
    818 		if(*l != nil)
    819 			*l = m->next;
    820 
    821 		/* clear out of name lookup hash table */
    822 		if(m->whole->whole == m->whole)
    823 			hfree(PATH(mb->id, Qmbox), m->name);
    824 		else
    825 			hfree(PATH(m->whole->id, Qdir), m->name);
    826 		for(i = 0; i < Qmax; i++)
    827 			hfree(PATH(m->id, Qdir), dirtab[i]);
    828 	}
    829 
    830 	/* recurse through sub-parts */
    831 	while(m->part)
    832 		delmessage(mb, m->part);
    833 
    834 	/* free memory */
    835 	if(m->mallocd)
    836 		free(m->start);
    837 	if(m->hallocd)
    838 		free(m->header);
    839 	if(m->ballocd)
    840 		free(m->body);
    841 	s_free(m->unixfrom);
    842 	s_free(m->unixdate);
    843 	s_free(m->unixheader);
    844 	s_free(m->from822);
    845 	s_free(m->sender822);
    846 	s_free(m->to822);
    847 	s_free(m->bcc822);
    848 	s_free(m->cc822);
    849 	s_free(m->replyto822);
    850 	s_free(m->date822);
    851 	s_free(m->inreplyto822);
    852 	s_free(m->subject822);
    853 	s_free(m->messageid822);
    854 	s_free(m->addrs);
    855 	s_free(m->mimeversion);
    856 	s_free(m->sdigest);
    857 	s_free(m->boundary);
    858 	s_free(m->type);
    859 	s_free(m->charset);
    860 	s_free(m->filename);
    861 
    862 	free(m);
    863 }
    864 
    865 /* mark messages (identified by path) for deletion */
    866 void
    867 delmessages(int ac, char **av)
    868 {
    869 	Mailbox *mb;
    870 	Message *m;
    871 	int i, needwrite;
    872 
    873 	qlock(&mbllock);
    874 	for(mb = mbl; mb != nil; mb = mb->next)
    875 		if(strcmp(av[0], mb->name) == 0){
    876 			qlock(&mb->ql);
    877 			break;
    878 		}
    879 	qunlock(&mbllock);
    880 	if(mb == nil)
    881 		return;
    882 
    883 	needwrite = 0;
    884 	for(i = 1; i < ac; i++){
    885 		for(m = mb->root->part; m != nil; m = m->next)
    886 			if(strcmp(m->name, av[i]) == 0){
    887 				if(!m->deleted){
    888 					mailplumb(mb, m, 1);
    889 					needwrite = 1;
    890 					m->deleted = 1;
    891 					logmsg("deleting", m);
    892 				}
    893 				break;
    894 			}
    895 	}
    896 	if(needwrite)
    897 		syncmbox(mb, 1);
    898 	qunlock(&mb->ql);
    899 }
    900 
    901 /*
    902  *  the following are called with the mailbox qlocked
    903  */
    904 void
    905 msgincref(Message *m)
    906 {
    907 	m->refs++;
    908 }
    909 void
    910 msgdecref(Mailbox *mb, Message *m)
    911 {
    912 	m->refs--;
    913 	if(m->refs == 0 && m->deleted)
    914 		syncmbox(mb, 1);
    915 }
    916 
    917 /*
    918  *  the following are called with mbllock'd
    919  */
    920 void
    921 mboxincref(Mailbox *mb)
    922 {
    923 	assert(mb->refs > 0);
    924 	mb->refs++;
    925 }
    926 void
    927 mboxdecref(Mailbox *mb)
    928 {
    929 	assert(mb->refs > 0);
    930 	qlock(&mb->ql);
    931 	mb->refs--;
    932 	if(mb->refs == 0){
    933 		delmessage(mb, mb->root);
    934 		if(mb->ctl)
    935 			hfree(PATH(mb->id, Qmbox), "ctl");
    936 		if(mb->close)
    937 			(*mb->close)(mb);
    938 		free(mb);
    939 	} else
    940 		qunlock(&mb->ql);
    941 }
    942 
    943 int
    944 cistrncmp(char *a, char *b, int n)
    945 {
    946 	while(n-- > 0){
    947 		if(tolower(*a++) != tolower(*b++))
    948 			return -1;
    949 	}
    950 	return 0;
    951 }
    952 
    953 int
    954 cistrcmp(char *a, char *b)
    955 {
    956 	for(;;){
    957 		if(tolower(*a) != tolower(*b++))
    958 			return -1;
    959 		if(*a++ == 0)
    960 			break;
    961 	}
    962 	return 0;
    963 }
    964 
    965 static char*
    966 skipwhite(char *p)
    967 {
    968 	while(isspace(*p))
    969 		p++;
    970 	return p;
    971 }
    972 
    973 static char*
    974 skiptosemi(char *p)
    975 {
    976 	while(*p && *p != ';')
    977 		p++;
    978 	while(*p == ';' || isspace(*p))
    979 		p++;
    980 	return p;
    981 }
    982 
    983 static char*
    984 getstring(char *p, String *s, int dolower)
    985 {
    986 	s = s_reset(s);
    987 	p = skipwhite(p);
    988 	if(*p == '"'){
    989 		p++;
    990 		for(;*p && *p != '"'; p++)
    991 			if(dolower)
    992 				s_putc(s, tolower(*p));
    993 			else
    994 				s_putc(s, *p);
    995 		if(*p == '"')
    996 			p++;
    997 		s_terminate(s);
    998 
    999 		return p;
   1000 	}
   1001 
   1002 	for(; *p && !isspace(*p) && *p != ';'; p++)
   1003 		if(dolower)
   1004 			s_putc(s, tolower(*p));
   1005 		else
   1006 			s_putc(s, *p);
   1007 	s_terminate(s);
   1008 
   1009 	return p;
   1010 }
   1011 
   1012 static void
   1013 setfilename(Message *m, char *p)
   1014 {
   1015 	m->filename = s_reset(m->filename);
   1016 	getstring(p, m->filename, 0);
   1017 	for(p = s_to_c(m->filename); *p; p++)
   1018 		if(*p == ' ' || *p == '\t' || *p == ';')
   1019 			*p = '_';
   1020 }
   1021 
   1022 /* */
   1023 /* undecode message body */
   1024 /* */
   1025 void
   1026 decode(Message *m)
   1027 {
   1028 	int i, len;
   1029 	char *x;
   1030 
   1031 	if(m->decoded)
   1032 		return;
   1033 	switch(m->encoding){
   1034 	case Ebase64:
   1035 		len = m->bend - m->body;
   1036 		i = (len*3)/4+1;	/* room for max chars + null */
   1037 		x = emalloc(i);
   1038 		len = dec64((uchar*)x, i, m->body, len);
   1039 		if(m->ballocd)
   1040 			free(m->body);
   1041 		m->body = x;
   1042 		m->bend = x + len;
   1043 		m->ballocd = 1;
   1044 		break;
   1045 	case Equoted:
   1046 		len = m->bend - m->body;
   1047 		x = emalloc(len+2);	/* room for null and possible extra nl */
   1048 		len = decquoted(x, m->body, m->bend);
   1049 		if(m->ballocd)
   1050 			free(m->body);
   1051 		m->body = x;
   1052 		m->bend = x + len;
   1053 		m->ballocd = 1;
   1054 		break;
   1055 	default:
   1056 		break;
   1057 	}
   1058 	m->decoded = 1;
   1059 }
   1060 
   1061 /* convert latin1 to utf */
   1062 void
   1063 convert(Message *m)
   1064 {
   1065 	int len;
   1066 	char *x;
   1067 
   1068 	/* don't convert if we're not a leaf, not text, or already converted */
   1069 	if(m->converted)
   1070 		return;
   1071 	if(m->part != nil)
   1072 		return;
   1073 	if(cistrncmp(s_to_c(m->type), "text", 4) != 0)
   1074 		return;
   1075 
   1076 	if(cistrcmp(s_to_c(m->charset), "us-ascii") == 0 ||
   1077 	   cistrcmp(s_to_c(m->charset), "iso-8859-1") == 0){
   1078 		len = is8bit(m);
   1079 		if(len > 0){
   1080 			len = 2*len + m->bend - m->body + 1;
   1081 			x = emalloc(len);
   1082 			len = latin1toutf(x, m->body, m->bend);
   1083 			if(m->ballocd)
   1084 				free(m->body);
   1085 			m->body = x;
   1086 			m->bend = x + len;
   1087 			m->ballocd = 1;
   1088 		}
   1089 	} else if(cistrcmp(s_to_c(m->charset), "iso-8859-2") == 0){
   1090 		len = xtoutf("8859-2", &x, m->body, m->bend);
   1091 		if(len != 0){
   1092 			if(m->ballocd)
   1093 				free(m->body);
   1094 			m->body = x;
   1095 			m->bend = x + len;
   1096 			m->ballocd = 1;
   1097 		}
   1098 	} else if(cistrcmp(s_to_c(m->charset), "iso-8859-15") == 0){
   1099 		len = xtoutf("8859-15", &x, m->body, m->bend);
   1100 		if(len != 0){
   1101 			if(m->ballocd)
   1102 				free(m->body);
   1103 			m->body = x;
   1104 			m->bend = x + len;
   1105 			m->ballocd = 1;
   1106 		}
   1107 	} else if(cistrcmp(s_to_c(m->charset), "big5") == 0){
   1108 		len = xtoutf("big5", &x, m->body, m->bend);
   1109 		if(len != 0){
   1110 			if(m->ballocd)
   1111 				free(m->body);
   1112 			m->body = x;
   1113 			m->bend = x + len;
   1114 			m->ballocd = 1;
   1115 		}
   1116 	} else if(cistrcmp(s_to_c(m->charset), "iso-2022-jp") == 0){
   1117 		len = xtoutf("jis", &x, m->body, m->bend);
   1118 		if(len != 0){
   1119 			if(m->ballocd)
   1120 				free(m->body);
   1121 			m->body = x;
   1122 			m->bend = x + len;
   1123 			m->ballocd = 1;
   1124 		}
   1125 	} else if(cistrcmp(s_to_c(m->charset), "windows-1257") == 0
   1126 			|| cistrcmp(s_to_c(m->charset), "windows-1252") == 0){
   1127 		len = is8bit(m);
   1128 		if(len > 0){
   1129 			len = 2*len + m->bend - m->body + 1;
   1130 			x = emalloc(len);
   1131 			len = windows1257toutf(x, m->body, m->bend);
   1132 			if(m->ballocd)
   1133 				free(m->body);
   1134 			m->body = x;
   1135 			m->bend = x + len;
   1136 			m->ballocd = 1;
   1137 		}
   1138 	} else if(cistrcmp(s_to_c(m->charset), "windows-1251") == 0){
   1139 		len = xtoutf("cp1251", &x, m->body, m->bend);
   1140 		if(len != 0){
   1141 			if(m->ballocd)
   1142 				free(m->body);
   1143 			m->body = x;
   1144 			m->bend = x + len;
   1145 			m->ballocd = 1;
   1146 		}
   1147 	} else if(cistrcmp(s_to_c(m->charset), "koi8-r") == 0){
   1148 		len = xtoutf("koi8", &x, m->body, m->bend);
   1149 		if(len != 0){
   1150 			if(m->ballocd)
   1151 				free(m->body);
   1152 			m->body = x;
   1153 			m->bend = x + len;
   1154 			m->ballocd = 1;
   1155 		}
   1156 	}
   1157 
   1158 	m->converted = 1;
   1159 }
   1160 
   1161 enum
   1162 {
   1163 	Self=	1,
   1164 	Hex=	2
   1165 };
   1166 uchar	tableqp[256];
   1167 
   1168 static void
   1169 initquoted(void)
   1170 {
   1171 	int c;
   1172 
   1173 	memset(tableqp, 0, 256);
   1174 	for(c = ' '; c <= '<'; c++)
   1175 		tableqp[c] = Self;
   1176 	for(c = '>'; c <= '~'; c++)
   1177 		tableqp[c] = Self;
   1178 	tableqp['\t'] = Self;
   1179 	tableqp['='] = Hex;
   1180 }
   1181 
   1182 static int
   1183 hex2int(int x)
   1184 {
   1185 	if(x >= '0' && x <= '9')
   1186 		return x - '0';
   1187 	if(x >= 'A' && x <= 'F')
   1188 		return (x - 'A') + 10;
   1189 	if(x >= 'a' && x <= 'f')
   1190 		return (x - 'a') + 10;
   1191 	return 0;
   1192 }
   1193 
   1194 static char*
   1195 decquotedline(char *out, char *in, char *e)
   1196 {
   1197 	int c, soft;
   1198 
   1199 	/* dump trailing white space */
   1200 	while(e >= in && (*e == ' ' || *e == '\t' || *e == '\r' || *e == '\n'))
   1201 		e--;
   1202 
   1203 	/* trailing '=' means no newline */
   1204 	if(*e == '='){
   1205 		soft = 1;
   1206 		e--;
   1207 	} else
   1208 		soft = 0;
   1209 
   1210 	while(in <= e){
   1211 		c = (*in++) & 0xff;
   1212 		switch(tableqp[c]){
   1213 		case Self:
   1214 			*out++ = c;
   1215 			break;
   1216 		case Hex:
   1217 			c = hex2int(*in++)<<4;
   1218 			c |= hex2int(*in++);
   1219 			*out++ = c;
   1220 			break;
   1221 		}
   1222 	}
   1223 	if(!soft)
   1224 		*out++ = '\n';
   1225 	*out = 0;
   1226 
   1227 	return out;
   1228 }
   1229 
   1230 int
   1231 decquoted(char *out, char *in, char *e)
   1232 {
   1233 	char *p, *nl;
   1234 
   1235 	if(tableqp[' '] == 0)
   1236 		initquoted();
   1237 
   1238 	p = out;
   1239 	while((nl = strchr(in, '\n')) != nil && nl < e){
   1240 		p = decquotedline(p, in, nl);
   1241 		in = nl + 1;
   1242 	}
   1243 	if(in < e)
   1244 		p = decquotedline(p, in, e-1);
   1245 
   1246 	/* make sure we end with a new line */
   1247 	if(*(p-1) != '\n'){
   1248 		*p++ = '\n';
   1249 		*p = 0;
   1250 	}
   1251 
   1252 	return p - out;
   1253 }
   1254 
   1255 #if 0 /* jpc */
   1256 static char*
   1257 lowercase(char *p)
   1258 {
   1259 	char *op;
   1260 	int c;
   1261 
   1262 	for(op = p; c = *p; p++)
   1263 		if(isupper(c))
   1264 			*p = tolower(c);
   1265 	return op;
   1266 }
   1267 #endif
   1268 
   1269 /*
   1270  *  return number of 8 bit characters
   1271  */
   1272 static int
   1273 is8bit(Message *m)
   1274 {
   1275 	int count = 0;
   1276 	char *p;
   1277 
   1278 	for(p = m->body; p < m->bend; p++)
   1279 		if(*p & 0x80)
   1280 			count++;
   1281 	return count;
   1282 }
   1283 
   1284 /* translate latin1 directly since it fits neatly in utf */
   1285 int
   1286 latin1toutf(char *out, char *in, char *e)
   1287 {
   1288 	Rune r;
   1289 	char *p;
   1290 
   1291 	p = out;
   1292 	for(; in < e; in++){
   1293 		r = (*in) & 0xff;
   1294 		p += runetochar(p, &r);
   1295 	}
   1296 	*p = 0;
   1297 	return p - out;
   1298 }
   1299 
   1300 /* translate any thing else using the tcs program */
   1301 int
   1302 xtoutf(char *charset, char **out, char *in, char *e)
   1303 {
   1304 	char *av[4];
   1305 	int totcs[2];
   1306 	int fromtcs[2];
   1307 	int n, len, sofar;
   1308 	char *p;
   1309 
   1310 	len = e-in+1;
   1311 	sofar = 0;
   1312 	*out = p = malloc(len+1);
   1313 	if(p == nil)
   1314 		return 0;
   1315 
   1316 	av[0] = charset;
   1317 	av[1] = "-f";
   1318 	av[2] = charset;
   1319 	av[3] = 0;
   1320 	if(pipe(totcs) < 0)
   1321 		return 0;
   1322 	if(pipe(fromtcs) < 0){
   1323 		close(totcs[0]); close(totcs[1]);
   1324 		return 0;
   1325 	}
   1326 	switch(rfork(RFPROC|RFFDG|RFNOWAIT)){
   1327 	case -1:
   1328 		close(fromtcs[0]); close(fromtcs[1]);
   1329 		close(totcs[0]); close(totcs[1]);
   1330 		return 0;
   1331 	case 0:
   1332 		close(fromtcs[0]); close(totcs[1]);
   1333 		dup(fromtcs[1], 1);
   1334 		dup(totcs[0], 0);
   1335 		close(fromtcs[1]); close(totcs[0]);
   1336 		dup(open("/dev/null", OWRITE), 2);
   1337 		/*jpc exec("/bin/tcs", av); */
   1338 		exec(unsharp("#9/bin/tcs"), av);
   1339 		/* _exits(0); */
   1340 		threadexits(nil);
   1341 	default:
   1342 		close(fromtcs[1]); close(totcs[0]);
   1343 		switch(rfork(RFPROC|RFFDG|RFNOWAIT)){
   1344 		case -1:
   1345 			close(fromtcs[0]); close(totcs[1]);
   1346 			return 0;
   1347 		case 0:
   1348 			close(fromtcs[0]);
   1349 			while(in < e){
   1350 				n = write(totcs[1], in, e-in);
   1351 				if(n <= 0)
   1352 					break;
   1353 				in += n;
   1354 			}
   1355 			close(totcs[1]);
   1356 			/* _exits(0); */
   1357 			threadexits(nil);
   1358 		default:
   1359 			close(totcs[1]);
   1360 			for(;;){
   1361 				n = read(fromtcs[0], &p[sofar], len-sofar);
   1362 				if(n <= 0)
   1363 					break;
   1364 				sofar += n;
   1365 				p[sofar] = 0;
   1366 				if(sofar == len){
   1367 					len += 1024;
   1368 					*out = p = realloc(p, len+1);
   1369 					if(p == nil)
   1370 						return 0;
   1371 				}
   1372 			}
   1373 			close(fromtcs[0]);
   1374 			break;
   1375 		}
   1376 		break;
   1377 	}
   1378 	return sofar;
   1379 }
   1380 
   1381 enum {
   1382 	Winstart= 0x7f,
   1383 	Winend= 0x9f
   1384 };
   1385 
   1386 Rune winchars[] = {
   1387 	L'•',
   1388 	L'•', L'•', L'‚', L'ƒ', L'„', L'…', L'†', L'‡',
   1389 	L'ˆ', L'‰', L'Š', L'‹', L'Œ', L'•', L'•', L'•',
   1390 	L'•', L'‘', L'’', L'“', L'”', L'•', L'–', L'—',
   1391 	L'˜', L'™', L'š', L'›', L'œ', L'•', L'•', L'Ÿ'
   1392 };
   1393 
   1394 int
   1395 windows1257toutf(char *out, char *in, char *e)
   1396 {
   1397 	Rune r;
   1398 	char *p;
   1399 
   1400 	p = out;
   1401 	for(; in < e; in++){
   1402 		r = (*in) & 0xff;
   1403 		if(r >= 0x7f && r <= 0x9f)
   1404 			r = winchars[r-0x7f];
   1405 		p += runetochar(p, &r);
   1406 	}
   1407 	*p = 0;
   1408 	return p - out;
   1409 }
   1410 
   1411 void *
   1412 emalloc(ulong n)
   1413 {
   1414 	void *p;
   1415 
   1416 	p = mallocz(n, 1);
   1417 	if(!p){
   1418 		fprint(2, "%s: out of memory alloc %lud\n", argv0, n);
   1419 		threadexits("out of memory");
   1420 	}
   1421 	setmalloctag(p, getcallerpc(&n));
   1422 	return p;
   1423 }
   1424 
   1425 void *
   1426 erealloc(void *p, ulong n)
   1427 {
   1428 	if(n == 0)
   1429 		n = 1;
   1430 	p = realloc(p, n);
   1431 	if(!p){
   1432 		fprint(2, "%s: out of memory realloc %lud\n", argv0, n);
   1433 		threadexits("out of memory");
   1434 	}
   1435 	setrealloctag(p, getcallerpc(&p));
   1436 	return p;
   1437 }
   1438 
   1439 void
   1440 mailplumb(Mailbox *mb, Message *m, int delete)
   1441 {
   1442 	Plumbmsg p;
   1443 	Plumbattr a[7];
   1444 	char buf[256];
   1445 	int ai;
   1446 	char lenstr[10], *from, *subject, *date;
   1447 	static int fd = -1;
   1448 
   1449 	if(m->subject822 == nil)
   1450 		subject = "";
   1451 	else
   1452 		subject = s_to_c(m->subject822);
   1453 
   1454 	if(m->from822 != nil)
   1455 		from = s_to_c(m->from822);
   1456 	else if(m->unixfrom != nil)
   1457 		from = s_to_c(m->unixfrom);
   1458 	else
   1459 		from = "";
   1460 
   1461 	if(m->unixdate != nil)
   1462 		date = s_to_c(m->unixdate);
   1463 	else
   1464 		date = "";
   1465 
   1466 	sprint(lenstr, "%ld", m->end-m->start);
   1467 
   1468 	if(biffing && !delete)
   1469 		print("[ %s / %s / %s ]\n", from, subject, lenstr);
   1470 
   1471 	if(!plumbing)
   1472 		return;
   1473 
   1474 	if(fd < 0)
   1475 		fd = plumbopen("send", OWRITE);
   1476 	if(fd < 0)
   1477 		return;
   1478 
   1479 	p.src = "mailfs";
   1480 	p.dst = "seemail";
   1481 	p.wdir = "/mail/fs";
   1482 	p.type = "text";
   1483 
   1484 	ai = 0;
   1485 	a[ai].name = "filetype";
   1486 	a[ai].value = "mail";
   1487 
   1488 	a[++ai].name = "sender";
   1489 	a[ai].value = from;
   1490 	a[ai-1].next = &a[ai];
   1491 
   1492 	a[++ai].name = "length";
   1493 	a[ai].value = lenstr;
   1494 	a[ai-1].next = &a[ai];
   1495 
   1496 	a[++ai].name = "mailtype";
   1497 	a[ai].value = delete?"delete":"new";
   1498 	a[ai-1].next = &a[ai];
   1499 
   1500 	a[++ai].name = "date";
   1501 	a[ai].value = date;
   1502 	a[ai-1].next = &a[ai];
   1503 
   1504 	if(m->sdigest){
   1505 		a[++ai].name = "digest";
   1506 		a[ai].value = s_to_c(m->sdigest);
   1507 		a[ai-1].next = &a[ai];
   1508 	}
   1509 
   1510 	a[ai].next = nil;
   1511 
   1512 	p.attr = a;
   1513 	snprint(buf, sizeof(buf), "%s/%s/%s",
   1514 		mntpt, mb->name, m->name);
   1515 	p.ndata = strlen(buf);
   1516 	p.data = buf;
   1517 
   1518 	plumbsend(fd, &p);
   1519 }
   1520 
   1521 /* */
   1522 /* count the number of lines in the body (for imap4) */
   1523 /* */
   1524 void
   1525 countlines(Message *m)
   1526 {
   1527 	int i;
   1528 	char *p;
   1529 
   1530 	i = 0;
   1531 	for(p = strchr(m->rbody, '\n'); p != nil && p < m->rbend; p = strchr(p+1, '\n'))
   1532 		i++;
   1533 	sprint(m->lines, "%d", i);
   1534 }
   1535 
   1536 char *LOG = "fs";
   1537 
   1538 void
   1539 logmsg(char *s, Message *m)
   1540 {
   1541 	int pid;
   1542 
   1543 	if(!logging)
   1544 		return;
   1545 	pid = getpid();
   1546 	if(m == nil)
   1547 		syslog(0, LOG, "%s.%d: %s", user, pid, s);
   1548 	else
   1549 		syslog(0, LOG, "%s.%d: %s msg from %s digest %s",
   1550 			user, pid, s,
   1551 			m->from822 ? s_to_c(m->from822) : "?",
   1552 			s_to_c(m->sdigest));
   1553 }
   1554 
   1555 /*
   1556  *  squeeze nulls out of the body
   1557  */
   1558 static void
   1559 nullsqueeze(Message *m)
   1560 {
   1561 	char *p, *q;
   1562 
   1563 	q = memchr(m->body, 0, m->end-m->body);
   1564 	if(q == nil)
   1565 		return;
   1566 
   1567 	for(p = m->body; q < m->end; q++){
   1568 		if(*q == 0)
   1569 			continue;
   1570 		*p++ = *q;
   1571 	}
   1572 	m->bend = m->rbend = m->end = p;
   1573 }
   1574 
   1575 
   1576 /* */
   1577 /* convert an RFC822 date into a Unix style date */
   1578 /* for when the Unix From line isn't there (e.g. POP3). */
   1579 /* enough client programs depend on having a Unix date */
   1580 /* that it's easiest to write this conversion code once, right here. */
   1581 /* */
   1582 /* people don't follow RFC822 particularly closely, */
   1583 /* so we use strtotm, which is a bunch of heuristics. */
   1584 /* */
   1585 
   1586 extern int strtotm(char*, Tm*);
   1587 String*
   1588 date822tounix(char *s)
   1589 {
   1590 	char *p, *q;
   1591 	Tm tm;
   1592 
   1593 	if(strtotm(s, &tm) < 0)
   1594 		return nil;
   1595 
   1596 	p = asctime(&tm);
   1597 	if(q = strchr(p, '\n'))
   1598 		*q = '\0';
   1599 	return s_copy(p);
   1600 }