/* ========================================================================
 * Copyright 1988-2006 University of Washington
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * 
 * ========================================================================
 */

/*
 * Program:	RFC 2822 and MIME routines
 *
 * 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:	30 August 2006
 *
 * This original version of this file is
 * Copyright 1988 Stanford University
 * and 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 NationalInstitutes of Health
 * under grant number RR-00785.
 */


#include <ctype.h>
#include <stdio.h>
#include <time.h>
#include "mail.h"
#include "osdep.h"
#include "rfc822.h"
#include "misc.h"
#include "utf8.h"


/* Support for deprecated features in earlier specifications.  Note that this
 * module follows RFC 2822, and all use of "rfc822" in function names is
 * for compatibility.  Only the code identified by the conditionals below
 * follows the earlier documents.
 */

#define RFC733 1		/* parse "at" */
#define RFC822 0		/* generate A-D-L (MUST be 0 for 2822) */

/* RFC-822 static data */

#define RFC822CONT "    "	/* RFC 2822 continuation */

				/* should have been "Remailed-" */
#define RESENTPREFIX "ReSent-"
static char *resentprefix = RESENTPREFIX;
				/* syntax error host string */
static const char *errhst = ERRHOST;


/* 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 */

				/* RFC 2822 specials */
const char *specials = " ()<>@,;:\\\"[].\1\2\3\4\5\6\7\10\11\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\177";
				/* RFC 2822 phrase specials (no space) */
const char *rspecials = "()<>@,;:\\\"[].\1\2\3\4\5\6\7\10\11\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\177";
				/* RFC 2822 dot-atom specials (no dot) */
const char *wspecials = " ()<>@,;:\\\"[]\1\2\3\4\5\6\7\10\11\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\177";
				/* RFC 2045 MIME body token specials */
const char *tspecials = " ()<>@,;:\\\"[]/?=\1\2\3\4\5\6\7\10\11\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\177";

/* 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";
  }
}

/* RFC 2822 parsing routines */


/* Parse an RFC 2822 message
 * Accepts: pointer to return envelope
 *	    pointer to return body
 *	    pointer to header
 *	    header byte count
 *	    pointer to body stringstruct
 *	    pointer to local host name
 *	    recursion depth
 *	    source driver flags
 */

void rfc822_parse_msg_full (ENVELOPE **en,BODY **bdy,char *s,unsigned long i,
			    STRING *bs,char *host,unsigned long depth,
			    unsigned long flags)
{
  char c,*t,*d;
  char *tmp = (char *) fs_get ((size_t) i + 100);
  ENVELOPE *env = (*en = mail_newenvelope ());
  BODY *body = bdy ? (*bdy = mail_newbody ()) : NIL;
  long MIMEp = -1;		/* flag that MIME semantics are in effect */
  parseline_t pl = (parseline_t) mail_parameters (NIL,GET_PARSELINE,NIL);
  if (!host) host = BADHOST;	/* make sure that host is non-null */
  while (i && *s != '\n') {	/* until end of header */
    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 header */
    }

				/* find header item type */
    if (t = d = strchr (tmp,':')) {
      *d++ = '\0';		/* tie off header item, point at its data */
      while (*d == ' ') d++;	/* flush whitespace */
      while ((tmp < t--) && (*t == ' ')) *t = '\0';
      ucase (tmp);		/* coerce to uppercase */
				/* external callback */
      if (pl) (*pl) (env,tmp,d,host);
      switch (*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)
	  switch (MIMEp) {
	  case -1:		/* unknown if MIME or not */
	    if (!(MIMEp =	/* see if MIME-Version header exists */
		  search ((unsigned char *) s-1,i,
			  (unsigned char *)"\012MIME-Version",(long) 13))) {
#if 1
	      /* This is a disgusting kludge, and most of the messages which
	       * benefit from it are spam.
	       */
	      if (!strcmp (tmp+8,"TRANSFER-ENCODING") ||
		  (!strcmp (tmp+8,"TYPE") && strchr (d,'/'))) {
		MM_LOG ("Warning: MIME header encountered in non-MIME message",
			PARSE);
		MIMEp = 1;	/* declare MIME now */
	      }
	      else
#endif
		break;		/* non-MIME message */
	    }
	  case T:		/* definitely MIME */
	    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));
	  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));
	  while (c = *d++) if (c != ' ') *t++ = c;
	  *t++ = '\0';
	}
	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;
      }
    }
  }
  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,depth,flags);
}

/* Parse a message body content
 * Accepts: pointer to body structure
 *	    body string
 *	    pointer to local host name
 *	    recursion depth
 *	    source driver flags
 */

void rfc822_parse_content (BODY *body,STRING *bs,char *h,unsigned long depth,
			   unsigned long flags)
{
  char c,c1,*s,*s1;
  int f;
  unsigned long i,j,k,m;
  PARAMETER *param;
  PART *part = NIL;
  if (depth > MAXMIMEDEPTH) {	/* excessively deep recursion? */
    body->type = TYPETEXT;	/* yes, probably a malicious MIMEgram */
    MM_LOG ("Ignoring excessively deep MIME recursion",PARSE);
  }
  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->size.bytes = body->contents.text.size = i = SIZE (bs);
  if (!(flags & DR_CRLF)) body->size.bytes = strcrlflen (bs);
  switch (body->type) {		/* see if anything else special to do */
  case TYPETEXT:		/* text content */
    if (!body->parameter) {	/* no parameters set */
      body->parameter = mail_newbody_parameter ();
      body->parameter->attribute = cpystr ("CHARSET");
      while (i--) {		/* count lines and guess charset */
	c = SNX (bs);		/* get current character */
				/* charset still unknown? */
	if (!body->parameter->value) {
	  if ((c == I2C_ESC) && (i && i--) && ((c = SNX (bs)) == I2C_MULTI) &&
	      (i && i--) && (((c = SNX (bs)) == I2CS_94x94_JIS_NEW) ||
			     (c == I2CS_94x94_JIS_OLD)))
	    body->parameter->value = cpystr ("ISO-2022-JP");
	  else if (c & 0x80) body->parameter->value = cpystr ("X-UNKNOWN");
	}
	if (c == '\n') body->size.lines++;
      }
				/* otherwise assume US-ASCII */
      if (!body->parameter->value) body->parameter->value = cpystr("US-ASCII");
    }
				/* just count lines */
    else while (i--) if ((SNX (bs)) == '\n') body->size.lines++;
    break;

  case TYPEMESSAGE:		/* encapsulated message */
				/* encapsulated RFC-822 message? */
    if (!strcmp (body->subtype,"RFC822")) {
      body->nested.msg = mail_newmsg ();
      switch (body->encoding) {	/* make sure valid encoding */
      case ENC7BIT:		/* these are valid nested encodings */
      case ENC8BIT:
      case ENCBINARY:
	break;
      default:
	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 header size */
      body->nested.msg->header.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->header.offset =
	body->contents.offset;
      body->nested.msg->full.text.size = body->contents.text.size;
				/* copy header string */
      SETPOS (bs,body->contents.offset);
      s = (char *) fs_get ((size_t) j + 1);
      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_full (&body->nested.msg->env,&body->nested.msg->body,s,
			     j,bs,h,depth+1,flags);
      fs_give ((void **) &s);	/* free header 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 */
    switch (body->encoding) {	/* make sure valid encoding */
    case ENC7BIT:		/* these are valid nested encodings */
    case ENC8BIT:
    case ENCBINARY:
      break;
    default:
      MM_LOG ("Ignoring nested encoding of multipart contents",PARSE);
    }
				/* remember if digest */
    f = !strcmp (body->subtype,"DIGEST");
				/* 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) + 2;	/* length of cookie and header */
    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 && i-- && ((c = SNX (bs)) == '-') && i-- &&
	      ((c = SNX (bs)) == '-'))) break;
				/* see if cookie matches */
	if (k = j - 2) for (s = s1; i-- && *s++ == (c = SNX (bs)) && --k;);
	if (k) break;		/* strings didn't match if non-zero */
				/* terminating delimiter? */
	if ((c = ((i && i--) ? (SNX (bs)) : '\012')) == '-') {
	  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;
	}
				/* swallow trailing whitespace */
	while ((c == ' ') || (c == '\t'))
	  c = ((i && i--) ? (SNX (bs)) : '\012');
	switch (c) {		/* need newline after all of it */
	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));
				/* in case empty multipart */
    if (!body->nested.part) body->nested.part = mail_newbody_part ();
				/* parse non-empty body parts */
    for (part = body->nested.part; part; part = part->next) {
				/* part non-empty (header and/or content)? */
      if (i = part->body.mime.text.size) {
				/* move to that part of the body */
	SETPOS (bs,part->body.mime.offset);
				/* until end of header */
	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 header */
	    if (!i || !--i) s1[j++] = c = '\0';
	  }

				/* find header 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 header */
	    rfc822_parse_content_header (&part->body,ucase (s1+8),s);
	  }
	}
				/* skip header 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,depth+1,flags);
	bs->size = j;		/* restore current level size */
      }
      else {			/* zero-length part, use default subtype */
	part->body.subtype = cpystr (rfc822_default_subtype (part->body.type));
				/* see if anything else special to do */
	switch (part->body.type) {
	case TYPETEXT:		/* text content */
				/* default parameters */
	  if (!part->body.parameter) {
	    part->body.parameter = mail_newbody_parameter ();
	    part->body.parameter->attribute = cpystr ("CHARSET");
	    part->body.parameter->value = cpystr ("US-ASCII");
	  }
	  break;
	case TYPEMESSAGE:	/* encapsulated message in digest */
	  part->body.nested.msg = mail_newmsg ();
	  break;
	default:
	  break;
	}
      }
    }
    fs_give ((void **) &s1);	/* finished with scratch buffer */
    break;
  default:			/* nothing special to do in any other case */
    break;
  }
}

/* Parse RFC 2822 body content header
 * Accepts: body to write to
 *	    possible content name
 *	    remainder of header
 */

void rfc822_parse_content_header (BODY *body,char *name,char *s)
{
  char c,*t;
  long i;
  STRINGLIST *stl;
  rfc822_skipws (&s);		/* skip leading comments */
				/* 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,tspecials))) 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,tspecials))) {
	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 = (unsigned char *) ucase (cpystr (s));
	stl->text.size = strlen ((char *) 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 */
      }
    }
    else if (!(strcmp (name+1,"OCATION") || body->location))
      body->location = cpystr (s);
    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,tspecials))) break;
      c = *name;		/* remember delimiter */
      *name = '\0';		/* tie off type */
      s = ucase (rfc822_cpy(s));/* search for body type */
      for (i = 0; (i <= TYPEMAX) && body_types[i] &&
	     strcmp (s,body_types[i]); i++);
				/* record body type index */
      body->type = (i <= TYPEMAX) ? (unsigned short) i : TYPEOTHER;
				/* and name if new type */
      if (body_types[body->type]) fs_give ((void **) &s);
      else body_types[body->type] = s;
      *name = c;		/* restore delimiter */
      rfc822_skipws (&name);	/* skip whitespace */
      if ((*name == '/') &&	/* subtype? */
	  (name = rfc822_parse_word ((s = ++name),tspecials))) {
	c = *name;		/* save delimiter */
	*name = '\0';		/* tie off subtype */
	rfc822_skipws (&s);	/* copy subtype */
	if (s) body->subtype = ucase (rfc822_cpy (s));
	*name = c;		/* restore delimiter */
	rfc822_skipws (&name);	/* skip whitespace */
      }
      else if (!name) {		/* no subtype, was 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")) {
      if (!(name = rfc822_parse_word (s,tspecials))) break;
      *name = '\0';		/* tie off encoding */
      s = ucase (rfc822_cpy(s));/* search for body encoding */
      for (i = 0; (i <= ENCMAX) && body_encodings[i] &&
	   strcmp (s,body_encodings[i]); i++);
				/* record body encoding index */
      body->encoding = (i <= ENCMAX) ? (unsigned short) i : ENCOTHER;
				/* and name if new encoding */
      if (body_encodings[body->encoding]) fs_give ((void **) &s);
      else body_encodings[body->encoding] = ucase (cpystr (s));
    }
    break;
  default:			/* otherwise unknown */
    break;
  }
}

/* Parse RFC 2822 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),tspecials))) {
    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 == '=') &&	/* make sure have value */
	  (text = rfc822_parse_word ((s = ++text),tspecials))) {
	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 (!param->value) {	/* value not found? */
	param->value = cpystr ("MISSING_PARAMETER_VALUE");
	sprintf (tmp,"Missing parameter value: %.80s",param->attribute);
	MM_LOG (tmp,PARSE);
      }
    }
  }
				/* string not present */
  if (!text) MM_LOG ("Missing parameter",PARSE);
  else if (*text) {		/* must be end of poop */
    sprintf (tmp,"Unexpected characters at end of parameters: %.80s",text);
    MM_LOG (tmp,PARSE);
  }
}

/* Parse RFC 2822 address list
 * Accepts: address list to write to
 *	    input string
 *	    default host name
 */

void rfc822_parse_adrlist (ADDRESS **lst,char *string,char *host)
{
  int c;
  char *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 */
    while (*string == ',') {	/* RFC 822 allowed null addresses!! */
      ++string;			/* skip the comma */
      rfc822_skipws (&string);	/* and any leading WS */
    }
    if (!*string) string = NIL;	/* punt if ran out of string */
				/* got an address? */
    else if (adr = rfc822_parse_address (lst,last,&string,host,0)) {
      last = adr;		/* new tail address */
      if (string) {		/* analyze what follows */
	rfc822_skipws (&string);
	switch (c = *(unsigned char *) 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 RFC 2822 address
 * Accepts: address list to write to
 *	    tail of address list
 *	    pointer to input string
 *	    default host name
 *	    group nesting depth
 * Returns: new list tail
 */

ADDRESS *rfc822_parse_address (ADDRESS **lst,ADDRESS *last,char **string,
			       char *defaulthost,unsigned long depth)
{
  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,depth)) last = adr;
				/* got an address? */
  else if (adr = rfc822_parse_mailbox (string,defaulthost)) {
    if (!*lst) *lst = adr;	/* yes, first time through? */
    else last->next = adr;	/* no, append to the list */
				/* set for subsequent linking */
    for (last = adr; last->next; last = last->next);
  }
  else if (*string) return NIL;
  return last;
}

/* Parse RFC 2822 group
 * Accepts: address list to write to
 *	    pointer to tail of address list
 *	    pointer to input string
 *	    default host name
 *	    group nesting depth
 */

ADDRESS *rfc822_parse_group (ADDRESS **lst,ADDRESS *last,char **string,
			     char *defaulthost,unsigned long depth)
{
  char tmp[MAILTMPLEN];
  char *p,*s;
  ADDRESS *adr;
  if (depth > MAXGROUPDEPTH) {	/* excessively deep recursion? */
    MM_LOG ("Ignoring excessively deep group recursion",PARSE);
    return NIL;			/* probably abusive */
  }
  if (!*string) return NIL;	/* no string */
  rfc822_skipws (string);	/* skip leading WS */
  if (!**string ||		/* trailing whitespace or not group */
      ((*(p = *string) != ':') && !(p = rfc822_parse_phrase (*string))))
    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,depth+1)) {
      last = adr;		/* new tail address */
      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 RFC 2822 mailbox
 * Accepts: pointer to string pointer
 *	    default host
 * Returns: address list
 *
 * Updates string pointer
 */

ADDRESS *rfc822_parse_mailbox (char **string,char *defaulthost)
{
  ADDRESS *adr = NIL;
  char *s,*end;
  parsephrase_t pp = (parsephrase_t) mail_parameters (NIL,GET_PARSEPHRASE,NIL);
  if (!*string) return NIL;	/* no string */
  rfc822_skipws (string);	/* flush leading whitespace */
  if (!**string) return NIL;	/* empty string */
  if (*(s = *string) == '<') 	/* note start, handle case of phraseless RA */
    adr = rfc822_parse_routeaddr (s,string,defaulthost);
				/* otherwise, expect at least one word */
  else if (end = rfc822_parse_phrase (s)) {
    if ((adr = rfc822_parse_routeaddr (end,string,defaulthost))) {
				/* phrase is a personal name */
      if (adr->personal) fs_give ((void **) &adr->personal);
      *end = '\0';		/* tie off phrase */
      adr->personal = rfc822_cpy (s);
    }
				/* call external phraseparser if phrase only */
    else if (pp && rfc822_phraseonly (end) &&
	     (adr = (*pp) (s,end,defaulthost))) {
      *string = end;		/* update parse pointer */
      rfc822_skipws (string);	/* skip WS in the normal way */
    }
    else adr = rfc822_parse_addrspec (s,string,defaulthost);
  }
  return adr;			/* return the address */
}


/* Check if address is a phrase only
 * Accepts: pointer to end of phrase
 * Returns: T if phrase only, else NIL;
 */

long rfc822_phraseonly (char *end)
{
  while (*end == ' ') ++end;	/* call rfc822_skipws() instead?? */
  switch (*end) {
  case '\0': case ',': case ';':
    return LONGT;		/* is a phrase only */
  }
  return NIL;			/* something other than phase is here */
}

/* Parse RFC 2822 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 *s,*t,*adl;
  size_t adllen,i;
  if (!string) return NIL;
  rfc822_skipws (&string);	/* flush leading whitespace */
				/* must start with open broket */
  if (*string != '<') return NIL;
  t = ++string;			/* see if A-D-L there */
  rfc822_skipws (&t);		/* flush leading whitespace */
  for (adl = NIL,adllen = 0;	/* parse possible A-D-L */
       (*t == '@') && (s = rfc822_parse_domain (t+1,&t));) {
    i = strlen (s) + 2;		/* @ plus domain plus delimiter or NUL */
    if (adl) {			/* have existing A-D-L? */
      fs_resize ((void **) &adl,adllen + i);
      sprintf (adl + adllen - 1,",@%s",s);
    }
				/* write initial A-D-L */
    else sprintf (adl = (char *) fs_get (i),"@%s",s);
    adllen += i;		/* new A-D-L length */
    fs_give ((void **) &s);	/* don't need domain any more */
    rfc822_skipws (&t);		/* skip WS */
    if (*t != ',') break;	/* put if not comma */
    t++;			/* skip the comma */
    rfc822_skipws (&t);		/* skip WS */
  }
  if (adl) {			/* got an A-D-L? */
    if (*t != ':') {		/* make sure syntax good */
      sprintf (tmp,"Unterminated at-domain-list: %.80s%.80s",adl,t);
      MM_LOG (tmp,PARSE);
    }
    else string = ++t;		/* continue parse from this point */
  }

				/* parse address spec */
  if (!(adr = rfc822_parse_addrspec (string,ret,defaulthost))) {
    if (adl) fs_give ((void **) &adl);
    return NIL;
  }
  if (adl) adr->adl = adl;	/* have an A-D-L? */
  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 RFC 2822 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 c,*s,*t,*v,*end;
  if (!string) return NIL;	/* no string */
  rfc822_skipws (&string);	/* flush leading whitespace */
  if (!*string) return NIL;	/* empty string */
				/* find end of mailbox */
  if (!(t = rfc822_parse_word (string,wspecials))) return NIL;
  adr = mail_newaddr ();	/* create address block */
  c = *t;			/* remember delimiter */
  *t = '\0';			/* tie off mailbox */
				/* copy mailbox */
  adr->mailbox = rfc822_cpy (string);
  *t = c;			/* restore delimiter */
  end = t;			/* remember end of mailbox */
  rfc822_skipws (&t);		/* skip whitespace */
  while (*t == '.') {		/* some cretin taking RFC 822 too seriously? */
    string = ++t;		/* skip past the dot and any WS */
    rfc822_skipws (&string);
				/* get next word of mailbox */
    if (t = rfc822_parse_word (string,wspecials)) {
      end = t;			/* remember new end of mailbox */
      c = *t;			/* remember delimiter */
      *t = '\0';		/* tie off word */
      s = rfc822_cpy (string);	/* copy successor part */
      *t = c;			/* restore delimiter */
				/* build new mailbox */
      sprintf (v = (char *) fs_get (strlen (adr->mailbox) + strlen (s) + 2),
	       "%s.%s",adr->mailbox,s);
      fs_give ((void **) &adr->mailbox);
      adr->mailbox = v;		/* new host name */
      rfc822_skipws (&t);	/* skip WS after mailbox */
    }
    else {			/* barf */
      MM_LOG ("Invalid mailbox part after .",PARSE);
      break;
    }
  }
  t = end;			/* remember delimiter in case no host */

  rfc822_skipws (&end);		/* sniff ahead at what follows */
#if RFC733			/* RFC 733 used "at" instead of "@" */
  if (((*end == 'a') || (*end == 'A')) &&
      ((end[1] == 't') || (end[1] == 'T')) &&
      ((end[2] == ' ') || (end[2] == '\t') || (end[2] == '\015') ||
       (end[2] == '\012') || (end[2] == '(')))
    *++end = '@';
#endif
  if (*end != '@') end = t;	/* host name missing */
				/* otherwise parse host name */
  else if (!(adr->host = rfc822_parse_domain (++end,&end)))
    adr->host = cpystr (errhst);
				/* default host if missing */
  if (!adr->host) adr->host = cpystr (defaulthost);
				/* try person name in comments if missing */
  if (end && !(adr->personal && *adr->personal)) {
    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 RFC 2822 domain
 * Accepts: string pointer
 *	    pointer to return end of domain
 * Returns: domain name or NIL if failure
 */

char *rfc822_parse_domain (char *string,char **end)
{
  char *ret = NIL;
  char c,*s,*t,*v;
  rfc822_skipws (&string);	/* skip whitespace */
  if (*string == '[') {		/* domain literal? */
    if (!(*end = rfc822_parse_word (string + 1,"]\\")))
      MM_LOG ("Empty domain literal",PARSE);
    else if (**end != ']') MM_LOG ("Unterminated domain literal",PARSE);
    else {
      size_t len = ++*end - string;
      strncpy (ret = (char *) fs_get (len + 1),string,len);
      ret[len] = '\0';		/* tie off literal */
    }
  }
				/* search for end of host */
  else if (t = rfc822_parse_word (string,wspecials)) {
    c = *t;			/* remember delimiter */
    *t = '\0';			/* tie off host */
    ret = rfc822_cpy (string);	/* copy host */
    *t = c;			/* restore delimiter */
    *end = t;			/* remember end of domain */
    rfc822_skipws (&t);		/* skip WS after host */
    while (*t == '.') {		/* some cretin taking RFC 822 too seriously? */
      string = ++t;		/* skip past the dot and any WS */
      rfc822_skipws (&string);
      if (string = rfc822_parse_domain (string,&t)) {
	*end = t;		/* remember new end of domain */
	c = *t;			/* remember delimiter */
	*t = '\0';		/* tie off host */
	s = rfc822_cpy (string);/* copy successor part */
	*t = c;			/* restore delimiter */
				/* build new domain */
	sprintf (v = (char *) fs_get (strlen (ret) + strlen (s) + 2),
		 "%s.%s",ret,s);
	fs_give ((void **) &ret);
	ret = v;		/* new host name */
	rfc822_skipws (&t);	/* skip WS after domain */
      }
      else {			/* barf */
	MM_LOG ("Invalid domain part after .",PARSE);
	break;
      }
    }
  }
  else MM_LOG ("Missing or invalid host name after @",PARSE);
  return ret;
}

/* Parse RFC 2822 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 RFC 2822 word
 * Accepts: string pointer
 *	    delimiter (or NIL for phrase word parsing)
 * 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 */
  str = s;			/* hunt pointer for strpbrk */
  while (T) {			/* look for delimiter, return if none */
    if (!(st = strpbrk (str,delimiters ? delimiters : wspecials)))
      return str + strlen (str);
				/* ESC in phrase */
    if (!delimiters && (*st == I2C_ESC)) {
      str = ++st;		/* always skip past ESC */
      switch (*st) {		/* special hack for RFC 1468 (ISO-2022-JP) */
      case I2C_MULTI:		/* multi byte sequence */
	switch (*++st) {
	case I2CS_94x94_JIS_OLD:/* old JIS (1978) */
	case I2CS_94x94_JIS_NEW:/* new JIS (1983) */
	  str = ++st;		/* skip past the shift to JIS */
	  while (st = strchr (st,I2C_ESC))
	    if ((*++st == I2C_G0_94) && ((st[1] == I2CS_94_ASCII) ||
					 (st[1] == I2CS_94_JIS_ROMAN) ||
					 (st[1] == I2CS_94_JIS_BUGROM))) {
	      str = st += 2;	/* skip past the shift back to ASCII */
	      break;
	    }
				/* eats entire text if no shift back */
	  if (!st || !*st) return str + strlen (str);
	}
	break;
      case I2C_G0_94:		/* single byte sequence */
	switch (st[1]) {
	case I2CS_94_ASCII:	/* shift to ASCII */
	case I2CS_94_JIS_ROMAN:	/* shift to JIS-Roman */
	case I2CS_94_JIS_BUGROM:/* old buggy definition of JIS-Roman */
	  str = st + 2;		/* skip past the shift */
	  break;
	}
      }
    }

    else 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 RFC 2822 format string
 * Accepts: string
 * Returns: copy of string
 */

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


/* Unquote an RFC 2822 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 RFC 2822 whitespace
 * Accepts: pointer to string pointer
 */

void rfc822_skipws (char **s)
{
  while (T) switch (**s) {
  case ' ': case '\t': case '\015': case '\012':
    ++*s;			/* skip all forms of LWSP */
    break;
  case '(':			/* start of comment */
    if (rfc822_skip_comment (s,(long) NIL)) break;
  default:
    return;			/* end of whitespace */
  }
}


/* Skips RFC 2822 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) {		/* next character non-null? */
      t = s1;			/* update last significant character pointer */
      break;			/* all OK */
    }
  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 */
}

/* Buffered output routines */


/* Output character to buffer
 * Accepts: buffer
 *	    character to write
 * Returns: T if success, NIL if error
 */

static long rfc822_output_char (RFC822BUFFER *buf,int c)
{
  *buf->cur++ = c;		/* add character, soutr buffer if full */
  return (buf->cur == buf->end) ? rfc822_output_flush (buf) : LONGT;
}


/* Output data to buffer
 * Accepts: buffer
 *	    data to write
 *	    size of data
 * Returns: T if success, NIL if error
 */

static long rfc822_output_data (RFC822BUFFER *buf,char *string,long len)
{
  while (len) {			/* until request satified */
    long i;
    if (i = min (len,buf->end - buf->cur)) {
      memcpy (buf->cur,string,i);
      buf->cur += i;		/* blat data */
      string += i;
      len -= i;
    }
				/* soutr buffer now if full */
    if (len && !rfc822_output_flush (buf)) return NIL;
  }
  return LONGT;
}

/* Output string to buffer
 * Accepts: buffer
 *	    string to write
 * Returns: T if success, NIL if error
 */

static long rfc822_output_string (RFC822BUFFER *buf,char *string)
{
  return rfc822_output_data (buf,string,strlen (string));
}


/* Flush buffer
 * Accepts: buffer
 *	    I/O routine
 *	    stream for I/O routine
 * Returns: T if success, NIL if error
 */

long rfc822_output_flush (RFC822BUFFER *buf)
{
  *buf->cur = '\0';		/* tie off buffer at this point */
  return (*buf->f) (buf->s,buf->cur = buf->beg);
}

/* Message writing routines */


/* Output RFC 822 message
 * Accepts: temporary buffer as a SIZEDTEXT
 *	    envelope
 *	    body
 *	    I/O routine
 *	    stream for I/O routine
 *	    non-zero if 8-bit output desired
 * Returns: T if successful, NIL if failure
 *
 * This routine always uses standard specials for phrases and does not write
 * bcc entries, since it is called from the SMTP and NNTP routines.  If you
 * need to do something different you need to arm an rfc822outfull_t and/or
 * rfc822out_t function.
 */

long rfc822_output_full (RFC822BUFFER *buf,ENVELOPE *env,BODY *body,long ok8)
{
  rfc822outfull_t r822of =
    (rfc822outfull_t) mail_parameters (NIL,GET_RFC822OUTPUTFULL,NIL);
  rfc822out_t r822o = (rfc822out_t) mail_parameters (NIL,GET_RFC822OUTPUT,NIL);
				/* call external RFC 2822 output generator */
  if (r822of) return (*r822of) (buf,env,body,ok8);
  else if (r822o) return (*r822o) (buf->cur,env,body,buf->f,buf->s,ok8);
				/* encode body as necessary */
  if (ok8) rfc822_encode_body_8bit (env,body);
  else rfc822_encode_body_7bit (env,body);
				/* output header and body */
  return rfc822_output_header (buf,env,body,NIL,NIL) &&
    rfc822_output_text (buf,body) && rfc822_output_flush (buf);
}

/* Output RFC 822 header
 * Accepts: buffer
 *	    envelope
 *	    body
 *	    non-standard specials to be used for phrases if non-NIL
 *	    flags (non-zero to include bcc
 * Returns: T if success, NIL if failure
 */

long rfc822_output_header (RFC822BUFFER *buf,ENVELOPE *env,BODY *body,
			   const char *specials,long flags)
{
  long i = env->remail ? strlen (env->remail) : 0;
  return			/* write header */
    (!i ||		      /* snip extra CRLF from remail header */
     rfc822_output_data (buf,env->remail,
			 ((i > 4) && (env->remail[i-4] == '\015')) ?
			 i - 2 : i)) &&
    rfc822_output_header_line (buf,"Newsgroups",i,env->newsgroups) &&
    rfc822_output_header_line (buf,"Date",i,env->date) &&
    rfc822_output_address_line (buf,"From",i,env->from,specials) &&
    rfc822_output_address_line (buf,"Sender",i,env->sender,specials) &&
    rfc822_output_address_line (buf,"Reply-To",i,env->reply_to,specials) &&
    rfc822_output_header_line (buf,"Subject",i,env->subject) &&
    ((env->bcc && !(env->to || env->cc)) ?
     rfc822_output_string (buf,"To: undisclosed recipients: ;\015\012") :
     LONGT) &&
    rfc822_output_address_line (buf,"To",i,env->to,specials) &&
    rfc822_output_address_line (buf,"cc",i,env->cc,specials) &&
    (flags ? rfc822_output_address_line (buf,"bcc",i,env->bcc,specials) : T) &&
    rfc822_output_header_line (buf,"In-Reply-To",i,env->in_reply_to) &&
    rfc822_output_header_line (buf,"Message-ID",i,env->message_id) &&
    rfc822_output_header_line (buf,"Followup-to",i,env->followup_to) &&
    rfc822_output_header_line (buf,"References",i,env->references) &&
    (env->remail || !body ||
     (rfc822_output_string (buf,"MIME-Version: 1.0\015\012") &&
      rfc822_output_body_header (buf,body))) &&
				/* write terminating blank line */
    rfc822_output_string (buf,"\015\012");
}

/* Output RFC 2822 header text line
 * Accepts: buffer
 *	    pointer to header type
 *	    non-NIL if resending
 *	    pointer to text
 * Returns: T if success, NIL if failure
 */

long rfc822_output_header_line (RFC822BUFFER *buf,char *type,long resent,
				char *text)
{
  return !text ||
    ((resent ? rfc822_output_string (buf,resentprefix) : LONGT) &&
     rfc822_output_string (buf,type) && rfc822_output_string (buf,": ") &&
     rfc822_output_string (buf,text) && rfc822_output_string (buf,"\015\012"));
}


/* Output RFC 2822 header address line
 * Accepts: buffer
 *	    pointer to header type
 *	    non-NIL if resending
 *	    address(s) to interpret
 *	    non-standard specials to be used for phrases if non-NIL
 * Returns: T if success, NIL if failure
 */

long rfc822_output_address_line (RFC822BUFFER *buf,char *type,long resent,
				 ADDRESS *adr,const char *specials)
{
  long pretty = strlen (type);
  return !adr ||
    ((resent ? rfc822_output_string (buf,resentprefix) : LONGT) &&
     rfc822_output_data (buf,type,pretty) && rfc822_output_string (buf,": ") &&
     rfc822_output_address_list (buf,adr,
				 resent ? pretty + sizeof (RESENTPREFIX) - 1 :
				 pretty,specials) &&
     rfc822_output_string (buf,"\015\012"));
}

/* Output RFC 2822 address list
 * Accepts: buffer
 *	    pointer to address list
 *	    non-zero if pretty-printing
 *	    non-standard specials to be used for phrases if non-NIL
 * Returns: T if success, NIL if failure
 */

long rfc822_output_address_list (RFC822BUFFER *buf,ADDRESS *adr,long pretty,
				 const char *specials)
{
  long n;
				/* default to rspecials */
  if (!specials) specials = rspecials;
  for (n = 0; adr; adr = adr->next) {
    char *base = buf->cur;
    if (adr->host) {		/* ordinary address? */
      if (!(pretty && n)) {	/* suppress if pretty and in group */
	if (			/* use phrase <route-addr> if phrase */
#if RFC822
	    adr->adl ||		/* or A-D-L */
#endif
	    (adr->personal && *adr->personal)) {
	  if (!((adr->personal ? rfc822_output_cat (buf,adr->personal,
						    rspecials) : LONGT) &&
		rfc822_output_string (buf," <") &&
		rfc822_output_address (buf,adr) &&
		rfc822_output_string (buf,">"))) return NIL;
	}
	else if (!rfc822_output_address (buf,adr)) return NIL;
	if (adr->next && adr->next->mailbox &&
	    !rfc822_output_string (buf,", ")) return NIL;
      }
    }
    else if (adr->mailbox) {	/* start of group? */
				/* yes, write group */
      if (!(rfc822_output_cat (buf,adr->mailbox,rspecials) &&
	    rfc822_output_string (buf,": "))) return NIL;
      ++n;			/* in a group now */
    }
    else if (n) {		/* must be end of group (but be paranoid) */
      if (!rfc822_output_char (buf,';') ||
	  ((!--n && adr->next && adr->next->mailbox) &&
	   !rfc822_output_string (buf,", "))) return NIL;
    }
    if (pretty &&		/* pretty printing? */
	((pretty += ((buf->cur > base) ? buf->cur - base :
		     (buf->end - base) + (buf->cur - buf->beg))) >= 78)) {
      if (!(rfc822_output_string (buf,"\015\012") &&
	    rfc822_output_string (buf,RFC822CONT))) return NIL;
      base = buf->cur;	/* update base for pretty printing */
      pretty = sizeof (RFC822CONT) - 1;
    }
  }
  return LONGT;
}

/* Write RFC 2822 route-address to string
 * Accepts: buffer
 *	    pointer to single address
 * Returns: T if success, NIL if failure
 */

long rfc822_output_address (RFC822BUFFER *buf,ADDRESS *adr)
{
  return !adr || !adr->host ||
    (
#if RFC822			/* old code with A-D-L support */
     (!adr->adl || (rfc822_output_string (buf,adr->adl) &&
		    rfc822_output_char (buf,':'))) &&
#endif
     rfc822_output_cat (buf,adr->mailbox,NIL) &&
     ((*adr->host == '@') ||	/* unless null host (HIGHLY discouraged!) */
      (rfc822_output_char (buf,'@') &&
       rfc822_output_cat (buf,adr->host,NIL))));
}


/* Output RFC 2822 string with concatenation
 * Accepts: buffer
 *	    string to concatenate
 *	    list of special characters or NIL for dot-atom format
 * Returns: T if success, NIL if failure
 */

long rfc822_output_cat (RFC822BUFFER *buf,char *src,const char *specials)
{
  char *s;
  if (!*src ||			/* empty string or any specials present? */
      (specials ? (T && strpbrk (src,specials)) :
       (strpbrk (src,wspecials) || (*src == '.') || strstr (src,"..") ||
	(src[strlen (src) - 1] == '.')))) {
				/* yes, write as quoted string*/
    if (!rfc822_output_char (buf,'"')) return NIL;
				/* embedded quote characters? */
    for (; s = strpbrk (src,"\\\""); src = s + 1) {
				/* yes, insert quoting */
      if (!(rfc822_output_data (buf,src,s-src) &&
	    rfc822_output_char (buf,'\\') &&
	    rfc822_output_char (buf,*s))) return NIL;
    }
				/* return string and trailing quote*/
    return rfc822_output_string (buf,src) && rfc822_output_char (buf,'"');
  }
				/* easy case */
  return rfc822_output_string (buf,src);
}

/* Output MIME parameter list
 * Accepts: buffer
 *	    parameter list
 * Returns: T if success, NIL if failure
 */

long rfc822_output_parameter (RFC822BUFFER *buf,PARAMETER *param)
{
  while (param) {
    if (rfc822_output_string (buf,"; ") &&
	rfc822_output_string (buf,param->attribute) &&
	rfc822_output_char (buf,'=') &&
	rfc822_output_cat (buf,param->value,tspecials)) param = param->next;
    else return NIL;
  }
  return LONGT;
}


/* Output RFC 2822 stringlist
 * Accepts: buffer
 *	    stringlist
 * Returns: T if success, NIL if failure
 */

long rfc822_output_stringlist (RFC822BUFFER *buf,STRINGLIST *stl)
{
  while (stl)
    if (!rfc822_output_cat (buf,(char *) stl->text.data,tspecials) ||
	((stl = stl->next) && !rfc822_output_string (buf,", ")))
      return NIL;
  return LONGT;
}

/* Output body content header
 * Accepts: buffer
 *	    body to interpret
 * Returns: T if success, NIL if failure
 */

long rfc822_output_body_header (RFC822BUFFER *buf,BODY *body)
{
  return			/* type and subtype*/
    rfc822_output_string (buf,"Content-Type: ") &&
    rfc822_output_string (buf,body_types[body->type]) &&
    rfc822_output_char (buf,'/') &&
    rfc822_output_string (buf,body->subtype ? body->subtype :
			  rfc822_default_subtype (body->type)) &&
				/* parameters (w/ US-ASCII default */
    (body->parameter ? rfc822_output_parameter (buf,body->parameter) :
     ((body->type != TYPETEXT) ||
      (rfc822_output_string (buf,"; CHARSET=") &&
       rfc822_output_string (buf,(body->encoding == ENC7BIT) ?
			     "US-ASCII" : "X-UNKNOWN")))) &&
    (!body->encoding ||	    /* note: 7BIT never output as encoding! */
     (rfc822_output_string (buf,"\015\012Content-Transfer-Encoding: ") &&
      rfc822_output_string (buf,body_encodings[body->encoding]))) &&
    (!body->id ||		/* identification */
     (rfc822_output_string (buf,"\015\012Content-ID: ") &&
      rfc822_output_string (buf,body->id))) &&
    (!body->description ||	/* description */
     (rfc822_output_string (buf,"\015\012Content-Description: ") &&
      rfc822_output_string (buf,body->description))) &&
    (!body->md5 ||		/* MD5 checksum */
     (rfc822_output_string (buf,"\015\012Content-MD5: ") &&
      rfc822_output_string (buf,body->md5))) &&
    (!body->language ||		/* language */
     (rfc822_output_string (buf,"\015\012Content-Language: ") &&
      rfc822_output_stringlist (buf,body->language))) &&
    (!body->location ||		/* location */
     (rfc822_output_string (buf,"\015\012Content-Location: ") &&
      rfc822_output_string (buf,body->location))) &&
    (!body->disposition.type ||	/* disposition */
     (rfc822_output_string (buf,"\015\012Content-Disposition: ") &&
      rfc822_output_string (buf,body->disposition.type) &&
      rfc822_output_parameter (buf,body->disposition.parameter))) &&
    rfc822_output_string (buf,"\015\012");
}

/* Encode a body for 7BIT transmittal
 * Accepts: envelope
 *	    body
 */

void rfc822_encode_body_7bit (ENVELOPE *env,BODY *body)
{
  void *f;
  PART *part;
  PARAMETER **param;
  if (body) switch (body->type) {
  case TYPEMULTIPART:		/* multi-part */
    for (param = &body->parameter;
	 *param && strcmp ((*param)->attribute,"BOUNDARY");
	 param = &(*param)->next);
    if (!*param) {		/* cookie not set up yet? */
      char tmp[MAILTMPLEN];	/* make cookie not in BASE64 or QUOTEPRINT*/
      sprintf (tmp,"%lu-%lu-%lu=:%lu",(unsigned long) gethostid (),
	       (unsigned long) random (),(unsigned long) time (0),
	       (unsigned long) getpid ());
      (*param) = mail_newbody_parameter ();
      (*param)->attribute = cpystr ("BOUNDARY");
      (*param)->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",PARSE);
      break;
    case ENCBINARY:
      MM_LOG ("Binary included message in 7-bit message body",PARSE);
      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 =
	rfc822_8bit (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 =
	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,BODY *body)
{
  void *f;
  PART *part;
  PARAMETER **param;
  if (body) switch (body->type) {
  case TYPEMULTIPART:		/* multi-part */
    for (param = &body->parameter;
	 *param && strcmp ((*param)->attribute,"BOUNDARY");
	 param = &(*param)->next);
    if (!*param) {		/* cookie not set up yet? */
      char tmp[MAILTMPLEN];	/* make cookie not in BASE64 or QUOTEPRINT*/
      sprintf (tmp,"%lu-%lu-%lu=:%lu",(unsigned long) gethostid (),
	       (unsigned long) random (),(unsigned long) time (0),
	       (unsigned long) getpid ());
      (*param) = mail_newbody_parameter ();
      (*param)->attribute = cpystr ("BOUNDARY");
      (*param)->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",PARSE);
      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 =
	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 text
 * Accepts: buffer
 *	    body
 * Returns: T if successful, NIL if failure
 */

long rfc822_output_text (RFC822BUFFER *buf,BODY *body)
{
				/* MULTIPART gets special handling */
  if (body->type == TYPEMULTIPART) {
    char *cookie,tmp[MAILTMPLEN];
    PARAMETER *param;
    PART *part;
				/* find cookie */
    for (param = body->parameter; param && strcmp (param->attribute,"BOUNDARY");
	 param = param->next);
    if (param) cookie = param->value;
    else {		  /* make cookie not in BASE64 or QUOTEPRINT*/
      sprintf (cookie = tmp,"%lu-%lu-%lu=:%lu",(unsigned long) gethostid (),
	       (unsigned long) random (),(unsigned long) time (0),
	       (unsigned long) getpid ());
      (param = mail_newbody_parameter ())->attribute = cpystr ("BOUNDARY");
      param->value = cpystr (tmp);
      param->next = body->parameter;
      body->parameter = param;
    }
				/* output each part */
    for (part = body->nested.part; part; part = part->next)
      if (!(rfc822_output_string (buf,"--") &&
	    rfc822_output_string (buf,cookie) &&
	    rfc822_output_string (buf,"\015\012") &&
	    rfc822_output_body_header (buf,&part->body) &&
	    rfc822_output_string (buf,"\015\012") &&
	    rfc822_output_text (buf,&part->body))) return NIL;
				/* output trailing cookie */
    return rfc822_output_string (buf,"--") &&
      rfc822_output_string (buf,cookie) &&
      rfc822_output_string (buf,"--\015\012");
  }
				/* output segment and trailing CRLF */
  return (!body->contents.text.data ||
	  rfc822_output_string (buf,(char *) body->contents.text.data)) &&
    rfc822_output_string (buf,"\015\012");
}

/* Body contents encoding/decoding routines */


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

#define WSP 0176		/* NUL, TAB, LF, FF, CR, SPC */
#define JNK 0177
#define PAD 0100

void *rfc822_base64 (unsigned char *src,unsigned long srcl,unsigned long *len)
{
  char c,*s,tmp[MAILTMPLEN];
  void *ret = fs_get ((size_t) ((*len = 4 + ((srcl * 3) / 4))) + 1);
  char *d = (char *) ret;
  int e;
  static char decode[256] = {
   WSP,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,WSP,WSP,JNK,WSP,WSP,JNK,JNK,
   JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,
   WSP,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,076,JNK,JNK,JNK,077,
   064,065,066,067,070,071,072,073,074,075,JNK,JNK,JNK,PAD,JNK,JNK,
   JNK,000,001,002,003,004,005,006,007,010,011,012,013,014,015,016,
   017,020,021,022,023,024,025,026,027,030,031,JNK,JNK,JNK,JNK,JNK,
   JNK,032,033,034,035,036,037,040,041,042,043,044,045,046,047,050,
   051,052,053,054,055,056,057,060,061,062,063,JNK,JNK,JNK,JNK,JNK,
   JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,
   JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,
   JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,
   JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,
   JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,
   JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,
   JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,
   JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK
  };
				/* initialize block */
  memset (ret,0,((size_t) *len) + 1);
  *len = 0;			/* in case we return an error */

				/* simple-minded decode */
  for (e = 0; srcl--; ) switch (c = decode[*src++]) {
  default:			/* valid BASE64 data 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;
    }
    break;
  case WSP:			/* whitespace */
    break;
  case PAD:			/* padding */
    switch (e++) {		/* check quantum position */
    case 3:			/* one = is good enough in quantum 3 */
				/* make sure no data characters in remainder */
      for (; srcl; --srcl) switch (decode[*src++]) {
				/* ignore space, junk and extraneous padding */
      case WSP: case JNK: case PAD:
	break;
      default:			/* valid BASE64 data character */
	/* This indicates bad MIME.  One way that it can be caused is if
	   a single-section message was BASE64 encoded and then something
	   (e.g. a mailing list processor) appended text.  The problem is
	   that in 1 out of 3 cases, there is no padding and hence no way
	   to detect the end of the data.  Consequently, prudent software
	   will always encapsulate a BASE64 segment inside a MULTIPART.
	   */
	sprintf (tmp,"Possible data truncation in rfc822_base64(): %.80s",
		 (char *) src - 1);
	if (s = strpbrk (tmp,"\015\012")) *s = NIL;
	mm_log (tmp,PARSE);
	srcl = 1;		/* don't issue any more messages */
	break;
      }
      break;
    case 2:			/* expect a second = in quantum 2 */
      if (srcl && (*src == '=')) break;
    default:			/* impossible quantum position */
      fs_give (&ret);
      return NIL;
    }
    break;
  case JNK:			/* junk character */
    fs_give (&ret);
    return NIL;
  }
  *len = d - (char *) ret;	/* calculate data length */
  *d = '\0';			/* NUL terminate just in case */
  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);
				/* process tuplets */
  for (i = 0; srcl >= 3; s += 3, srcl -= 3) {
    *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) + (s[1] >> 4)) & 0x3f];
				/* byte 3: low 4 bits (2), high 2 bits (3) */
    *d++ = v[((s[1] << 2) + (s[2] >> 6)) & 0x3f];
    *d++ = v[s[2] & 0x3f];	/* byte 4: low 6 bits (3) */
    if ((++i) == 15) {		/* output 60 characters? */
      i = 0;			/* restart line break count, insert CRLF */
      *d++ = '\015'; *d++ = '\012';
    }
  }
  if (srcl) {
    *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 or NIL if error
 */

unsigned char *rfc822_qprint (unsigned char *src,unsigned long srcl,
			      unsigned long *len)
{
  char tmp[MAILTMPLEN];
  unsigned int bogon = NIL;
  unsigned char *ret = (unsigned char *) fs_get ((size_t) srcl + 1);
  unsigned char *d = ret;
  unsigned char *t = d;
  unsigned char *s = src;
  unsigned char c,e;
  *len = 0;			/* in case we return an error */
				/* until run out of characters */
  while (((unsigned long) (s - src)) < srcl) {
    switch (c = *s++) {		/* what type of character is it? */
    case '=':			/* quoting character */
      if (((unsigned long) (s - src)) < srcl) switch (c = *s++) {
      case '\0':		/* end of data */
	s--;			/* back up pointer */
	break;
      case '\015':		/* non-significant line break */
	if ((((unsigned long) (s - src)) < srcl) && (*s == '\012')) s++;
      case '\012':		/* bare LF */
	t = d;			/* accept any leading spaces */
	break;
      default:			/* two hex digits then */
	if (!(isxdigit (c) && (((unsigned long) (s - src)) < srcl) &&
	      (e = *s++) && isxdigit (e))) {
	  /* This indicates bad MIME.  One way that it can be caused is if
	     a single-section message was QUOTED-PRINTABLE encoded and then
	     something (e.g. a mailing list processor) appended text.  The
	     problem is that there is no way to determine where the encoded
	     data ended and the appended crud began.  Consequently, prudent
	     software will always encapsulate a QUOTED-PRINTABLE segment
	     inside a MULTIPART.
	   */
	  if (!bogon++) {	/* only do this once */
	    sprintf (tmp,"Invalid quoted-printable sequence: =%.80s",
		   (char *) s - 1);
	    mm_log (tmp,PARSE);
	  }
	  *d++ = '=';		/* treat = as ordinary character */
	  *d++ = c;		/* and the character following */
	  t = d;		/* note point of non-space */
	  break;
	}
	*d++ = hex2byte (c,e);	/* merge the two hex digits */
	t = 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 */
    case '\012':		/* bare LF */
      d = t;			/* slide back to last non-space, drop in */
    default:
      *d++ = c;			/* stash the character */
      t = 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) (3*srcl + 3*(((3*srcl)/MAXL) + 1)));
  unsigned char *d = ret;
  char *hex = "0123456789ABCDEF";
  unsigned char c;
  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;
}

/* Legacy Routines */

/*
 * WARNING: These routines are for compatibility with old software only.
 *
 * Their use in new software is to be avoided.
 *
 * These interfaces do not provide satisfactory buffer checking.  In
 * versions of c-client prior to imap-2005, they did not provide any
 * buffer checking at all.
 *
 * As a half-hearted attempt, these new compatability functions for the
 * legacy interfaces limit what they write to size SENDBUFLEN and will
 * fatal() if more than that is written.  However, that isn't good enough
 * since several of these functions *append* to the buffer, and return an
 * updated pointer.  Consequently, there is no way of knowing what the
 * actual available space is in the buffer, yet the function will still
 * write up to SENDBUFLEN bytes even if there is much less space actually
 * available.  The result is a buffer overflow.
 *
 * You won't get a buffer overflow if you never attempt to append using
 * these interfaces, but you can get the fatal() if it tries to write
 * more than SENDBUFLEN bytes.
 *
 * To avoid this problem, use the corresponding rfc822_output_???()
 * functions instead, e.g., rfc822_output_address() instead of
 * rfc822_address().
 *
 */

/* Flush routine, only called if overflow
 * Accepts: stream
 *	    string to output
 * Returns: never
 */

static long rfc822_legacy_soutr (void *stream,char *string)
{
  fatal ("rfc822.c legacy routine buffer overflow");
  return NIL;
}

/* Legacy write RFC 2822 header from message structure
 * Accepts: scratch buffer to write into
 *	    message envelope
 *	    message body
 */

void rfc822_header (char *header,ENVELOPE *env,BODY *body)
{
  RFC822BUFFER buf;
				/* write at start of buffer */
  buf.end = (buf.beg = buf.cur = header) + SENDBUFLEN - 1;
  buf.f = rfc822_legacy_soutr;
  buf.s = NIL;
  rfc822_output_header (&buf,env,body,NIL,NIL);
  *buf.cur = '\0';		/* tie off buffer */
}


/* Legacy write RFC 2822 text from header line
 * Accepts: pointer to destination string pointer
 *	    pointer to header type
 *	    message to interpret
 *	    pointer to text
 */

void rfc822_header_line (char **header,char *type,ENVELOPE *env,char *text)
{
  RFC822BUFFER buf;
				/* append to buffer */
  buf.end = (buf.beg = buf.cur = *header + strlen (*header)) + SENDBUFLEN - 1;
  buf.f = rfc822_legacy_soutr;
  buf.s = NIL;
  rfc822_output_header_line (&buf,type,env->remail ? LONGT : NIL,text);
  *(*header = buf.cur) = '\0';	/* tie off buffer */
}

/* Legacy write RFC 2822 address from header line
 * Accepts: pointer to destination string pointer
 *	    pointer to header type
 *	    message to interpret
 *	    address to interpret
 */

void rfc822_address_line (char **header,char *type,ENVELOPE *env,ADDRESS *adr)
{
  RFC822BUFFER buf;
				/* append to buffer */
  buf.end = (buf.beg = buf.cur = *header + strlen (*header)) + SENDBUFLEN - 1;
  buf.f = rfc822_legacy_soutr;
  buf.s = NIL;
  rfc822_output_address_line (&buf,type,env->remail ? LONGT : NIL,adr,NIL);
  *(*header = buf.cur) = '\0';	/* tie off buffer */
}


/* Legacy write RFC 2822 address list
 * Accepts: pointer to destination string
 *	    address to interpret
 *	    header base if pretty-printing
 * Returns: end of destination string
 */

char *rfc822_write_address_full (char *dest,ADDRESS *adr,char *base)
{
  RFC822BUFFER buf;
				/* append to buffer */
  buf.end = (buf.beg = buf.cur = dest + strlen (dest)) + SENDBUFLEN - 1;
  buf.f = rfc822_legacy_soutr;
  buf.s = NIL;
  rfc822_output_address_list (&buf,adr,base ? dest - base : 0,NIL);
  *buf.cur = '\0';		/* tie off buffer */
  return buf.cur;
}


/* Legacy write RFC 2822 route-address to string
 * Accepts: pointer to destination string
 *	    address to interpret
 */

void rfc822_address (char *dest,ADDRESS *adr)
{
  RFC822BUFFER buf;
				/* append to buffer */
  buf.end = (buf.beg = buf.cur = dest + strlen (dest)) + SENDBUFLEN - 1;
  buf.f = rfc822_legacy_soutr;
  buf.s = NIL;
  rfc822_output_address (&buf,adr);
  *buf.cur = '\0';		/* tie off buffer */
}

/* Concatenate RFC 2822 string
 * Accepts: pointer to destination string
 *	    pointer to string to concatenate
 *	    list of special characters or NIL for dot-atom format
 */

void rfc822_cat (char *dest,char *src,const char *specials)
{
  RFC822BUFFER buf;
				/* append to buffer */
  buf.end = (buf.beg = buf.cur = dest + strlen (dest)) + SENDBUFLEN - 1;
  buf.f = rfc822_legacy_soutr;
  buf.s = NIL;
  rfc822_output_cat (&buf,src,specials);
  *buf.cur = '\0';		/* tie off buffer */
}


/* Legacy write body content header
 * Accepts: pointer to destination string pointer
 *	    pointer to body to interpret
 */

void rfc822_write_body_header (char **dst,BODY *body)
{
  RFC822BUFFER buf;
				/* append to buffer */
  buf.end = (buf.beg = buf.cur = *dst + strlen (*dst)) + SENDBUFLEN - 1;
  buf.f = rfc822_legacy_soutr;
  buf.s = NIL;
  rfc822_output_body_header (&buf,body);
  *(*dst = buf.cur) = '\0';	/* tie off buffer */
}

/* Legacy 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,BODY *body,soutr_t f,void *s,
		    long ok8bit)
{
  long ret;
  rfc822out_t r822o = (rfc822out_t) mail_parameters (NIL,GET_RFC822OUTPUT,NIL);
				/* call external RFC 2822 output generator */
  if (r822o) ret = (*r822o) (t,env,body,f,s,ok8bit);
  else {			/* output generator not armed */
    RFC822BUFFER buf;		/* use our own buffer rather than trust */
    char tmp[SENDBUFLEN+1];	/*  client to give us a big enough one */
    buf.f = f;
    buf.s = s;
    buf.end = (buf.beg = buf.cur = t) + SENDBUFLEN - 1;
    tmp[SENDBUFLEN] = '\0';	/* must have additional guard byte */
    ret = rfc822_output_full (&buf,env,body,ok8bit);
  }
  return ret;
}


/* Legacy 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 (BODY *body,soutr_t f,void *s)
{
  RFC822BUFFER buf;
  char tmp[SENDBUFLEN+1];
  buf.f = f;
  buf.s = s;
  buf.end = (buf.beg = buf.cur = tmp) + SENDBUFLEN;
  tmp[SENDBUFLEN] = '\0';	/* must have additional guard byte */
  return rfc822_output_text (&buf,body) && rfc822_output_flush (&buf);
}
