/* Copyright (c) 2017, Computer History Museum 
All rights reserved. 
Redistribution and use in source and binary forms, with or without modification, are permitted (subject to 
the limitations in the disclaimer below) provided that the following conditions are met: 
 * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 
 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following 
   disclaimer in the documentation and/or other materials provided with the distribution. 
 * Neither the name of Computer History Museum nor the names of its contributors may be used to endorse or promote products 
   derived from this software without specific prior written permission. 
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE 
COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 
DAMAGE. */

#ifdef	IMAP

/*
 * Program:	RFC-822 routines (originally from SMTP)
 *
 * Author:	Mark Crispin
 *		Networks and Distributed Computing
 *		Computing & Communications
 *		University of Washington
 *		Administration Building, AG-44
 *		Seattle, WA  98195
 *		Internet: MRC@CAC.Washington.EDU
 *
 * Date:	27 July 1988
 * Last Edited:	20 February 1997
 *
 * Sponsorship:	The original version of this work was developed in the
 *		Symbolic Systems Resources Group of the Knowledge Systems
 *		Laboratory at Stanford University in 1987-88, and was funded
 *		by the Biomedical Research Technology Program of the National
 *		Institutes of Health under grant number RR-00785.
 *
 * Original version Copyright 1988 by The Leland Stanford Junior University
 * Copyright 1997 by the University of Washington
 *
 *  Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted, provided
 * that the above copyright notices appear in all copies and that both the
 * above copyright notices and this permission notice appear in supporting
 * documentation, and that the name of the University of Washington or The
 * Leland Stanford Junior University not be used in advertising or publicity
 * pertaining to distribution of the software without specific, written prior
 * permission.  This software is made available "as is", and
 * THE UNIVERSITY OF WASHINGTON AND THE LELAND STANFORD JUNIOR UNIVERSITY
 * DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED, WITH REGARD TO THIS SOFTWARE,
 * INCLUDING WITHOUT LIMITATION ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE, AND IN NO EVENT SHALL THE UNIVERSITY OF
 * WASHINGTON OR THE LELAND STANFORD JUNIOR UNIVERSITY BE LIABLE FOR ANY
 * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
 * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
 * CONTRACT, TORT (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 */

#include "mail.h"
#include "osdep.h"
#include "rfc822.h"
#include "misc.h"

/* RFC-822 static data */


char *errhst = ERRHOST;		/* syntax error host string */


/* Body formats constant strings, must match definitions in mail.h */

char *body_types[TYPEMAX+1] = {
  "TEXT", "MULTIPART", "MESSAGE", "APPLICATION", "AUDIO", "IMAGE", "VIDEO",
  "MODEL", "X-UNKNOWN"
};


char *body_encodings[ENCMAX+1] = {
  "7BIT", "8BIT", "BINARY", "BASE64", "QUOTED-PRINTABLE", "X-UNKNOWN"
};


/* Token delimiting special characters */

				/* full RFC-822 specials */
const char *rspecials =  "()<>@,;:\\\"[].";
				/* body token specials */
const char *tspecials = " ()<>@,;:\\\"[]./?=";


/* Once upon a time, CSnet had a mailer which assigned special semantics to
 * dot in e-mail addresses.  For the sake of that mailer, dot was added to
 * the RFC-822 definition of `specials', even though it had numerous bad side
 * effects:
 *   1)	It broke mailbox names on systems which had dots in user names, such as
 *	Multics and TOPS-20.  RFC-822's syntax rules require that `Admin . MRC'
 *	be considered equivalent to `Admin.MRC'.  Fortunately, few people ever
 *	tried this in practice.
 *   2) It required that all personal names with an initial be quoted, a widely
 *	detested user interface misfeature.
 *   3)	It made the parsing of host names be non-atomic for no good reason.
 * To work around these problems, the following alternate specials lists are
 * defined.  hspecials and wspecials are used in lieu of rspecials, and
 * ptspecials are used in lieu of tspecials.  These alternate specials lists
 * make the parser work a lot better in the real world.  It ain't politically
 * correct, but it lets the users get their job done!
 */

				/* parse-word specials */
const char *wspecials = " ()<>@,;:\\\"[]";
				/* parse-token specials for parsing */
const char *ptspecials = " ()<>@,;:\\\"[]/?=";


/* RFC822 writing routines */


/* Write RFC822 IMAPheader from message structure
 * Accepts: scratch buffer to write into
 *	    message envelope
 *	    message body
 */

void rfc822_header (char *IMAPheader,ENVELOPE *env,IMAPBODY *body)
{
	if (env->remail)	/* if remailing */
	{
		long i = strlen (env->remail);
	
		/* flush extra blank line */
		if (i > 4 && env->remail[i-4] == '\015') env->remail[i-2] = '\0';
		strcpy (IMAPheader,env->remail);/* start with remail IMAPheader */
	}
	else *IMAPheader = '\0';		/* else initialize IMAPheader to null */
	
	rfc822_header_line (&IMAPheader,"Newsgroups",env,env->newsgroups);
	rfc822_header_line (&IMAPheader,"Date",env,env->date);
	rfc822_address_line (&IMAPheader,"From",env,env->from);
	rfc822_address_line (&IMAPheader,"Sender",env,env->sender);
	rfc822_address_line (&IMAPheader,"Reply-To",env,env->reply_to);
	rfc822_header_line (&IMAPheader,"Subject",env,env->subject);
	
	if (env->bcc && !(env->to || env->cc)) strcat (IMAPheader,"To: undisclosed recipients: ;\015\012");
	
	rfc822_address_line (&IMAPheader,"To",env,env->to);
	rfc822_address_line (&IMAPheader,"cc",env,env->cc);
	
	/* bcc's are never written...
	* rfc822_address_line (&IMAPheader,"bcc",env,env->bcc);
	*/
	rfc822_header_line (&IMAPheader,"In-Reply-To",env,env->in_reply_to);
	rfc822_header_line (&IMAPheader,"Message-ID",env,env->message_id);
	rfc822_header_line (&IMAPheader,"Followup-to",env,env->followup_to);
	rfc822_header_line (&IMAPheader,"References",env,env->references);
	
	if (body && !env->remail)	/* not if remail or no body structure */
	{
		strcat (IMAPheader,"MIME-Version: 1.0\015\012");
		rfc822_write_body_header (&IMAPheader,body);
	}
	
	strcat (IMAPheader,"\015\012");	/* write terminating blank line */
}


/* Write RFC822 address from IMAPheader line
 * Accepts: pointer to destination string pointer
 *	    pointer to IMAPheader type
 *	    message to interpret
 *	    address to interpret
 */

void rfc822_address_line (char **IMAPheader,char *type,ENVELOPE *env,ADDRESS *adr)
{
	char *t,tmp[MAILTMPLEN];
	long i,len,n = 0;
	char *s = (*IMAPheader += strlen (*IMAPheader));
	
	if (adr) 	/* do nothing if no addresses */
	{
		if (env && env->remail) strcat (s,"ReSent-");
		strcat (s,type);		/* write IMAPheader name */
		strcat (s,": ");
		s += (len = strlen (s));	/* initial string length */
		do	/* run down address list */
		{
			*(t = tmp) = '\0';	/* initially empty string */
			
			/* start of group? */
			if (adr->mailbox && !adr->host) 
			{
				/* yes, write group name */
				rfc822_cat (t,adr->mailbox,rspecials);
				strcat (t,": ");	/* write group identifier */
				n++;			/* in a group, suppress expansion */
			}
			else	/* not start of group */
			{
				if (!adr->host && n)	/* end of group? */
				{
					strcat (t,";");	/* write close delimiter */
					n--;			/* no longer in a group */
				}
				else if (!n)	/* only print if not inside a group */
				{
					/* simple case? */
					if (!(adr->personal || adr->adl)) rfc822_address (t,adr);
					else	/* no, must use phrase <route-addr> form */
					{
						if (adr->personal) rfc822_cat (t,adr->personal,rspecials);
						strcat (t," <");	/* write address delimiter */
		
						/* write address */
						rfc822_address (t,adr);
						strcat (t,">");	/* closing delimiter */
					}
				}
				/* write delimiter for next recipient */
				if (!n && adr->next && adr->next->mailbox) strcat (t,", ");
			}
			
			/* if string would overflow */
			if ((len += (i = strlen (t))) > 78) 
			{
				len = 4 + i;		/* continue it on a new line */
				*s++ = '\015'; *s++ = '\012';
				*s++ = ' '; *s++ = ' '; *s++ = ' '; *s++ = ' ';
			}
			
			while (*t) *s++ = *t++;	/* write this address */
			
		} while (adr = adr->next);
		
		/* tie off IMAPheader line */
		*s++ = '\015'; *s++ = '\012'; *s = '\0';
		*IMAPheader = s;		/* set return value */
	}
}


/* Write RFC822 text from IMAPheader line
 * Accepts: pointer to destination string pointer
 *	    pointer to IMAPheader type
 *	    message to interpret
 *	    pointer to text
 */

void rfc822_header_line (char **IMAPheader,char *type,ENVELOPE *env,char *text)
{
	if (text) sprintf ((*IMAPheader += strlen (*IMAPheader)),"%s%s: %s\015\012",env->remail ? "ReSent-" : "",type,text);
}


/* Write RFC822 address
 * Accepts: pointer to destination string
 *	    address to interpret
 */

void rfc822_write_address (char *dest,ADDRESS *adr)
{
	while (adr) 
	{
		/* start of group? */
		if (adr->mailbox && !adr->host) 
		{
			/* yes, write group name */
			rfc822_cat (dest,adr->mailbox,rspecials);
			strcat (dest,": ");	/* write group identifier */
			adr = adr->next;		/* move to next address block */
		}
		else 	/* end of group? */
		{
			if (!adr->host) strcat (dest,";");
			/* simple case? */
			else if (!(adr->personal || adr->adl)) rfc822_address (dest,adr);
			else 	/* no, must use phrase <route-addr> form */
			{
				if (adr->personal)	/* in case have adl but no personal name */
				{
					rfc822_cat (dest,adr->personal,rspecials);
					strcat (dest," ");
				}
			
				strcat (dest,"<");	/* write address delimiter */
				rfc822_address (dest,adr);/* write address */
				strcat (dest,">");	/* closing delimiter */
			}
			/* delimit if there is one */
			if ((adr = adr->next) && adr->mailbox) strcat (dest,", ");
		}
	}
}


/* Write RFC822 route-address to string
 * Accepts: pointer to destination string
 *	    address to interpret
 */

void rfc822_address (char *dest,ADDRESS *adr)
{
	if (adr && adr->host)	/* no-op if no address */
	{
		if (adr->adl)	/* have an A-D-L? */
		{
			strcat (dest,adr->adl);
			strcat (dest,":");
		}
	
		/* write mailbox name */
		rfc822_cat (dest,adr->mailbox,wspecials);
	
		if (*adr->host != '@')	/* unless null host (HIGHLY discouraged!) */
		{
			strcat (dest,"@");	/* host delimiter */
			strcat (dest,adr->host);	/* write host name */
		}
	}
}


/* Concatenate RFC822 string
 * Accepts: pointer to destination string
 *	    pointer to string to concatenate
 *	    list of special characters
 */

void rfc822_cat (char *dest,char *src,const char *specials)
{
	char *s;
	
	if (strpbrk (src,specials))	/* any specials present? */
	{
		strcat (dest,"\"");		/* opening quote */
		
		/* truly bizarre characters in there? */
		while (s = strpbrk (src,"\\\"")) 
		{
			strncat (dest,src,s-src);	/* yes, output leader */
			strcat (dest,"\\");	/* quoting */
			strncat (dest,s,1);	/* output the bizarre character */
			src = ++s;		/* continue after the bizarre character */
		}
		
		if (*src) strcat (dest,src);/* output non-bizarre string */
		
		strcat (dest,"\"");		/* closing quote */
	}
	else strcat (dest,src);	/* otherwise it's the easy case */
}


/* Write body content IMAPheader
 * Accepts: pointer to destination string pointer
 *	    pointer to body to interpret
 */

void rfc822_write_body_header (char **dst,IMAPBODY *body)
{
	char *s;
	STRINGLIST *stl;
	PARAMETER *param = body->parameter;
	
	sprintf (*dst += strlen (*dst),"Content-Type: %s",body_types[body->type]);
	s = body->subtype ? body->subtype : rfc822_default_subtype (body->type);
	sprintf (*dst += strlen (*dst),"/%s",s);
	if (param)
		do 
		{
			sprintf (*dst += strlen (*dst),"; %s=",param->attribute);
			rfc822_cat (*dst,param->value,tspecials);
		} while (param = param->next);
	else if (body->type == TYPETEXT) strcat (*dst,"; CHARSET=US-ASCII");
	
	strcpy (*dst += strlen (*dst),"\015\012");
	if (body->encoding)		/* note: encoding 7BIT never output! */
		sprintf (*dst += strlen (*dst),"Content-Transfer-Encoding: %s\015\012",body_encodings[body->encoding]);
	if (body->id) sprintf (*dst += strlen (*dst),"Content-ID: %s\015\012", body->id);
	if (body->description) sprintf (*dst += strlen (*dst),"Content-Description: %s\015\012", body->description);
	if (body->md5) sprintf (*dst += strlen (*dst),"Content-MD5: %s\015\012",body->md5);
	if (stl = body->language) 
	{
		strcpy (*dst += strlen (*dst),"Content-Language: ");
		do 
		{
			rfc822_cat (*dst,stl->text.data,tspecials);
			if (stl = stl->next) strcat (*dst += strlen (*dst),", ");
		} while (stl);
	}
	if (body->disposition.type) 
	{
		sprintf (*dst += strlen (*dst),"Content-Disposition: %s",
		body->disposition.type);
		if (param = body->disposition.parameter) 
			do 
			{
				sprintf (*dst += strlen (*dst),"; %s=",param->attribute);
				rfc822_cat (*dst,param->value,tspecials);
			} while (param = param->next);
		strcpy (*dst += strlen (*dst),"\015\012");
	}
}


/* Subtype defaulting (a no-no, but regretably necessary...)
 * Accepts: type code
 * Returns: default subtype name
 */

char *rfc822_default_subtype (unsigned short type)
{
	switch (type) 
	{
		case TYPETEXT:		/* default is TEXT/PLAIN */
			return "PLAIN";
	
		case TYPEMULTIPART:		/* default is MULTIPART/MIXED */
			return "MIXED";
	
		case TYPEMESSAGE:		/* default is MESSAGE/RFC822 */
			return "RFC822";
	
		case TYPEAPPLICATION:		/* default is APPLICATION/OCTET-STREAM */
			return "OCTET-STREAM";
	
		case TYPEAUDIO:		/* default is AUDIO/BASIC */
			return "BASIC";
		
		default:			/* others have no default subtype */
			return "UNKNOWN";
	}
}


/* RFC822 parsing routines */


/* Parse an RFC822 message
 * Accepts: pointer to return envelope
 *	    pointer to return body
 *	    pointer to IMAPheader
 *	    IMAPheader byte count
 *	    pointer to body stringstruct
 *	    pointer to local host name
 */

void rfc822_parse_msg (ENVELOPE **en,IMAPBODY **bdy,char *s,unsigned long i,STRING *bs,char *host)
{
	char c,*t,*d;
	char *tmp = (char *) fs_get ((size_t) i + 100);
	ENVELOPE *env = (*en = mail_newenvelope ());
	IMAPBODY *body = bdy ? (*bdy = mail_newbody ()) : NIL;
	long MIMEp = NIL;		/* flag that MIME semantics are in effect */
	long PathP = NIL;		/* flag that a Path: was seen */
	
	if (!tmp) return;
	
	while (i && *s != '\n')	/* until end of IMAPheader */
	{
		t = tmp;			/* initialize buffer pointer */
		c = ' ';			/* and previous character */
		while (i && c)		/* collect text until logical end of line */
		{
			switch (c = *s++)	/* slurp a character */
			{
				case '\015':		/* return, possible end of logical line */
					if (*s == '\n') break;	/* ignore if LF follows */
				
				case '\012':		/* LF, possible end of logical line */
					/* tie off unless next line starts with WS */
					if (*s != ' ' && *s != '\t') *t++ = c = '\0';
					break;
			
				case '\t':		/* tab */
					*t++ = ' ';		/* coerce to space */
					break;
				
				default:			/* all other characters */
					*t++ = c;		/* insert the character into the line */
					break;
			}
			if (!--i) *t++ = '\0';	/* see if end of IMAPheader */
		}
		
		/* find IMAPheader item type */
		if (t = d = strchr (tmp,':')) 
		{
			*d++ = '\0';		/* tie off IMAPheader item, point at its data */
			while (*d == ' ') d++;	/* flush whitespace */
			while ((tmp < t--) && (*t == ' ')) *t = '\0';
			switch (*ucase (tmp))	/* dispatch based on first character */
			{
				case '>':			/* possible >From: */
					if (!strcmp (tmp+1,"FROM")) rfc822_parse_adrlist (&env->from,d,host);
					break;
				
				case 'B':			/* possible bcc: */
					if (!strcmp (tmp+1,"CC")) rfc822_parse_adrlist (&env->bcc,d,host);
					break;
				
				case 'C':			/* possible cc: or Content-<mumble>*/
					if (!strcmp (tmp+1,"C")) rfc822_parse_adrlist (&env->cc,d,host);
					else if ((tmp[1] == 'O') && (tmp[2] == 'N') && (tmp[3] == 'T') 
						   && (tmp[4] == 'E') && (tmp[5] == 'N') && (tmp[6] == 'T') 
						   && (tmp[7] == '-') && body && (MIMEp || (search (s-1,i,"\012MIME-Version",(long) 13))))
							rfc822_parse_content_header (body,tmp+8,d);
					break;
				
				case 'D':			/* possible Date: */
					if (!env->date && !strcmp (tmp+1,"ATE")) env->date = cpystr (d);
					break;
				
				case 'F':			/* possible From: */
					if (!strcmp (tmp+1,"ROM")) rfc822_parse_adrlist (&env->from,d,host);
					else if (!strcmp (tmp+1,"OLLOWUP-TO")) 
					{
						t = env->followup_to = (char *) fs_get (1 + strlen (d));
						if (!t) return;
						while (c = *d++) if (c != ' ') *t++ = c;
						*t++ = '\0';
					}
					break;
				
				case 'I':			/* possible In-Reply-To: */
					if (!env->in_reply_to && !strcmp (tmp+1,"N-REPLY-TO")) env->in_reply_to = cpystr (d);
					break;
				
				case 'M':			/* possible Message-ID: or MIME-Version: */
					if (!env->message_id && !strcmp (tmp+1,"ESSAGE-ID")) env->message_id = cpystr (d);
					else if (!strcmp (tmp+1,"IME-VERSION")) 
					{
						/* tie off at end of phrase */
						if (t = rfc822_parse_phrase (d)) *t = '\0';
						rfc822_skipws (&d);	/* skip whitespace */
						/* known version? */
						if (strcmp (d,"1.0") && strcmp (d,"RFC-XXXX"))
						mm_log ("Warning: message has unknown MIME version",PARSE);
						MIMEp = T;		/* note that we are MIME */
					}
					break;
				
				case 'N':			/* possible Newsgroups: */
					if (!env->newsgroups && !strcmp (tmp+1,"EWSGROUPS")) 
					{
						t = env->newsgroups = (char *) fs_get (1 + strlen (d));
						if (!t) return;
						while (c = *d++) if (c != ' ') *t++ = c;
						*t++ = '\0';
					}
					break;
			
				case 'P':			/* possible Path: */
					if (!strcmp (tmp+1,"ATH")) PathP = T;
					break;
				
				case 'R':			/* possible Reply-To: */
					if (!strcmp (tmp+1,"EPLY-TO")) rfc822_parse_adrlist (&env->reply_to,d,host);
					else if (!env->references && !strcmp (tmp+1,"EFERENCES")) env->references = cpystr (d);
					break;
				
				case 'S':			/* possible Subject: or Sender: */
					if (!env->subject && !strcmp (tmp+1,"UBJECT")) env->subject = cpystr (d);
					else if (!strcmp (tmp+1,"ENDER")) rfc822_parse_adrlist (&env->sender,d,host);
					break;
				
				case 'T':			/* possible To: */
					if (!strcmp (tmp+1,"O")) rfc822_parse_adrlist (&env->to,d,host);
					break;
				
				default:
					break;
			}
		}
	}
	
	/* We require a Path: IMAPheader and/or a Message-ID belonging to a known
	* winning mail program, in order to believe Newsgroups:.  This is because
	* of the unfortunate existance of certain cretins who believe that it
	* is reasonable to transmit messages via SMTP with a "Newsgroups" IMAPheader
	* that were not actually posted to any of the named newsgroups.
	* The authors of other high-quality email/news software are encouraged to
	* use similar methods to indentify messages as coming from their software,
	* and having done so, to tell us so they too can be blessed in this list.
	*/
	if (env->newsgroups && !PathP && env->message_id 
	  && strncmp (env->message_id,"<Pine.",6) 
	  && strncmp (env->message_id,"<MS-C.",6) 
	  && strncmp (env->message_id,"<MailManager.",13) 
	  && strncmp (env->message_id,"<EasyMail.",11) 
	  && strncmp (env->message_id,"<ML-",4)) 
	{
		sprintf (tmp,"Probable bogus newsgroup list \"%s\" in \"%s\" ignored",
		env->newsgroups,env->message_id);
		mm_log (tmp,PARSE);
		fs_give ((void **) &env->newsgroups);
	}
	
	fs_give ((void **) &tmp);	/* done with scratch buffer */
	
	/* default Sender: and Reply-To: to From: */
	if (!env->sender) env->sender = rfc822_cpy_adr (env->from);
	if (!env->reply_to) env->reply_to = rfc822_cpy_adr (env->from);
	
	/* now parse the body */
	if (body) rfc822_parse_content (body,bs,host);
}

/* Parse a message body content
 * Accepts: pointer to body structure
 *	    body string
 *	    pointer to local host name
 */

void rfc822_parse_content (IMAPBODY *body,STRING *bs,char *h)
{
	char c,c1,*s,*s1;
	int f;
	unsigned long i,j,k,m;
	PARAMETER *param;
	PART *part = NIL;
	
	if (!body->subtype)		/* default subtype if still unknown */
	body->subtype = cpystr (rfc822_default_subtype (body->type));
	/* note offset and sizes */
	body->contents.offset = GETPOS (bs);
	/* note internal body size in all cases */
	body->contents.text.size = i = SIZE (bs);
	body->size.bytes = ((body->encoding == ENCBINARY) || (body->type == TYPEMULTIPART)) ? body->contents.text.size : strcrlflen (bs);
	switch (body->type)	/* see if anything else special to do */
	{
		case TYPETEXT:		/* text content */
			if (!body->parameter)	/* default parameters */
			{
				body->parameter = mail_newbody_parameter ();
				body->parameter->attribute = cpystr ("CHARSET");
				body->parameter->value = cpystr ("US-ASCII");
			}
			
			/* count number of lines */
			while (i--) if ((SNX (bs)) == '\n') body->size.lines++;
			break;

		case TYPEMESSAGE:		/* encapsulated message */
			body->nested.msg = mail_newmsg ();
			
			/* encapsulated RFC-822 message? */
			if (!strcmp (body->subtype,"RFC822")) 
			{
				if ((body->encoding == ENCBASE64) || (body->encoding == ENCQUOTEDPRINTABLE) || (body->encoding == ENCOTHER))
					mm_log ("Ignoring nested encoding of message contents",PARSE);
				
				/* hunt for blank line */
				for (c = '\012',j = 0; (i > j) && ((c != '\012') || (CHR(bs) != '\012'));
				j++) if ((c1 = SNX (bs)) != '\015') c = c1;
				if (i > j)	/* unless no more text */
				{
					c1 = SNX (bs);		/* body starts here */
					j++;			/* advance count */
				}
				
				/* note body text offset and IMAPheader size */
				body->nested.msg->IMAPheader.text.size = j;
				body->nested.msg->text.text.size = body->contents.text.size - j;
				body->nested.msg->text.offset = GETPOS (bs);
				body->nested.msg->full.offset = body->nested.msg->IMAPheader.offset =
				body->contents.offset;
				body->nested.msg->full.text.size = body->contents.text.size;
				
				/* copy IMAPheader string */
				SETPOS (bs,body->contents.offset);
				s = (char *) fs_get ((size_t) j + 1);
				if (!s) return;
				
				for (s1 = s,k = j; k--; *s1++ = SNX (bs));
				s[j] = '\0';		/* tie off string (not really necessary) */
				
				/* now parse the body */
				rfc822_parse_msg (&body->nested.msg->env,&body->nested.msg->body,s,j,bs,h);
				fs_give ((void **) &s);	/* free IMAPheader string */
				
				/* restore position */
				SETPOS (bs,body->contents.offset);
			}
			
			/* count number of lines */
			while (i--) if (SNX (bs) == '\n') body->size.lines++;
			break;
		
		case TYPEMULTIPART:		/* multiple parts */
			/* remember if digest */
			f = !strcmp (body->subtype,"DIGEST");
			if ((body->encoding == ENCBASE64) || (body->encoding == ENCQUOTEDPRINTABLE) || (body->encoding == ENCOTHER))
				mm_log ("Ignoring nested encoding of multipart contents",PARSE);
		
			/* find cookie */
			for (s1 = NIL,param = body->parameter; param && !s1; param = param->next)
			if (!strcmp (param->attribute,"BOUNDARY")) s1 = param->value;
			if (!s1) s1 = "-";		/* yucky default */
			j = strlen (s1);		/* length of cookie and IMAPheader */
			c = '\012';			/* initially at beginning of line */
			while (i > j)	/* examine data */
			{
				if (m = GETPOS (bs)) m--;	/* get position in front of character */
				switch (c)	/* examine each line */
				{
					case '\015':		/* handle CRLF form */
						if (CHR (bs) == '\012')	/* following LF? */
						{
							c = SNX (bs); i--;	/* yes, slurp it */
						}
					
					case '\012':		/* at start of a line, start with -- ? */
						if (i-- && ((c = SNX (bs)) == '-') && i-- && ((c = SNX (bs)) == '-')) 
						{
							/* see if cookie matches */
							for (k = j,s = s1; i-- && *s++ == (c = SNX (bs)) && --k;);
							if (k) break;		/* strings didn't match if non-zero */
							/* look at what follows cookie */
							if (i && i--) 
								switch (c = SNX (bs)) 
								{
									case '-':		/* at end if two dashes */
										if ((i && i--) && ((c = SNX (bs)) == '-') && ((i && i--) ? (((c = SNX (bs)) == '\015') || (c=='\012')):T)) 
										{
											/* if have a final part calculate its size */
											if (part) part->body.mime.text.size =
											(m > part->body.mime.offset) ? (m - part->body.mime.offset) :0;
											part = NIL; i = 1; /* terminate scan */
										}
										break;
		
									case '\015':		/* handle CRLF form */
										if (i && CHR (bs) == '\012') 
										{
											c = SNX (bs); i--;/* yes, slurp it */
										}
								
									case '\012':		/* new line */
										if (part)	/* calculate size of previous */
										{
										part->body.mime.text.size = (m > part->body.mime.offset) ? (m-part->body.mime.offset) : 0;
										/* instantiate next */
										part = part->next = mail_newbody_part ();
										}			/* otherwise start new list */
										else part = body->nested.part = mail_newbody_part ();
									
										/* digest has a different default */
										if (f) part->body.type = TYPEMESSAGE;
										/* note offset from main body */
										part->body.mime.offset = GETPOS (bs);
										break;
									
									default:		/* whatever it was it wasn't valid */
										break;
								}
						}
						break;
					
					default:			/* not at a line */
					c = SNX (bs); i--;	/* get next character */
					break;
				}
			}

			/* calculate size of any final part */
			if (part) part->body.mime.text.size = i + ((GETPOS(bs) > part->body.mime.offset) ? (GETPOS(bs) - part->body.mime.offset) : 0);
		
			/* make a scratch buffer */
			s1 = (char *) fs_get ((size_t) (k = MAILTMPLEN));
			if (!s1) return;
			
			/* parse non-empty body parts */
			for (part = body->nested.part; part; part = part->next)
				if (i = part->body.mime.text.size) 
				{
					/* move to that part of the body */
					SETPOS (bs,part->body.mime.offset);
					
					/* until end of IMAPheader */
					while (i && ((c = CHR (bs)) != '\015') && (c != '\012')) 
					{
						/* collect text until logical end of line */
						for (j = 0,c = ' '; c; ) 
						{
							/* make sure buffer big enough */
							if (j > (k - 10)) fs_resize ((void *) &s1,k += MAILTMPLEN);
							switch (c1 = SNX (bs)) 
							{
								case '\015':	/* return */
									if (i && (CHR (bs) == '\012')) 
									{
										c1 = SNX (bs);	/* eat any LF following */
										i--;
									}
								
								case '\012':	/* newline, possible end of logical line */
									/* tie off unless continuation */
									if (!i || ((CHR (bs) != ' ') && (CHR (bs) != '\t')))
									s1[j] = c = '\0';
									break;
						
								case '\t':		/* tab */
								case ' ':		/* insert whitespace if not already there */
									if (c != ' ') s1[j++] = c = ' ';
									break;
							
								default:		/* all other characters */
									s1[j++] = c = c1;	/* insert the character into the line */
									break;
							}
							/* end of data ties off the IMAPheader */
							if (!--i) s1[j++] = c = '\0';
						}

						/* find IMAPheader item type */
						if (((s1[0] == 'C') || (s1[0] == 'c')) &&
						((s1[1] == 'O') || (s1[1] == 'o')) &&
						((s1[2] == 'N') || (s1[2] == 'n')) &&
						((s1[3] == 'T') || (s1[3] == 't')) &&
						((s1[4] == 'E') || (s1[4] == 'e')) &&
						((s1[5] == 'N') || (s1[5] == 'n')) &&
						((s1[6] == 'T') || (s1[6] == 't')) &&
						(s1[7] == '-') && (s = strchr (s1+8,':'))) {
						/* tie off and flush whitespace */
						for (*s++ = '\0'; *s == ' '; s++);
						/* parse the IMAPheader */
						rfc822_parse_content_header (&part->body,ucase (s1+8),s);
						}
					}			/* skip IMAPheader trailing (CR)LF */
					
					if (i && (CHR (bs) =='\015')) {i--; c1 = SNX (bs);}
					if (i && (CHR (bs) =='\012')) {i--; c1 = SNX (bs);}
					j = bs->size;		/* save upper level size */
					
					/* set offset for next level, fake size to i */
					bs->size = GETPOS (bs) + i;
					part->body.mime.text.size -= i;
					
					/* now parse it */
					rfc822_parse_content (&part->body,bs,h);
					bs->size = j;		/* restore current level size */
				}
			
			fs_give ((void **) &s1);	/* finished with scratch buffer */
			break;
		
		default:			/* nothing special to do in any other case */
			break;
	}
}


/* Parse RFC822 body content IMAPheader
 * Accepts: body to write to
 *	    possible content name
 *	    remainder of IMAPheader
 */

void rfc822_parse_content_header (IMAPBODY *body,char *name,char *s)
{
	char c,*t;
	long i;
	STRINGLIST *stl;
	
	/* flush whitespace */
	if (t = strchr (name,' ')) *t = '\0';
	switch (*name)	/* see what kind of content */
	{
		case 'I':			/* possible Content-ID */
			if (!(strcmp (name+1,"D") || body->id)) body->id = cpystr (s);
			break;
		
		case 'D':			/* possible Content-Description */
			if (!(strcmp (name+1,"ESCRIPTION") || body->description))
			body->description = cpystr (s);
			if (!(strcmp (name+1,"ISPOSITION") || body->disposition.type)) 
			{
				/* get type word */
				if (!(name = rfc822_parse_word (s,ptspecials))) break;
				c = *name;		/* remember delimiter */
				*name = '\0';		/* tie off type */
				body->disposition.type = ucase (cpystr (s));
				*name = c;		/* restore delimiter */
				rfc822_skipws (&name);	/* skip whitespace */
				rfc822_parse_parameter (&body->disposition.parameter,name);
			}
			break;
		
		case 'L':			/* possible Content-Language */
			if (!(strcmp (name+1,"ANGUAGE") || body->language)) 
			{
				stl = NIL;		/* process languages */
				while (s && (name = rfc822_parse_word (s,ptspecials))) 
				{
					c = *name;		/* save delimiter */
					*name = '\0';		/* tie off subtype */
					if (stl) stl = stl->next = mail_newstringlist ();
					else stl = body->language = mail_newstringlist ();
					stl->text.data = ucase (cpystr (s));
					stl->text.size = strlen (stl->text.data);
					*name = c;		/* restore delimiter */
					rfc822_skipws (&name);	/* skip whitespace */
					if (*name == ',')	/* any more languages? */
					{
						s = ++name;		/* advance to it them */
						rfc822_skipws (&s);
					}
					else s = NIL;		/* bogus or end of list */
				}
			}
			break;
			
		case 'M':			/* possible Content-MD5 */
			if (!(strcmp (name+1,"D5") || body->md5)) body->md5 = cpystr (s);
			break;

		case 'T':			/* possible Content-Type/Transfer-Encoding */
			if (!(strcmp (name+1,"YPE") || body->subtype || body->parameter)) 
			{
					/* get type word */
				if (!(name = rfc822_parse_word (s,ptspecials))) break;
				c = *name;		/* remember delimiter */
				*name = '\0';		/* tie off type */
				ucase (s);		/* search for body type */
				for (i=0; (i<=TYPEMAX) && body_types[i] && strcmp(s,body_types[i]); i++);
				if (i > TYPEMAX) body->type = TYPEOTHER;
				else	/* if empty slot, assign it to this type */
				{
					if (!body_types[i]) body_types[i] = cpystr (s);
					body->type = (unsigned short) i;
				}
				*name = c;		/* restore delimiter */
				rfc822_skipws (&name);	/* skip whitespace */
				if ((*name == '/') && (name = rfc822_parse_word ((s = ++name),ptspecials))) 
				{
					c = *name;		/* save delimiter */
					*name = '\0';		/* tie off subtype */
					rfc822_skipws (&s);	/* copy subtype */
					if (s) body->subtype = ucase (cpystr (s));
					*name = c;		/* restore delimiter */
					rfc822_skipws (&name);	/* skip whitespace */
				}
				else	/* no subtype */
				{
					if (!name)	/* did the fool have a subtype delimiter? */
					{
						name = s;		/* barf, restore pointer */
						rfc822_skipws (&name);/* skip leading whitespace */
					}
				}
				rfc822_parse_parameter (&body->parameter,name);
			}
			else if (!strcmp (name+1,"RANSFER-ENCODING")) 
			{
				/* flush out any confusing whitespace */
				if (t = strchr (ucase (s),' ')) *t = '\0';
				/* search for body encoding */
				for (i = 0; (i <= ENCMAX) && body_encodings[i] && strcmp (s,body_encodings[i]); i++);
				if (i > ENCMAX) body->type = ENCOTHER;
				else	/* if empty slot, assign it to this type */
				{
					if (!body_encodings[i]) body_encodings[i] = cpystr (s);
					body->encoding = (unsigned short) i;
				}
			}
			break;
		
		default:			/* otherwise unknown */
			break;
	}
}


/* Parse RFC822 body parameter list
 * Accepts: parameter list to write to
 *	    text of list
 */

void rfc822_parse_parameter (PARAMETER **par,char *text)
{
	char c,*s,tmp[MAILTMPLEN];
	PARAMETER *param = NIL;
	
	/* parameter list? */
	while (text && (*text == ';') && (text = rfc822_parse_word ((s = ++text),ptspecials))) 
	{
		c = *text;			/* remember delimiter */
		*text = '\0';		/* tie off attribute name */
		rfc822_skipws (&s);		/* skip leading attribute whitespace */
		if (!*s) *text = c;		/* must have an attribute name */
		else 	/* instantiate a new parameter */
		{
			if (*par) param = param->next = mail_newbody_parameter ();
			else param = *par = mail_newbody_parameter ();
			
			param->attribute = ucase (cpystr (s));
			*text = c;		/* restore delimiter */
			rfc822_skipws (&text);	/* skip whitespace before equal sign */
			if ((*text != '=') || !(text = rfc822_parse_word ((s = ++text),ptspecials)))
				param->value = cpystr ("UNKNOWN_PARAMETER_VALUE");
			else 	/* good, have equals sign */
			{
				c = *text;		/* remember delimiter */
				*text = '\0';		/* tie off value */
				rfc822_skipws (&s);	/* skip leading value whitespace */
				if (*s) param->value = rfc822_cpy (s);
				*text = c;		/* restore delimiter */
				rfc822_skipws (&text);
			}	
		}
	}
	if (!text)	/* must be end of poop */
	{
		if (param && param->attribute)
		sprintf (tmp,"Missing parameter value: %.80s",param->attribute);
		else strcpy (tmp,"Missing parameter");
		mm_log (tmp,PARSE);
	}
	else if (*text)	/* must be end of poop */
	{
		sprintf (tmp,"Unexpected characters at end of parameters: %.80s",text);
		mm_log (tmp,PARSE);
	}
}


/* Parse RFC822 address list
 * Accepts: address list to write to
 *	    input string
 *	    default host name
 */

void rfc822_parse_adrlist (ADDRESS **lst,char *string,char *host)
{
	char c,*s,tmp[MAILTMPLEN];
	ADDRESS *last = *lst;
	ADDRESS *adr;
	
	if (!string) return;		/* no string */
	
	rfc822_skipws (&string);	/* skip leading WS */
	
	if (!*string) return;		/* empty string */
	
	/* run to tail of list */
	if (last) while (last->next) last = last->next;
	while (string)	/* loop until string exhausted */
	{
		/* got an address? */
		if (adr = rfc822_parse_address (lst,last,&string,host)) 
		{
			last = adr;
			if (string)	/* analyze what follows */
			{
				rfc822_skipws (&string);
				switch (c = *string) 
				{
					case ',':		/* comma? */
						++string;		/* then another address follows */
						break;
					
					default:
						s = isalnum (c) ? "Must use comma to separate addresses: %.80s" : "Unexpected characters at end of address: %.80s";
						sprintf (tmp,s,string);
						mm_log (tmp,PARSE);
						last = last->next = mail_newaddr ();
						last->mailbox = cpystr ("UNEXPECTED_DATA_AFTER_ADDRESS");
						last->host = cpystr (errhst);
					
					/* falls through */
					case '\0':		/* null-specified address? */
						string = NIL;		/* punt remainder of parse */
						break;
				}
			}
		}
		else if (string)	/* bad mailbox */
		{
			rfc822_skipws (&string);	/* skip WS */
			if (!*string) strcpy (tmp,"Missing address after comma");
			else sprintf (tmp,"Invalid mailbox list: %.80s",string);
			mm_log (tmp,PARSE);
			string = NIL;
			(adr = mail_newaddr ())->mailbox = cpystr ("INVALID_ADDRESS");
			adr->host = cpystr (errhst);
			if (last) last = last->next = adr;
			else *lst = last = adr;
			break;
		}
	}
}


/* Parse RFC822 address
 * Accepts: address list to write to
 *	    tail of address list
 *	    pointer to input string
 *	    default host name
 */

ADDRESS *rfc822_parse_address (ADDRESS **lst,ADDRESS *last,char **string,char *defaulthost)
{
	ADDRESS *adr;
	
	if (!*string) return NIL;	/* no string */
	rfc822_skipws (string);	/* skip leading WS */
	if (!**string) return NIL;	/* empty string */
	if (adr = rfc822_parse_group (lst,last,string,defaulthost)) last = adr;
	else if (adr = rfc822_parse_mailbox (string,defaulthost))		/* got an address? */
	{
		if (!*lst) *lst = adr;	/* yes, first time through? */
		else last->next = adr;	/* no, append to the list */
		last = adr;			/* set for subsequent linking */
	}
	else if (*string) return NIL;
	
	return last;
}


/* Parse RFC822 group
 * Accepts: address list to write to
 *	    pointer to tail of address list
 *	    pointer to input string
 *	    default host name
 */

ADDRESS *rfc822_parse_group (ADDRESS **lst,ADDRESS *last,char **string,char *defaulthost)
{
	char tmp[MAILTMPLEN];
	char *p,*s;
	ADDRESS *adr;
	
	if (!*string) return NIL;	/* no string */
	
	rfc822_skipws (string);	/* skip leading WS */
	
	if (!**string || ((*(p = *string) != ':') && !(p = rfc822_parse_phrase (*string))))	/* trailing whitespace or not group */
		return NIL;
	s = p;			/* end of candidate phrase */
	rfc822_skipws (&s);		/* find delimiter */
	if (*s != ':') return NIL;	/* not really a group */	
	*p = '\0';			/* tie off group name */
	p = ++s;			/* continue after the delimiter */
	rfc822_skipws (&p);		/* skip subsequent whitespace */

	/* write as address */
	(adr = mail_newaddr ())->mailbox = rfc822_cpy (*string);
	if (!*lst) *lst = adr;	/* first time through? */
	else last->next = adr;	/* no, append to the list */
	last = adr;			/* set for subsequent linking */
	*string = p;			/* continue after this point */
	while (*string && **string && (**string != ';')) 
	{
		if (adr = rfc822_parse_address (lst,last,string,defaulthost)) 
		{
			last = adr;
			if (*string)	/* anything more? */
			{
				rfc822_skipws (string);	/* skip whitespace */
				switch (**string)	/* see what follows */
				{
					case ',':		/* another address? */
						++*string;		/* yes, skip past the comma */
					
					case ';':		/* end of group? */
					case '\0':		/* end of string */
						break;
					
					default:
						sprintf (tmp,"Unexpected characters after address in group: %.80s",
						*string);
						mm_log (tmp,PARSE);
						*string = NIL;	/* cancel remainder of parse */
						last = last->next = mail_newaddr ();
						last->mailbox = cpystr ("UNEXPECTED_DATA_AFTER_ADDRESS_IN_GROUP");
						last->host = cpystr (errhst);
				}
			}
		}
		else	/* bogon */
		{
			sprintf (tmp,"Invalid group mailbox list: %.80s",*string);
			mm_log (tmp,PARSE);
			*string = NIL;		/* cancel remainder of parse */
			(adr = mail_newaddr ())->mailbox = cpystr ("INVALID_ADDRESS_IN_GROUP");
			adr->host = cpystr (errhst);
			last = last->next = adr;
		}
	}
	if (*string) {		/* skip close delimiter */
	if (**string == ';') ++*string;
	rfc822_skipws (string);
	}
	
	/* append end of address mark to the list */
	last->next = (adr = mail_newaddr ());
	last = adr;			/* set for subsequent linking */
	return last;			/* return the tail */
}


/* Parse RFC822 mailbox
 * Accepts: pointer to string pointer
 *	    default host
 * Returns: address
 *
 * Updates string pointer
 */

ADDRESS *rfc822_parse_mailbox (char **string,char *defaulthost)
{
	ADDRESS *adr;
	char *s,*phrase;
	
	if (!*string) return NIL;	/* no string */
	rfc822_skipws (string);	/* flush leading whitespace */
	if (!**string) return NIL;	/* empty string */

	/* This is much more complicated than it should be because users like
	* to write local addrspecs without "@localhost".  This makes it very
	* difficult to tell a phrase from an addrspec!
	* The other problem we must cope with is a route-addr without a leading
	* phrase.  Yuck!
	*/

	if (*(s = *string) == '<') 	/* note start, handle case of phraseless RA */
	adr = rfc822_parse_routeaddr (s,string,defaulthost);
	else	/* get phrase if any */
	{
		if ((phrase = rfc822_parse_phrase (s)) &&
		(adr = rfc822_parse_routeaddr (phrase,string,defaulthost))) 
		{
			*phrase = '\0';		/* tie off phrase */
			/* phrase is a personal name */
			adr->personal = rfc822_cpy (s);
		}
		else adr = rfc822_parse_addrspec (s,string,defaulthost);
	}
	return adr;			/* return the address */
}


/* Parse RFC822 route-address
 * Accepts: string pointer
 *	    pointer to string pointer to update
 * Returns: address
 *
 * Updates string pointer
 */

ADDRESS *rfc822_parse_routeaddr (char *string,char **ret,char *defaulthost)
{
	char tmp[MAILTMPLEN];
	ADDRESS *adr;
	char *adl = NIL;
	char *routeend = NIL;
	
	if (!string) return NIL;
	
	rfc822_skipws (&string);	/* flush leading whitespace */
	
	/* must start with open broket */
	if (*string != '<') return NIL;
	if (string[1] == '@')		/* have an A-D-L? */
	{
		adl = ++string;		/* yes, remember that fact */
		while (*string != ':')	/* search for end of A-D-L */
		{
			/* punt if never found */
			if (!*string) return NIL;
			++string;			/* try next character */
		}
		*string = '\0';		/* tie off A-D-L */
		routeend = string;		/* remember in case need to put back */
	}
	
	/* parse address spec */
	if (!(adr = rfc822_parse_addrspec (++string,ret,defaulthost))) 
	{
		if (adl) *routeend = ':';	/* put colon back since parse barfed */
		return NIL;
	}
	
	/* have an A-D-L? */
	if (adl) adr->adl = cpystr (adl);
	if (*ret) if (**ret == '>')	/* make sure terminated OK */
	{
		++*ret;			/* skip past the broket */
		rfc822_skipws (ret);	/* flush trailing WS */
		if (!**ret) *ret = NIL;	/* wipe pointer if at end of string */
		return adr;			/* return the address */
	}
	
	sprintf (tmp,"Unterminated mailbox: %.80s@%.80s",adr->mailbox,
	*adr->host == '@' ? "<null>" : adr->host);
	mm_log (tmp,PARSE);
	adr->next = mail_newaddr ();
	adr->next->mailbox = cpystr ("MISSING_MAILBOX_TERMINATOR");
	adr->next->host = cpystr (errhst);
	
	return adr;			/* return the address */
}


/* Parse RFC822 address-spec
 * Accepts: string pointer
 *	    pointer to string pointer to update
 *	    default host
 * Returns: address
 *
 * Updates string pointer
 */

ADDRESS *rfc822_parse_addrspec (char *string,char **ret,char *defaulthost)
{
	ADDRESS *adr;
	char *end;
	char c,*s,*t;
	
	if (!string) return NIL;	/* no string */
	rfc822_skipws (&string);	/* flush leading whitespace */
	if (!*string) return NIL;	/* empty string */
	
	/* find end of mailbox */
	if (!(end = rfc822_parse_word (string,NIL))) return NIL;
	adr = mail_newaddr ();	/* create address block */
	c = *end;			/* remember delimiter */
	*end = '\0';			/* tie off mailbox */
	
	/* copy mailbox */
	adr->mailbox = rfc822_cpy (string);
	*end = c;			/* restore delimiter */
	t = end;			/* remember end of mailbox for no host case */
	rfc822_skipws (&end);		/* skip whitespace */
	if (*end == '@')	/* have host name? */
	{
		++end;			/* skip delimiter */
		rfc822_skipws (&end);	/* skip whitespace */
		if (*end == '[')	/* domain literal? */
		{
			string = end;		/* start of domain literal */
			if (end = rfc822_parse_word (string + 1,"]\\")) 
			{
				c = end[1];		/* tie off literal */
				end[1] = '\0';
				adr->host = cpystr (string);
				*++end = c;		/* restore delimiter */
			}
			else 
			{
				mm_log ("Invalid domain literal after @",PARSE);
				adr->host = cpystr (BADHOST);
			}
		}
		/* search for end of host */
		else if (end = rfc822_parse_word ((string = end),wspecials)) 
		{
			c = *end;			/* remember delimiter */
			*end = '\0';		/* tie off host */
			/* copy host */
			adr->host = rfc822_cpy (string);
			*end = c;			/* restore delimiter */
		}
		else 
		{
			mm_log ("Missing or invalid host name after @",PARSE);
			adr->host = cpystr (BADHOST);
		}
	}
	else end = t;			/* make person name default start after mbx */
	
	/* default host if missing */
	if (!adr->host) adr->host = cpystr (defaulthost);
	if (end && !adr->personal)	/* try person name in comments if missing */
	{
		while (*end == ' ') ++end;	/* see if we can find a person name here */
		if ((*end == '(') && (s = rfc822_skip_comment (&end,LONGT)) && strlen (s))
		adr->personal = rfc822_cpy (s);
		rfc822_skipws (&end);	/* skip any other WS in the normal way */
	}
	
	/* set return to end pointer */
	*ret = (end && *end) ? end : NIL;
	
	return adr;			/* return the address we got */
}


/* Parse RFC822 phrase
 * Accepts: string pointer
 * Returns: pointer to end of phrase
 */

char *rfc822_parse_phrase (char *s)
{
	char *curpos;
	
	if (!s) return NIL;		/* no-op if no string */
	
	/* find first word of phrase */
	curpos = rfc822_parse_word (s,NIL);
	if (!curpos) return NIL;	/* no words means no phrase */
	if (!*curpos) return curpos;	/* check if string ends with word */
	s = curpos;			/* sniff past the end of this word and WS */
	rfc822_skipws (&s);		/* skip whitespace */
	
	/* recurse to see if any more */
	return (s = rfc822_parse_phrase (s)) ? s : curpos;
}


/* Parse RFC822 word
 * Accepts: string pointer
 * Returns: pointer to end of word
 */

char *rfc822_parse_word (char *s,const char *delimiters)
{
	char *st,*str;
	
	if (!s) return NIL;		/* no string */
	rfc822_skipws (&s);		/* flush leading whitespace */
	if (!*s) return NIL;		/* empty string */
	
	/* default delimiters to standard */
	if (!delimiters) delimiters = wspecials;
	str = s;			/* hunt pointer for strpbrk */
	while (T)	/* look for delimiter */
	{
		if (!(st = strpbrk (str,delimiters))) 
		{
			while (*s) ++s;		/* no delimiter, hunt for end */
			return s;			/* return it */
		}
		switch (*st)	/* dispatch based on delimiter */
		{
			case '"':			/* quoted string */
				/* look for close quote */
				while (*++st != '"') 
					switch (*st) 
					{
						case '\0':		/* unbalanced quoted string */
							return NIL;		/* sick sick sick */
					
						case '\\':		/* quoted character */
							if (!*++st) return NIL;	/* skip the next character */
					
						default:			/* ordinary character */
							break;			/* no special action */
					}
				str = ++st;		/* continue parse */
				break;
		
			case '\\':			/* quoted character */
				/* This is wrong; a quoted-pair can not be part of a word.  However,
				* domain-literal is parsed as a word and quoted-pairs can be used
				* *there*.  Either way, it's pretty pathological.
				*/
				if (st[1])	/* not on NUL though... */
				{
					str = st + 2;		/* skip quoted character and go on */
					break;
				}
		
			default:			/* found a word delimiter */
				return (st == s) ? NIL : st;
		}
	}
}


/* Copy an RFC822 format string
 * Accepts: string
 * Returns: copy of string
 */

char *rfc822_cpy (char *src)
{
	/* copy and unquote */
	return rfc822_quote (cpystr (src));
}


/* Unquote an RFC822 format string
 * Accepts: string
 * Returns: string
 */

char *rfc822_quote (char *src)
{
	char *ret = src;
	if (strpbrk (src,"\\\""))	/* any quoting in string? */
	{
		char *dst = ret;
		while (*src)	/* copy string */
		{
			if (*src == '\"') src++;	/* skip double quote entirely */
			else 
			{
				if (*src == '\\') src++;/* skip over single quote, copy next always */
				*dst++ = *src++;	/* copy character */
			}
		}
		*dst = '\0';		/* tie off string */
	}
	return ret;			/* return our string */
}


/* Copy address list
 * Accepts: address list
 * Returns: address list
 */

ADDRESS *rfc822_cpy_adr (ADDRESS *adr)
{
	ADDRESS *dadr;
	ADDRESS *ret = NIL;
	ADDRESS *prev = NIL;
	
	while (adr)	/* loop while there's still an MAP adr */
	{
		dadr = mail_newaddr ();	/* instantiate a new address */
		if (!ret) ret = dadr;	/* note return */
		if (prev) prev->next = dadr;/* tie on to the end of any previous */
		dadr->personal = cpystr (adr->personal);
		dadr->adl = cpystr (adr->adl);
		dadr->mailbox = cpystr (adr->mailbox);
		dadr->host = cpystr (adr->host);
		prev = dadr;		/* this is now the previous */
		adr = adr->next;		/* go to next address in list */
	}
	return (ret);			/* return the MTP address list */
}


/* Skips RFC822 whitespace
 * Accepts: pointer to string pointer
 */

void rfc822_skipws (char **s)
{
	while (T) 
	{
		if (**s == ' ') ++*s;	/* skip space */
		else if ((**s != '(') || !rfc822_skip_comment (s,(long) NIL)) return;
	}
}


/* Skips RFC822 comment
 * Accepts: pointer to string pointer
 *	    trim flag
 * Returns: pointer to first non-blank character of comment
 */

char *rfc822_skip_comment (char **s,long trim)
{
	char *ret,tmp[MAILTMPLEN];
	char *s1 = *s;
	char *t = NIL;
	
	/* skip past whitespace */
	for (ret = ++s1; *ret == ' '; ret++);
	do 
	{
		switch (*s1)	/* get character of comment */
		{		
			case '(':			/* nested comment? */
				if (!rfc822_skip_comment (&s1,(long) NIL)) return NIL;
				t = --s1;			/* last significant char at end of comment */
				break;
	
			case ')':			/* end of comment? */
				*s = ++s1;			/* skip past end of comment */
				if (trim)	/* if level 0, must trim */
				{
					if (t) t[1] = '\0';	/* tie off comment string */
					else *ret = '\0';		/* empty comment */
				}
				return ret;

			case '\\':			/* quote next character? */
				if (*++s1) break;		/* drop in if null seen */
	
			case '\0':			/* end of string */
				sprintf (tmp,"Unterminated comment: %.80s",*s);
				mm_log (tmp,PARSE);
				**s = '\0';			/* nuke duplicate messages in case reparse */
				return NIL;			/* this is wierd if it happens */
	
			case ' ':			/* whitespace isn't significant */
				break;

			default:			/* random character */
				t = s1;			/* update last significant character pointer */
				break;
		} 
	} while (s1++);
	
	return NIL;			/* impossible, but pacify lint et al */
}


/* Body contents utility and encoding/decoding routines */


/* Return body contents in normal form
 * Accepts: pointer to destination
 *	    pointer to length of destination
 *	    returned destination length
 *	    source
 *	    length of source
 *	    source encoding
 * Returns: destination
 *
 * Originally, this routine was supposed to do decoding as well, but that was
 * moved to a higher level.  Now, it's merely a jacket into strcrlfcpy that
 * avoids the work for BINARY segments.
 */

char *rfc822_contents (char **dst,unsigned long *dstl,unsigned long *len,char *src,unsigned long srcl,unsigned short encoding)
{
	*len = 0;			/* in case we return an error */
	if (encoding == ENCBINARY)	/* unmodified binary */
	{
		if ((*len = srcl) > *dstl)	/* resize if not enough space */
		{
			fs_give ((void **) dst);	/* fs_resize does an unnecessary copy */
			*dst = (char *) fs_get ((size_t) (*dstl = srcl) + 1);
			if (!dst) return NULL;
		}
		/* copy that many bytes */
		memcpy (*dst,src,(size_t) srcl);
		*(*dst + srcl) = '\0';	/* tie off destination */
	}
	/* all other cases return strcrlfcpy version */
	else *len = strcrlfcpy (dst,dstl,src,srcl);
	
	return *dst;			/* return the string */
}


/* Output RFC 822 message
 * Accepts: temporary buffer
 *	    envelope
 *	    body
 *	    I/O routine
 *	    stream for I/O routine
 *	    non-zero if 8-bit output desired
 * Returns: T if successful, NIL if failure
 */

long rfc822_output (char *t,ENVELOPE *env,IMAPBODY *body,soutr_t f,void *s,long ok8bit)
{
	rfc822out_t r822o = (rfc822out_t) mail_parameters (NIL,GET_RFC822OUTPUT,NIL);
	
	/* call external RFC822 output generator */
	if (r822o) return (*r822o) (t,env,body,f,s,ok8bit);
	
	/* encode body as necessary */
	if (ok8bit) rfc822_encode_body_8bit (env,body);
	else rfc822_encode_body_7bit (env,body);
	rfc822_header (t,env,body);	/* build RFC822 IMAPheader */
	
	/* output IMAPheader and body */
	return (*f) (s,t) && (body ? rfc822_output_body (body,f,s) : T);
}


/* Encode a body for 7BIT transmittal
 * Accepts: envelope
 *	    body
 */

void rfc822_encode_body_7bit (ENVELOPE *env,IMAPBODY *body)
{
	void *f;
	PART *part;
	
	if (body) 
		switch (body->type)
		{
			case TYPEMULTIPART:		/* multi-part */
				if (!body->parameter)	/* cookie not set up yet? */
				{
					char tmp[MAILTMPLEN];	/* make cookie not in BASE64 or QUOTEPRINT*/
					sprintf (tmp,"%ld-%ld-%ld=:%ld",gethostid (),random (),time (0),
					getpid ());
					body->parameter = mail_newbody_parameter ();
					body->parameter->attribute = cpystr ("BOUNDARY");
					body->parameter->value = cpystr (tmp);
				}
				part = body->nested.part;	/* encode body parts */
				do 
					rfc822_encode_body_7bit (env,&part->body);
				while (part = part->next);	/* until done */
				break;
			
				case TYPEMESSAGE:		/* encapsulated message */
					switch (body->encoding) 
					{
						case ENC7BIT:
							break;
						
						case ENC8BIT:
							mm_log ("8-bit included message in 7-bit message body",WARN);
							break;
						
						case ENCBINARY:
							mm_log ("Binary included message in 7-bit message body",WARN);
							break;
						
						default:
							fatal ("Invalid rfc822_encode_body_7bit message encoding");
					}
					break;			/* can't change encoding */
			
				default:			/* all else has some encoding */
					switch (body->encoding) 
					{
						case ENC8BIT:		/* encode 8BIT into QUOTED-PRINTABLE */
							/* remember old 8-bit contents */
							f = (void *) body->contents.text.data;
							body->contents.text.data = (char *)
							rfc822_8bit ((unsigned char *) body->contents.text.data,
							body->contents.text.size,&body->contents.text.size);
							body->encoding = ENCQUOTEDPRINTABLE;
							fs_give (&f);		/* flush old binary contents */
							break;
					
						case ENCBINARY:		/* encode binary into BASE64 */
							/* remember old binary contents */
							f = (void *) body->contents.text.data;
							body->contents.text.data = (char *)
							rfc822_binary ((void *) body->contents.text.data,
							body->contents.text.size,&body->contents.text.size);
							body->encoding = ENCBASE64;
							fs_give (&f);		/* flush old binary contents */
					
						default:			/* otherwise OK */
							break;
					}
			break;
		}
}


/* Encode a body for 8BIT transmittal
 * Accepts: envelope
 *	    body
 */

void rfc822_encode_body_8bit (ENVELOPE *env,IMAPBODY *body)
{
	void *f;
	PART *part;
	
	if (body)
		switch (body->type) 
		{
			case TYPEMULTIPART:		/* multi-part */
				if (!body->parameter) 	/* cookie not set up yet? */
				{
					char tmp[MAILTMPLEN];	/* make cookie not in BASE64 or QUOTEPRINT*/
					
					sprintf (tmp,"%ld-%ld-%ld=:%ld",gethostid (),random (),time (0),
					getpid ());
					body->parameter = mail_newbody_parameter ();
					body->parameter->attribute = cpystr ("BOUNDARY");
					body->parameter->value = cpystr (tmp);
				}
				part = body->nested.part;	/* encode body parts */
				do 
					rfc822_encode_body_8bit (env,&part->body);
				while (part = part->next);	/* until done */
				break;
			
			case TYPEMESSAGE:		/* encapsulated message */
				switch (body->encoding) 
				{
					case ENC7BIT:
					case ENC8BIT:
						break;
					
					case ENCBINARY:
						mm_log ("Binary included message in 8-bit message body",WARN);
						break;
					
					default:
						fatal ("Invalid rfc822_encode_body_7bit message encoding");
				}
				break;			/* can't change encoding */
			
			default:			/* other type, encode binary into BASE64 */
				if (body->encoding == ENCBINARY) 
				{
					/* remember old binary contents */
					f = (void *) body->contents.text.data;
					body->contents.text.data = (char *)
					rfc822_binary ((void *) body->contents.text.data,
					body->contents.text.size,&body->contents.text.size);
					body->encoding = ENCBASE64;
					fs_give (&f);		/* flush old binary contents */
				}
				break;
		}
}


/* Output RFC 822 body
 * Accepts: body
 *	    I/O routine
 *	    stream for I/O routine
 * Returns: T if successful, NIL if failure
 */

long rfc822_output_body (IMAPBODY *body,soutr_t f,void *s)
{
	PART *part;
	PARAMETER *param;
	char *cookie = NIL;
	char tmp[MAILTMPLEN];
	char *t;
	
	switch (body->type) 
	{
		case TYPEMULTIPART:		/* multipart gets special handling */
			part = body->nested.part;	/* first body part */
			/* find cookie */
			for (param = body->parameter; param && !cookie; param = param->next)
			if (!strcmp (param->attribute,"BOUNDARY")) cookie = param->value;
			if (!cookie) cookie = "-";	/* yucky default */
			do	/* for each part */
			{
				/* build cookie */
				sprintf (t = tmp,"--%s\015\012",cookie);
				/* append mini-IMAPheader */
				rfc822_write_body_header (&t,&part->body);
				strcat (t,"\015\012");	/* write terminating blank line */
				/* output cookie, mini-IMAPheader, and contents */
				if (!((*f) (s,tmp) && rfc822_output_body (&part->body,f,s))) return NIL;
			} while (part = part->next);/* until done */
			/* output trailing cookie */
			sprintf (t = tmp,"--%s--",cookie);
			break;
	
		default:			/* all else is text now */
			t = (char *) body->contents.text.data;
			break;
	}
	/* output final stuff */
	if (t && *t && !((*f) (s,t) && (*f) (s,"\015\012"))) return NIL;
	return LONGT;
}


/* Convert BASE64 contents to binary
 * Accepts: source
 *	    length of source
 *	    pointer to return destination length
 * Returns: destination as binary
 */

void *rfc822_base64 (unsigned char *src,unsigned long srcl,unsigned long *len)
{
	char c;
	void *ret = fs_get ((size_t) (*len = 4 + ((srcl * 3) / 4)));
	char *d = (char *) ret;
	short e = 0;
	
	if (!ret) return NULL;
	
	memset (ret,0,(size_t) *len);	/* initialize block */
	*len = 0;			/* in case we return an error */
	while (srcl--)	/* until run out of characters */
	{
		c = *src++;			/* simple-minded decode */
		if (isupper (c)) c -= 'A';
		else if (islower (c)) c -= 'a' - 26;
		else if (isdigit (c)) c -= '0' - 52;
		else if (c == '+') c = 62;
		else if (c == '/') c = 63;
		else if (c == '=')	/* padding */
		{
			switch (e++)	/* check quantum position */
			{
				case 3:
					e = 0;			/* restart quantum */
					break;
			
				case 2:
					if (*src == '=') break;
			
				default:			/* impossible quantum position */
					fs_give (&ret);
					return NIL;
			}
			continue;	
		}
		else continue;		/* junk character */
		
		switch (e++)	/* install based on quantum position */
		{
			case 0:
				*d = c << 2;		/* byte 1: high 6 bits */
				break;
			
			case 1:
				*d++ |= c >> 4;		/* byte 1: low 2 bits */
				*d = c << 4;		/* byte 2: high 4 bits */
				break;
			
			case 2:
				*d++ |= c >> 2;		/* byte 2: low 4 bits */
				*d = c << 6;		/* byte 3: high 2 bits */
				break;
			
			case 3:
				*d++ |= c;		/* byte 3: low 6 bits */
				e = 0;			/* reinitialize mechanism */
				break;
		}
	}
	*len = d - (char *) ret;	/* calculate data length */
	
	return ret;			/* return the string */
}


/* Convert binary contents to BASE64
 * Accepts: source
 *	    length of source
 *	    pointer to return destination length
 * Returns: destination as BASE64
 */

unsigned char *rfc822_binary (void *src,unsigned long srcl,unsigned long *len)
{
	unsigned char *ret,*d;
	unsigned char *s = (unsigned char *) src;
	char *v = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
	unsigned long i = ((srcl + 2) / 3) * 4;
	
	*len = i += 2 * ((i / 60) + 1);
	d = ret = (unsigned char *) fs_get ((size_t) ++i);
	if (!ret) return NULL;
	
	for (i = 0; srcl; s += 3)	/* process tuplets */
	{
		*d++ = v[s[0] >> 2];	/* byte 1: high 6 bits (1) */
		
		/* byte 2: low 2 bits (1), high 4 bits (2) */
		*d++ = v[((s[0] << 4) + (--srcl ? (s[1] >> 4) : 0)) & 0x3f];
		
		/* byte 3: low 4 bits (2), high 2 bits (3) */
		*d++ = srcl ? v[((s[1] << 2) + (--srcl ? (s[2] >> 6) : 0)) & 0x3f] : '=';
		
		/* byte 4: low 6 bits (3) */
		*d++ = srcl ? v[s[2] & 0x3f] : '=';
		if (srcl) srcl--;		/* count third character if processed */
		if ((++i) == 15)	/* output 60 characters? */
		{
			i = 0;			/* restart line break count, insert CRLF */
			*d++ = '\015'; *d++ = '\012';
		}
	}
	*d++ = '\015'; *d++ = '\012';	/* insert final CRLF */
	*d = '\0';			/* tie off string */
	if (((unsigned long) (d - ret)) != *len) fatal ("rfc822_binary logic flaw");
	
	return ret;			/* return the resulting string */
}


/* Convert QUOTED-PRINTABLE contents to 8BIT
 * Accepts: source
 *	    length of source
 * 	    pointer to return destination length
 * Returns: destination as 8-bit text
 */

unsigned char *rfc822_qprint (unsigned char *src,unsigned long srcl,unsigned long *len)
{
	unsigned char *ret = (unsigned char *) fs_get ((size_t) srcl + 1);
	unsigned char *d = ret;
	unsigned char *s = d;
	unsigned char c,e;
	
	if (!ret) return NULL;
	
	*len = 0;			/* in case we return an error */
	src[srcl] = '\0';		/* make sure string tied off */
	while (c = *src++)	/* until run out of characters */
	{
		switch (c)	/* what type of character is it? */
		{
			case '=':			/* quoting character */
				switch (c = *src++)	/* what does it quote? */
				{
					case '\0':		/* end of data */
						src--;			/* back up pointer */
						break;
					
					case '\015':		/* non-significant line break */
						s = d;			/* accept any leading spaces */
						if (*src == '\012') src++;
						break;
					
					default:			/* two hex digits then */
						if (!isxdigit (c))	/* must be hex! */
						{
							fs_give ((void **) &ret);
							return NIL;
						}
						if (isdigit (c)) e = c - '0';
						else e = c - (isupper (c) ? 'A' - 10 : 'a' - 10);
						c = *src++;		/* snarf next character */
						if (!isxdigit (c))	/* must be hex! */
						{
							fs_give ((void **) &ret);
							return NIL;
						}
						if (isdigit (c)) c -= '0';
						else c -= (isupper (c) ? 'A' - 10 : 'a' - 10);
						*d++ = c + (e << 4);	/* merge the two hex digits */
						s = d;			/* note point of non-space */
						break;
				}
				break;
			
			case ' ':			/* space, possibly bogus */
				*d++ = c;			/* stash the space but don't update s */
				break;
			
			case '\015':		/* end of line */
				d = s;			/* slide back to last non-space, drop in */
			
			default:
				*d++ = c;			/* stash the character */
				s = d;			/* note point of non-space */
		}      
	}
	*d = '\0';			/* tie off results */
	*len = d - ret;		/* calculate length */

	return ret;			/* return the string */
}


/* Convert 8BIT contents to QUOTED-PRINTABLE
 * Accepts: source
 *	    length of source
 * 	    pointer to return destination length
 * Returns: destination as quoted-printable text
 */

#define MAXL (size_t) 75	/* 76th position only used by continuation = */

unsigned char *rfc822_8bit (unsigned char *src,unsigned long srcl,unsigned long *len)
{
	unsigned long lp = 0;
	unsigned char *ret = (unsigned char *)fs_get ((((size_t) srcl) * (size_t) 3) + (((size_t) srcl) / MAXL) + (size_t) 2);
	unsigned char *d = ret;
	char *hex = "0123456789ABCDEF";
	unsigned char c;
	
	if (!ret) return NULL;
	
	while (srcl--)	/* for each character */
	{
		/* true line break? */
		if (((c = *src++) == '\015') && (*src == '\012') && srcl) 
		{
			*d++ = '\015'; *d++ = *src++; srcl--;
			lp = 0;			/* reset line count */
		}
		else 	/* not a line break */
		{
			/* quoting required? */
			if (iscntrl (c) || (c == 0x7f) || (c & 0x80) || (c == '=') || ((c == ' ') && (*src == '\015'))) 
			{
				if ((lp += 3) > MAXL)	/* yes, would line overflow? */
				{
					*d++ = '='; *d++ = '\015'; *d++ = '\012';
					lp = 3;		/* set line count */
				}
				*d++ = '=';		/* quote character */
				*d++ = hex[c >> 4];	/* high order 4 bits */
				*d++ = hex[c & 0xf];	/* low order 4 bits */
			}
			else	/* ordinary character */
			{
				if ((++lp) > MAXL)	/* would line overflow? */
				{
					*d++ = '='; *d++ = '\015'; *d++ = '\012';
					lp = 1;		/* set line count */
				}
				*d++ = c;		/* ordinary character */
			}
		}
	}
	*d = '\0';			/* tie off destination */
	*len = d - ret;		/* calculate true size */
	/* try to give some space back */
	fs_resize ((void **) &ret,(size_t) *len + 1);
	return ret;
}

#endif	//IMAP