text: #include "common.h"
  #include <ctype.h>
  #include <plumb.h>
  #include <libsec.h>
  #include <thread.h>
  #include "dat.h"
  
  extern char* dirtab[]; /* jpc */
  
  typedef struct Header Header;
  
  struct Header {
  	char *type;
  	void (*f)(Message*, Header*, char*);
  	int len;
  };
  
  /* headers */
  static	void	ctype(Message*, Header*, char*);
  static	void	cencoding(Message*, Header*, char*);
  static	void	cdisposition(Message*, Header*, char*);
  static	void	date822(Message*, Header*, char*);
  static	void	from822(Message*, Header*, char*);
  static	void	to822(Message*, Header*, char*);
  static	void	sender822(Message*, Header*, char*);
  static	void	replyto822(Message*, Header*, char*);
  static	void	subject822(Message*, Header*, char*);
  static	void	inreplyto822(Message*, Header*, char*);
  static	void	cc822(Message*, Header*, char*);
  static	void	bcc822(Message*, Header*, char*);
  static	void	messageid822(Message*, Header*, char*);
  static	void	mimeversion(Message*, Header*, char*);
  static	void	nullsqueeze(Message*);
  enum
  {
  	Mhead=	11,	/* offset of first mime header */
  };
  
  Header head[] =
  {
  	{ "date:", date822, },
  	{ "from:", from822, },
  	{ "to:", to822, },
  	{ "sender:", sender822, },
  	{ "reply-to:", replyto822, },
  	{ "subject:", subject822, },
  	{ "cc:", cc822, },
  	{ "bcc:", bcc822, },
  	{ "in-reply-to:", inreplyto822, },
  	{ "mime-version:", mimeversion, },
  	{ "message-id:", messageid822, },
  
  [Mhead]	{ "content-type:", ctype, },
  	{ "content-transfer-encoding:", cencoding, },
  	{ "content-disposition:", cdisposition, },
  	{ 0, }
  };
  
  /* static	void	fatal(char *fmt, ...); jpc */
  static	void	initquoted(void);
  /* static	void	startheader(Message*);
  static	void	startbody(Message*); jpc */
  static	char*	skipwhite(char*);
  static	char*	skiptosemi(char*);
  static	char*	getstring(char*, String*, int);
  static	void	setfilename(Message*, char*);
  /* static	char*	lowercase(char*); jpc */
  static	int	is8bit(Message*);
  static	int	headerline(char**, String*);
  static	void	initheaders(void);
  static void	parseattachments(Message*, Mailbox*);
  
  int		debug;
  
  char *Enotme = "path not served by this file server";
  
  enum
  {
  	Chunksize = 1024
  };
  
  Mailboxinit *boxinit[] = {
  	imap4mbox,
  	pop3mbox,
  	plan9mbox
  };
  
  char*
  syncmbox(Mailbox *mb, int doplumb)
  {
  	return (*mb->sync)(mb, doplumb);
  }
  
  /* create a new mailbox */
  char*
  newmbox(char *path, char *name, int std)
  {
  	Mailbox *mb, **l;
  	char *p, *rv;
  	int i;
  
  	initheaders();
  
  	mb = emalloc(sizeof(*mb));
  	strncpy(mb->path, path, sizeof(mb->path)-1);
  	if(name == nil){
  		p = strrchr(path, '/');
  		if(p == nil)
  			p = path;
  		else
  			p++;
  		if(*p == 0){
  			free(mb);
  			return "bad mbox name";
  		}
  		strncpy(mb->name, p, sizeof(mb->name)-1);
  	} else {
  		strncpy(mb->name, name, sizeof(mb->name)-1);
  	}
  
  	rv = nil;
  	/* check for a mailbox type */
  	for(i=0; i<nelem(boxinit); i++)
  		if((rv = (*boxinit[i])(mb, path)) != Enotme)
  			break;
  	if(i == nelem(boxinit)){
  		free(mb);
  		return "bad path";
  	}
  
  	/* on error, give up */
  	if(rv){
  		free(mb);
  		return rv;
  	}
  
  	/* make sure name isn't taken */
  	qlock(&mbllock);
  	for(l = &mbl; *l != nil; l = &(*l)->next){
  		if(strcmp((*l)->name, mb->name) == 0){
  			if(strcmp(path, (*l)->path) == 0)
  				rv = nil;
  			else
  				rv = "mbox name in use";
  			if(mb->close)
  				(*mb->close)(mb);
  			free(mb);
  			qunlock(&mbllock);
  			return rv;
  		}
  	}
  
  	/* always try locking */
  	mb->dolock = 1;
  
  	mb->refs = 1;
  	mb->next = nil;
  	mb->id = newid();
  	mb->root = newmessage(nil);
  	mb->std = std;
  	*l = mb;
  	qunlock(&mbllock);
  
  	qlock(&mb->ql);
  	if(mb->ctl){
  		henter(PATH(mb->id, Qmbox), "ctl",
  			(Qid){PATH(mb->id, Qmboxctl), 0, QTFILE}, nil, mb);
  	}
  	rv = syncmbox(mb, 0);
  	qunlock(&mb->ql);
  
  	return rv;
  }
  
  /* close the named mailbox */
  void
  freembox(char *name)
  {
  	Mailbox **l, *mb;
  
  	qlock(&mbllock);
  	for(l=&mbl; *l != nil; l=&(*l)->next){
  		if(strcmp(name, (*l)->name) == 0){
  			mb = *l;
  			*l = mb->next;
  			mboxdecref(mb);
  			break;
  		}
  	}
  	hfree(PATH(0, Qtop), name);
  	qunlock(&mbllock);
  }
  
  static void
  initheaders(void)
  {
  	Header *h;
  	static int already;
  
  	if(already)
  		return;
  	already = 1;
  
  	for(h = head; h->type != nil; h++)
  		h->len = strlen(h->type);
  }
  
  /*
   *  parse a Unix style header
   */
  void
  parseunix(Message *m)
  {
  	char *p;
  	String *h;
  
  	h = s_new();
  	for(p = m->start + 5; *p && *p != '\r' && *p != '\n'; p++)
  		s_putc(h, *p);
  	s_terminate(h);
  	s_restart(h);
  
  	m->unixfrom = s_parse(h, s_reset(m->unixfrom));
  	m->unixdate = s_append(s_reset(m->unixdate), h->ptr);
  
  	s_free(h);
  }
  
  /*
   *  parse a message
   */
  void
  parseheaders(Message *m, int justmime, Mailbox *mb, int addfrom)
  {
  	String *hl;
  	Header *h;
  	char *p, *q;
  	int i;
  
  	if(m->whole == m->whole->whole){
  		henter(PATH(mb->id, Qmbox), m->name,
  			(Qid){PATH(m->id, Qdir), 0, QTDIR}, m, mb);
  	} else {
  		henter(PATH(m->whole->id, Qdir), m->name,
  			(Qid){PATH(m->id, Qdir), 0, QTDIR}, m, mb);
  	}
  	for(i = 0; i < Qmax; i++)
  		henter(PATH(m->id, Qdir), dirtab[i],
  			(Qid){PATH(m->id, i), 0, QTFILE}, m, mb);
  
  	/* parse mime headers */
  	p = m->header;
  	hl = s_new();
  	while(headerline(&p, hl)){
  		if(justmime)
  			h = &head[Mhead];
  		else
  			h = head;
  		for(; h->type; h++){
  			if(cistrncmp(s_to_c(hl), h->type, h->len) == 0){
  				(*h->f)(m, h, s_to_c(hl));
  				break;
  			}
  		}
  		s_reset(hl);
  	}
  	s_free(hl);
  
  	/* the blank line isn't really part of the body or header */
  	if(justmime){
  		m->mhend = p;
  		m->hend = m->header;
  	} else {
  		m->hend = p;
  	}
  	if(*p == '\n')
  		p++;
  	m->rbody = m->body = p;
  
  	/* if type is text, get any nulls out of the body.  This is */
  	/* for the two seans and imap clients that get confused. */
  	if(strncmp(s_to_c(m->type), "text/", 5) == 0)
  		nullsqueeze(m);
  
  	/* */
  	/* cobble together Unix-style from line */
  	/* for local mailbox messages, we end up recreating the */
  	/* original header. */
  	/* for pop3 messages, the best we can do is  */
  	/* use the From: information and the RFC822 date. */
  	/* */
  	if(m->unixdate == nil || strcmp(s_to_c(m->unixdate), "???") == 0
  	|| strcmp(s_to_c(m->unixdate), "Thu Jan 1 00:00:00 GMT 1970") == 0){
  		if(m->unixdate){
  			s_free(m->unixdate);
  			m->unixdate = nil;
  		}
  		/* look for the date in the first Received: line. */
  		/* it's likely to be the right time zone (it's */
  	 	/* the local system) and in a convenient format. */
  		if(cistrncmp(m->header, "received:", 9)==0){
  			if((q = strchr(m->header, ';')) != nil){
  				p = q;
  				while((p = strchr(p, '\n')) != nil){
  					if(p[1] != ' ' && p[1] != '\t' && p[1] != '\n')
  						break;
  					p++;
  				}
  				if(p){
  					*p = '\0';
  					m->unixdate = date822tounix(q+1);
  					*p = '\n';
  				}
  			}
  		}
  
  		/* fall back on the rfc822 date	 */
  		if(m->unixdate==nil && m->date822)
  			m->unixdate = date822tounix(s_to_c(m->date822));
  	}
  
  	if(m->unixheader != nil)
  		s_free(m->unixheader);
  
  	/* only fake header for top-level messages for pop3 and imap4 */
  	/* clients (those protocols don't include the unix header). */
  	/* adding the unix header all the time screws up mime-attached */
  	/* rfc822 messages. */
  	if(!addfrom && !m->unixfrom){
  		m->unixheader = nil;
  		return;
  	}
  
  	m->unixheader = s_copy("From ");
  	if(m->unixfrom && strcmp(s_to_c(m->unixfrom), "???") != 0)
  		s_append(m->unixheader, s_to_c(m->unixfrom));
  	else if(m->from822)
  		s_append(m->unixheader, s_to_c(m->from822));
  	else
  		s_append(m->unixheader, "???");
  
  	s_append(m->unixheader, " ");
  	if(m->unixdate)
  		s_append(m->unixheader, s_to_c(m->unixdate));
  	else
  		s_append(m->unixheader, "Thu Jan  1 00:00:00 GMT 1970");
  
  	s_append(m->unixheader, "\n");
  }
  
  String*
  promote(String **sp)
  {
  	String *s;
  
  	if(*sp != nil)
  		s = s_clone(*sp);
  	else
  		s = nil;
  	return s;
  }
  
  void
  parsebody(Message *m, Mailbox *mb)
  {
  	Message *nm;
  
  	/* recurse */
  	if(strncmp(s_to_c(m->type), "multipart/", 10) == 0){
  		parseattachments(m, mb);
  	} else if(strcmp(s_to_c(m->type), "message/rfc822") == 0){
  		decode(m);
  		parseattachments(m, mb);
  		nm = m->part;
  
  		/* promote headers */
  		if(m->replyto822 == nil && m->from822 == nil && m->sender822 == nil){
  			m->from822 = promote(&nm->from822);
  			m->to822 = promote(&nm->to822);
  			m->date822 = promote(&nm->date822);
  			m->sender822 = promote(&nm->sender822);
  			m->replyto822 = promote(&nm->replyto822);
  			m->subject822 = promote(&nm->subject822);
  			m->unixdate = promote(&nm->unixdate);
  		}
  	}
  }
  
  void
  parse(Message *m, int justmime, Mailbox *mb, int addfrom)
  {
  	parseheaders(m, justmime, mb, addfrom);
  	parsebody(m, mb);
  }
  
  static void
  parseattachments(Message *m, Mailbox *mb)
  {
  	Message *nm, **l;
  	char *p, *x;
  
  	/* if there's a boundary, recurse... */
  	if(m->boundary != nil){
  		p = m->body;
  		nm = nil;
  		l = &m->part;
  		for(;;){
  			x = strstr(p, s_to_c(m->boundary));
  
  			/* no boundary, we're done */
  			if(x == nil){
  				if(nm != nil)
  					nm->rbend = nm->bend = nm->end = m->bend;
  				break;
  			}
  
  			/* boundary must be at the start of a line */
  			if(x != m->body && *(x-1) != '\n'){
  				p = x+1;
  				continue;
  			}
  
  			if(nm != nil)
  				nm->rbend = nm->bend = nm->end = x;
  			x += strlen(s_to_c(m->boundary));
  
  			/* is this the last part? ignore anything after it */
  			if(strncmp(x, "--", 2) == 0)
  				break;
  
  			p = strchr(x, '\n');
  			if(p == nil)
  				break;
  			nm = newmessage(m);
  			nm->start = nm->header = nm->body = nm->rbody = ++p;
  			nm->mheader = nm->header;
  			*l = nm;
  			l = &nm->next;
  		}
  		for(nm = m->part; nm != nil; nm = nm->next)
  			parse(nm, 1, mb, 0);
  		return;
  	}
  
  	/* if we've got an rfc822 message, recurse... */
  	if(strcmp(s_to_c(m->type), "message/rfc822") == 0){
  		nm = newmessage(m);
  		m->part = nm;
  		nm->start = nm->header = nm->body = nm->rbody = m->body;
  		nm->end = nm->bend = nm->rbend = m->bend;
  		parse(nm, 0, mb, 0);
  	}
  }
  
  /*
   *  pick up a header line
   */
  static int
  headerline(char **pp, String *hl)
  {
  	char *p, *x;
  
  	s_reset(hl);
  	p = *pp;
  	x = strpbrk(p, ":\n");
  	if(x == nil || *x == '\n')
  		return 0;
  	for(;;){
  		x = strchr(p, '\n');
  		if(x == nil)
  			x = p + strlen(p);
  		s_nappend(hl, p, x-p);
  		p = x;
  		if(*p != '\n' || *++p != ' ' && *p != '\t')
  			break;
  		while(*p == ' ' || *p == '\t')
  			p++;
  		s_putc(hl, ' ');
  	}
  	*pp = p;
  	return 1;
  }
  
  static String*
  addr822(char *p)
  {
  	String *s, *list;
  	int incomment, addrdone, inanticomment, quoted;
  	int n;
  	int c;
  
  	list = s_new();
  	s = s_new();
  	quoted = incomment = addrdone = inanticomment = 0;
  	n = 0;
  	for(; *p; p++){
  		c = *p;
  
  		/* whitespace is ignored */
  		if(!quoted && isspace(c) || c == '\r')
  			continue;
  
  		/* strings are always treated as atoms */
  		if(!quoted && c == '"'){
  			if(!addrdone && !incomment)
  				s_putc(s, c);
  			for(p++; *p; p++){
  				if(!addrdone && !incomment)
  					s_putc(s, *p);
  				if(!quoted && *p == '"')
  					break;
  				if(*p == '\\')
  					quoted = 1;
  				else
  					quoted = 0;
  			}
  			if(*p == 0)
  				break;
  			quoted = 0;
  			continue;
  		}
  
  		/* ignore everything in an expicit comment */
  		if(!quoted && c == '('){
  			incomment = 1;
  			continue;
  		}
  		if(incomment){
  			if(!quoted && c == ')')
  				incomment = 0;
  			quoted = 0;
  			continue;
  		}
  
  		/* anticomments makes everything outside of them comments */
  		if(!quoted && c == '<' && !inanticomment){
  			inanticomment = 1;
  			s = s_reset(s);
  			continue;
  		}
  		if(!quoted && c == '>' && inanticomment){
  			addrdone = 1;
  			inanticomment = 0;
  			continue;
  		}
  
  		/* commas separate addresses */
  		if(!quoted && c == ',' && !inanticomment){
  			s_terminate(s);
  			addrdone = 0;
  			if(n++ != 0)
  				s_append(list, " ");
  			s_append(list, s_to_c(s));
  			s = s_reset(s);
  			continue;
  		}
  
  		/* what's left is part of the address */
  		s_putc(s, c);
  
  		/* quoted characters are recognized only as characters */
  		if(c == '\\')
  			quoted = 1;
  		else
  			quoted = 0;
  
  	}
  
  	if(*s_to_c(s) != 0){
  		s_terminate(s);
  		if(n++ != 0)
  			s_append(list, " ");
  		s_append(list, s_to_c(s));
  	}
  	s_free(s);
  
  	if(n == 0){
  		s_free(list);
  		return nil;
  	}
  	return list;
  }
  
  static void
  to822(Message *m, Header *h, char *p)
  {
  	p += strlen(h->type);
  	s_free(m->to822);
  	m->to822 = addr822(p);
  }
  
  static void
  cc822(Message *m, Header *h, char *p)
  {
  	p += strlen(h->type);
  	s_free(m->cc822);
  	m->cc822 = addr822(p);
  }
  
  static void
  bcc822(Message *m, Header *h, char *p)
  {
  	p += strlen(h->type);
  	s_free(m->bcc822);
  	m->bcc822 = addr822(p);
  }
  
  static void
  from822(Message *m, Header *h, char *p)
  {
  	p += strlen(h->type);
  	s_free(m->from822);
  	m->from822 = addr822(p);
  }
  
  static void
  sender822(Message *m, Header *h, char *p)
  {
  	p += strlen(h->type);
  	s_free(m->sender822);
  	m->sender822 = addr822(p);
  }
  
  static void
  replyto822(Message *m, Header *h, char *p)
  {
  	p += strlen(h->type);
  	s_free(m->replyto822);
  	m->replyto822 = addr822(p);
  }
  
  static void
  mimeversion(Message *m, Header *h, char *p)
  {
  	p += strlen(h->type);
  	s_free(m->mimeversion);
  	m->mimeversion = addr822(p);
  }
  
  static void
  killtrailingwhite(char *p)
  {
  	char *e;
  
  	e = p + strlen(p) - 1;
  	while(e > p && isspace(*e))
  		*e-- = 0;
  }
  
  static void
  date822(Message *m, Header *h, char *p)
  {
  	p += strlen(h->type);
  	p = skipwhite(p);
  	s_free(m->date822);
  	m->date822 = s_copy(p);
  	p = s_to_c(m->date822);
  	killtrailingwhite(p);
  }
  
  static void
  subject822(Message *m, Header *h, char *p)
  {
  	p += strlen(h->type);
  	p = skipwhite(p);
  	s_free(m->subject822);
  	m->subject822 = s_copy(p);
  	p = s_to_c(m->subject822);
  	killtrailingwhite(p);
  }
  
  static void
  inreplyto822(Message *m, Header *h, char *p)
  {
  	p += strlen(h->type);
  	p = skipwhite(p);
  	s_free(m->inreplyto822);
  	m->inreplyto822 = s_copy(p);
  	p = s_to_c(m->inreplyto822);
  	killtrailingwhite(p);
  }
  
  static void
  messageid822(Message *m, Header *h, char *p)
  {
  	p += strlen(h->type);
  	p = skipwhite(p);
  	s_free(m->messageid822);
  	m->messageid822 = s_copy(p);
  	p = s_to_c(m->messageid822);
  	killtrailingwhite(p);
  }
  
  static int
  isattribute(char **pp, char *attr)
  {
  	char *p;
  	int n;
  
  	n = strlen(attr);
  	p = *pp;
  	if(cistrncmp(p, attr, n) != 0)
  		return 0;
  	p += n;
  	while(*p == ' ')
  		p++;
  	if(*p++ != '=')
  		return 0;
  	while(*p == ' ')
  		p++;
  	*pp = p;
  	return 1;
  }
  
  static void
  ctype(Message *m, Header *h, char *p)
  {
  	String *s;
  
  	p += h->len;
  	p = skipwhite(p);
  
  	p = getstring(p, m->type, 1);
  
  	while(*p){
  		if(isattribute(&p, "boundary")){
  			s = s_new();
  			p = getstring(p, s, 0);
  			m->boundary = s_reset(m->boundary);
  			s_append(m->boundary, "--");
  			s_append(m->boundary, s_to_c(s));
  			s_free(s);
  		} else if(cistrncmp(p, "multipart", 9) == 0){
  			/*
  			 *  the first unbounded part of a multipart message,
  			 *  the preamble, is not displayed or saved
  			 */
  		} else if(isattribute(&p, "name")){
  			if(m->filename == nil)
  				setfilename(m, p);
  		} else if(isattribute(&p, "charset")){
  			p = getstring(p, s_reset(m->charset), 0);
  		}
  
  		p = skiptosemi(p);
  	}
  }
  
  static void
  cencoding(Message *m, Header *h, char *p)
  {
  	p += h->len;
  	p = skipwhite(p);
  	if(cistrncmp(p, "base64", 6) == 0)
  		m->encoding = Ebase64;
  	else if(cistrncmp(p, "quoted-printable", 16) == 0)
  		m->encoding = Equoted;
  }
  
  static void
  cdisposition(Message *m, Header *h, char *p)
  {
  	p += h->len;
  	p = skipwhite(p);
  	while(*p){
  		if(cistrncmp(p, "inline", 6) == 0){
  			m->disposition = Dinline;
  		} else if(cistrncmp(p, "attachment", 10) == 0){
  			m->disposition = Dfile;
  		} else if(cistrncmp(p, "filename=", 9) == 0){
  			p += 9;
  			setfilename(m, p);
  		}
  		p = skiptosemi(p);
  	}
  
  }
  
  ulong msgallocd, msgfreed;
  
  Message*
  newmessage(Message *parent)
  {
  	/* static int id; jpc */
  	Message *m;
  
  	msgallocd++;
  
  	m = emalloc(sizeof(*m));
  	memset(m, 0, sizeof(*m));
  	m->disposition = Dnone;
  	m->type = s_copy("text/plain");
  	m->charset = s_copy("iso-8859-1");
  	m->id = newid();
  	if(parent)
  		sprint(m->name, "%d", ++(parent->subname));
  	if(parent == nil)
  		parent = m;
  	m->whole = parent;
  	m->hlen = -1;
  	return m;
  }
  
  /* delete a message from a mailbox */
  void
  delmessage(Mailbox *mb, Message *m)
  {
  	Message **l;
  	int i;
  
  	mb->vers++;
  	msgfreed++;
  
  	if(m->whole != m){
  		/* unchain from parent */
  		for(l = &m->whole->part; *l && *l != m; l = &(*l)->next)
  			;
  		if(*l != nil)
  			*l = m->next;
  
  		/* clear out of name lookup hash table */
  		if(m->whole->whole == m->whole)
  			hfree(PATH(mb->id, Qmbox), m->name);
  		else
  			hfree(PATH(m->whole->id, Qdir), m->name);
  		for(i = 0; i < Qmax; i++)
  			hfree(PATH(m->id, Qdir), dirtab[i]);
  	}
  
  	/* recurse through sub-parts */
  	while(m->part)
  		delmessage(mb, m->part);
  
  	/* free memory */
  	if(m->mallocd)
  		free(m->start);
  	if(m->hallocd)
  		free(m->header);
  	if(m->ballocd)
  		free(m->body);
  	s_free(m->unixfrom);
  	s_free(m->unixdate);
  	s_free(m->unixheader);
  	s_free(m->from822);
  	s_free(m->sender822);
  	s_free(m->to822);
  	s_free(m->bcc822);
  	s_free(m->cc822);
  	s_free(m->replyto822);
  	s_free(m->date822);
  	s_free(m->inreplyto822);
  	s_free(m->subject822);
  	s_free(m->messageid822);
  	s_free(m->addrs);
  	s_free(m->mimeversion);
  	s_free(m->sdigest);
  	s_free(m->boundary);
  	s_free(m->type);
  	s_free(m->charset);
  	s_free(m->filename);
  
  	free(m);
  }
  
  /* mark messages (identified by path) for deletion */
  void
  delmessages(int ac, char **av)
  {
  	Mailbox *mb;
  	Message *m;
  	int i, needwrite;
  
  	qlock(&mbllock);
  	for(mb = mbl; mb != nil; mb = mb->next)
  		if(strcmp(av[0], mb->name) == 0){
  			qlock(&mb->ql);
  			break;
  		}
  	qunlock(&mbllock);
  	if(mb == nil)
  		return;
  
  	needwrite = 0;
  	for(i = 1; i < ac; i++){
  		for(m = mb->root->part; m != nil; m = m->next)
  			if(strcmp(m->name, av[i]) == 0){
  				if(!m->deleted){
  					mailplumb(mb, m, 1);
  					needwrite = 1;
  					m->deleted = 1;
  					logmsg("deleting", m);
  				}
  				break;
  			}
  	}
  	if(needwrite)
  		syncmbox(mb, 1);
  	qunlock(&mb->ql);
  }
  
  /*
   *  the following are called with the mailbox qlocked
   */
  void
  msgincref(Message *m)
  {
  	m->refs++;
  }
  void
  msgdecref(Mailbox *mb, Message *m)
  {
  	m->refs--;
  	if(m->refs == 0 && m->deleted)
  		syncmbox(mb, 1);
  }
  
  /*
   *  the following are called with mbllock'd
   */
  void
  mboxincref(Mailbox *mb)
  {
  	assert(mb->refs > 0);
  	mb->refs++;
  }
  void
  mboxdecref(Mailbox *mb)
  {
  	assert(mb->refs > 0);
  	qlock(&mb->ql);
  	mb->refs--;
  	if(mb->refs == 0){
  		delmessage(mb, mb->root);
  		if(mb->ctl)
  			hfree(PATH(mb->id, Qmbox), "ctl");
  		if(mb->close)
  			(*mb->close)(mb);
  		free(mb);
  	} else
  		qunlock(&mb->ql);
  }
  
  int
  cistrncmp(char *a, char *b, int n)
  {
  	while(n-- > 0){
  		if(tolower(*a++) != tolower(*b++))
  			return -1;
  	}
  	return 0;
  }
  
  int
  cistrcmp(char *a, char *b)
  {
  	for(;;){
  		if(tolower(*a) != tolower(*b++))
  			return -1;
  		if(*a++ == 0)
  			break;
  	}
  	return 0;
  }
  
  static char*
  skipwhite(char *p)
  {
  	while(isspace(*p))
  		p++;
  	return p;
  }
  
  static char*
  skiptosemi(char *p)
  {
  	while(*p && *p != ';')
  		p++;
  	while(*p == ';' || isspace(*p))
  		p++;
  	return p;
  }
  
  static char*
  getstring(char *p, String *s, int dolower)
  {
  	s = s_reset(s);
  	p = skipwhite(p);
  	if(*p == '"'){
  		p++;
  		for(;*p && *p != '"'; p++)
  			if(dolower)
  				s_putc(s, tolower(*p));
  			else
  				s_putc(s, *p);
  		if(*p == '"')
  			p++;
  		s_terminate(s);
  
  		return p;
  	}
  
  	for(; *p && !isspace(*p) && *p != ';'; p++)
  		if(dolower)
  			s_putc(s, tolower(*p));
  		else
  			s_putc(s, *p);
  	s_terminate(s);
  
  	return p;
  }
  
  static void
  setfilename(Message *m, char *p)
  {
  	m->filename = s_reset(m->filename);
  	getstring(p, m->filename, 0);
  	for(p = s_to_c(m->filename); *p; p++)
  		if(*p == ' ' || *p == '\t' || *p == ';')
  			*p = '_';
  }
  
  /* */
  /* undecode message body */
  /* */
  void
  decode(Message *m)
  {
  	int i, len;
  	char *x;
  
  	if(m->decoded)
  		return;
  	switch(m->encoding){
  	case Ebase64:
  		len = m->bend - m->body;
  		i = (len*3)/4+1;	/* room for max chars + null */
  		x = emalloc(i);
  		len = dec64((uchar*)x, i, m->body, len);
  		if(m->ballocd)
  			free(m->body);
  		m->body = x;
  		m->bend = x + len;
  		m->ballocd = 1;
  		break;
  	case Equoted:
  		len = m->bend - m->body;
  		x = emalloc(len+2);	/* room for null and possible extra nl */
  		len = decquoted(x, m->body, m->bend);
  		if(m->ballocd)
  			free(m->body);
  		m->body = x;
  		m->bend = x + len;
  		m->ballocd = 1;
  		break;
  	default:
  		break;
  	}
  	m->decoded = 1;
  }
  
  /* convert latin1 to utf */
  void
  convert(Message *m)
  {
  	int len;
  	char *x;
  
  	/* don't convert if we're not a leaf, not text, or already converted */
  	if(m->converted)
  		return;
  	if(m->part != nil)
  		return;
  	if(cistrncmp(s_to_c(m->type), "text", 4) != 0)
  		return;
  
  	if(cistrcmp(s_to_c(m->charset), "us-ascii") == 0 ||
  	   cistrcmp(s_to_c(m->charset), "iso-8859-1") == 0){
  		len = is8bit(m);
  		if(len > 0){
  			len = 2*len + m->bend - m->body + 1;
  			x = emalloc(len);
  			len = latin1toutf(x, m->body, m->bend);
  			if(m->ballocd)
  				free(m->body);
  			m->body = x;
  			m->bend = x + len;
  			m->ballocd = 1;
  		}
  	} else if(cistrcmp(s_to_c(m->charset), "iso-8859-2") == 0){
  		len = xtoutf("8859-2", &x, m->body, m->bend);
  		if(len != 0){
  			if(m->ballocd)
  				free(m->body);
  			m->body = x;
  			m->bend = x + len;
  			m->ballocd = 1;
  		}
  	} else if(cistrcmp(s_to_c(m->charset), "iso-8859-15") == 0){
  		len = xtoutf("8859-15", &x, m->body, m->bend);
  		if(len != 0){
  			if(m->ballocd)
  				free(m->body);
  			m->body = x;
  			m->bend = x + len;
  			m->ballocd = 1;
  		}
  	} else if(cistrcmp(s_to_c(m->charset), "big5") == 0){
  		len = xtoutf("big5", &x, m->body, m->bend);
  		if(len != 0){
  			if(m->ballocd)
  				free(m->body);
  			m->body = x;
  			m->bend = x + len;
  			m->ballocd = 1;
  		}
  	} else if(cistrcmp(s_to_c(m->charset), "iso-2022-jp") == 0){
  		len = xtoutf("jis", &x, m->body, m->bend);
  		if(len != 0){
  			if(m->ballocd)
  				free(m->body);
  			m->body = x;
  			m->bend = x + len;
  			m->ballocd = 1;
  		}
  	} else if(cistrcmp(s_to_c(m->charset), "windows-1257") == 0
  			|| cistrcmp(s_to_c(m->charset), "windows-1252") == 0){
  		len = is8bit(m);
  		if(len > 0){
  			len = 2*len + m->bend - m->body + 1;
  			x = emalloc(len);
  			len = windows1257toutf(x, m->body, m->bend);
  			if(m->ballocd)
  				free(m->body);
  			m->body = x;
  			m->bend = x + len;
  			m->ballocd = 1;
  		}
  	} else if(cistrcmp(s_to_c(m->charset), "windows-1251") == 0){
  		len = xtoutf("cp1251", &x, m->body, m->bend);
  		if(len != 0){
  			if(m->ballocd)
  				free(m->body);
  			m->body = x;
  			m->bend = x + len;
  			m->ballocd = 1;
  		}
  	} else if(cistrcmp(s_to_c(m->charset), "koi8-r") == 0){
  		len = xtoutf("koi8", &x, m->body, m->bend);
  		if(len != 0){
  			if(m->ballocd)
  				free(m->body);
  			m->body = x;
  			m->bend = x + len;
  			m->ballocd = 1;
  		}
  	}
  
  	m->converted = 1;
  }
  
  enum
  {
  	Self=	1,
  	Hex=	2
  };
  uchar	tableqp[256];
  
  static void
  initquoted(void)
  {
  	int c;
  
  	memset(tableqp, 0, 256);
  	for(c = ' '; c <= '<'; c++)
  		tableqp[c] = Self;
  	for(c = '>'; c <= '~'; c++)
  		tableqp[c] = Self;
  	tableqp['\t'] = Self;
  	tableqp['='] = Hex;
  }
  
  static int
  hex2int(int x)
  {
  	if(x >= '0' && x <= '9')
  		return x - '0';
  	if(x >= 'A' && x <= 'F')
  		return (x - 'A') + 10;
  	if(x >= 'a' && x <= 'f')
  		return (x - 'a') + 10;
  	return 0;
  }
  
  static char*
  decquotedline(char *out, char *in, char *e)
  {
  	int c, soft;
  
  	/* dump trailing white space */
  	while(e >= in && (*e == ' ' || *e == '\t' || *e == '\r' || *e == '\n'))
  		e--;
  
  	/* trailing '=' means no newline */
  	if(*e == '='){
  		soft = 1;
  		e--;
  	} else
  		soft = 0;
  
  	while(in <= e){
  		c = (*in++) & 0xff;
  		switch(tableqp[c]){
  		case Self:
  			*out++ = c;
  			break;
  		case Hex:
  			c = hex2int(*in++)<<4;
  			c |= hex2int(*in++);
  			*out++ = c;
  			break;
  		}
  	}
  	if(!soft)
  		*out++ = '\n';
  	*out = 0;
  
  	return out;
  }
  
  int
  decquoted(char *out, char *in, char *e)
  {
  	char *p, *nl;
  
  	if(tableqp[' '] == 0)
  		initquoted();
  
  	p = out;
  	while((nl = strchr(in, '\n')) != nil && nl < e){
  		p = decquotedline(p, in, nl);
  		in = nl + 1;
  	}
  	if(in < e)
  		p = decquotedline(p, in, e-1);
  
  	/* make sure we end with a new line */
  	if(*(p-1) != '\n'){
  		*p++ = '\n';
  		*p = 0;
  	}
  
  	return p - out;
  }
  
  #if 0 /* jpc */
  static char*
  lowercase(char *p)
  {
  	char *op;
  	int c;
  
  	for(op = p; c = *p; p++)
  		if(isupper(c))
  			*p = tolower(c);
  	return op;
  }
  #endif
  
  /*
   *  return number of 8 bit characters
   */
  static int
  is8bit(Message *m)
  {
  	int count = 0;
  	char *p;
  
  	for(p = m->body; p < m->bend; p++)
  		if(*p & 0x80)
  			count++;
  	return count;
  }
  
  /* translate latin1 directly since it fits neatly in utf */
  int
  latin1toutf(char *out, char *in, char *e)
  {
  	Rune r;
  	char *p;
  
  	p = out;
  	for(; in < e; in++){
  		r = (*in) & 0xff;
  		p += runetochar(p, &r);
  	}
  	*p = 0;
  	return p - out;
  }
  
  /* translate any thing else using the tcs program */
  int
  xtoutf(char *charset, char **out, char *in, char *e)
  {
  	char *av[4];
  	int totcs[2];
  	int fromtcs[2];
  	int n, len, sofar;
  	char *p;
  
  	len = e-in+1;
  	sofar = 0;
  	*out = p = malloc(len+1);
  	if(p == nil)
  		return 0;
  
  	av[0] = charset;
  	av[1] = "-f";
  	av[2] = charset;
  	av[3] = 0;
  	if(pipe(totcs) < 0)
  		return 0;
  	if(pipe(fromtcs) < 0){
  		close(totcs[0]); close(totcs[1]);
  		return 0;
  	}
  	switch(rfork(RFPROC|RFFDG|RFNOWAIT)){
  	case -1:
  		close(fromtcs[0]); close(fromtcs[1]);
  		close(totcs[0]); close(totcs[1]);
  		return 0;
  	case 0:
  		close(fromtcs[0]); close(totcs[1]);
  		dup(fromtcs[1], 1);
  		dup(totcs[0], 0);
  		close(fromtcs[1]); close(totcs[0]);
  		dup(open("/dev/null", OWRITE), 2);
  		/*jpc exec("/bin/tcs", av); */
  		exec(unsharp("#9/bin/tcs"), av);
  		/* _exits(0); */
  		threadexits(nil);
  	default:
  		close(fromtcs[1]); close(totcs[0]);
  		switch(rfork(RFPROC|RFFDG|RFNOWAIT)){
  		case -1:
  			close(fromtcs[0]); close(totcs[1]);
  			return 0;
  		case 0:
  			close(fromtcs[0]);
  			while(in < e){
  				n = write(totcs[1], in, e-in);
  				if(n <= 0)
  					break;
  				in += n;
  			}
  			close(totcs[1]);
  			/* _exits(0); */
  			threadexits(nil);
  		default:
  			close(totcs[1]);
  			for(;;){
  				n = read(fromtcs[0], &p[sofar], len-sofar);
  				if(n <= 0)
  					break;
  				sofar += n;
  				p[sofar] = 0;
  				if(sofar == len){
  					len += 1024;
  					*out = p = realloc(p, len+1);
  					if(p == nil)
  						return 0;
  				}
  			}
  			close(fromtcs[0]);
  			break;
  		}
  		break;
  	}
  	return sofar;
  }
  
  enum {
  	Winstart= 0x7f,
  	Winend= 0x9f
  };
  
  Rune winchars[] = {
  	L'•',
  	L'•', L'•', L'‚', L'ƒ', L'„', L'…', L'†', L'‡',
  	L'ˆ', L'‰', L'Š', L'‹', L'Œ', L'•', L'•', L'•',
  	L'•', L'‘', L'’', L'“', L'”', L'•', L'–', L'—',
  	L'˜', L'™', L'š', L'›', L'œ', L'•', L'•', L'Ÿ'
  };
  
  int
  windows1257toutf(char *out, char *in, char *e)
  {
  	Rune r;
  	char *p;
  
  	p = out;
  	for(; in < e; in++){
  		r = (*in) & 0xff;
  		if(r >= 0x7f && r <= 0x9f)
  			r = winchars[r-0x7f];
  		p += runetochar(p, &r);
  	}
  	*p = 0;
  	return p - out;
  }
  
  void *
  emalloc(ulong n)
  {
  	void *p;
  
  	p = mallocz(n, 1);
  	if(!p){
  		fprint(2, "%s: out of memory alloc %lud\n", argv0, n);
  		threadexits("out of memory");
  	}
  	setmalloctag(p, getcallerpc(&n));
  	return p;
  }
  
  void *
  erealloc(void *p, ulong n)
  {
  	if(n == 0)
  		n = 1;
  	p = realloc(p, n);
  	if(!p){
  		fprint(2, "%s: out of memory realloc %lud\n", argv0, n);
  		threadexits("out of memory");
  	}
  	setrealloctag(p, getcallerpc(&p));
  	return p;
  }
  
  void
  mailplumb(Mailbox *mb, Message *m, int delete)
  {
  	Plumbmsg p;
  	Plumbattr a[7];
  	char buf[256];
  	int ai;
  	char lenstr[10], *from, *subject, *date;
  	static int fd = -1;
  
  	if(m->subject822 == nil)
  		subject = "";
  	else
  		subject = s_to_c(m->subject822);
  
  	if(m->from822 != nil)
  		from = s_to_c(m->from822);
  	else if(m->unixfrom != nil)
  		from = s_to_c(m->unixfrom);
  	else
  		from = "";
  
  	if(m->unixdate != nil)
  		date = s_to_c(m->unixdate);
  	else
  		date = "";
  
  	sprint(lenstr, "%ld", m->end-m->start);
  
  	if(biffing && !delete)
  		print("[ %s / %s / %s ]\n", from, subject, lenstr);
  
  	if(!plumbing)
  		return;
  
  	if(fd < 0)
  		fd = plumbopen("send", OWRITE);
  	if(fd < 0)
  		return;
  
  	p.src = "mailfs";
  	p.dst = "seemail";
  	p.wdir = "/mail/fs";
  	p.type = "text";
  
  	ai = 0;
  	a[ai].name = "filetype";
  	a[ai].value = "mail";
  
  	a[++ai].name = "sender";
  	a[ai].value = from;
  	a[ai-1].next = &a[ai];
  
  	a[++ai].name = "length";
  	a[ai].value = lenstr;
  	a[ai-1].next = &a[ai];
  
  	a[++ai].name = "mailtype";
  	a[ai].value = delete?"delete":"new";
  	a[ai-1].next = &a[ai];
  
  	a[++ai].name = "date";
  	a[ai].value = date;
  	a[ai-1].next = &a[ai];
  
  	if(m->sdigest){
  		a[++ai].name = "digest";
  		a[ai].value = s_to_c(m->sdigest);
  		a[ai-1].next = &a[ai];
  	}
  
  	a[ai].next = nil;
  
  	p.attr = a;
  	snprint(buf, sizeof(buf), "%s/%s/%s",
  		mntpt, mb->name, m->name);
  	p.ndata = strlen(buf);
  	p.data = buf;
  
  	plumbsend(fd, &p);
  }
  
  /* */
  /* count the number of lines in the body (for imap4) */
  /* */
  void
  countlines(Message *m)
  {
  	int i;
  	char *p;
  
  	i = 0;
  	for(p = strchr(m->rbody, '\n'); p != nil && p < m->rbend; p = strchr(p+1, '\n'))
  		i++;
  	sprint(m->lines, "%d", i);
  }
  
  char *LOG = "fs";
  
  void
  logmsg(char *s, Message *m)
  {
  	int pid;
  
  	if(!logging)
  		return;
  	pid = getpid();
  	if(m == nil)
  		syslog(0, LOG, "%s.%d: %s", user, pid, s);
  	else
  		syslog(0, LOG, "%s.%d: %s msg from %s digest %s",
  			user, pid, s,
  			m->from822 ? s_to_c(m->from822) : "?",
  			s_to_c(m->sdigest));
  }
  
  /*
   *  squeeze nulls out of the body
   */
  static void
  nullsqueeze(Message *m)
  {
  	char *p, *q;
  
  	q = memchr(m->body, 0, m->end-m->body);
  	if(q == nil)
  		return;
  
  	for(p = m->body; q < m->end; q++){
  		if(*q == 0)
  			continue;
  		*p++ = *q;
  	}
  	m->bend = m->rbend = m->end = p;
  }
  
  
  /* */
  /* convert an RFC822 date into a Unix style date */
  /* for when the Unix From line isn't there (e.g. POP3). */
  /* enough client programs depend on having a Unix date */
  /* that it's easiest to write this conversion code once, right here. */
  /* */
  /* people don't follow RFC822 particularly closely, */
  /* so we use strtotm, which is a bunch of heuristics. */
  /* */
  
  extern int strtotm(char*, Tm*);
  String*
  date822tounix(char *s)
  {
  	char *p, *q;
  	Tm tm;
  
  	if(strtotm(s, &tm) < 0)
  		return nil;
  
  	p = asctime(&tm);
  	if(q = strchr(p, '\n'))
  		*q = '\0';
  	return s_copy(p);
  }
  