/*
 * Common tricks to check and downgrade headers from MIME 8BIT to
 * MIME QUOTED-PRINTABLE
 */

/*
 *	Copyright 1994-2003 by Matti Aarnio
 *
 * To really understand how headers (and their converted versions)
 * are processed you do need to draw a diagram.
 * Basically:
 *    rp->desc->headers[]    is index to ALL of the headers, and
 *    rp->desc->headerscvt[] is index to ALL of the CONVERTED headers.
 * Elements on these arrays are  "char *strings[]" which are the
 * actual headers.
 * There are multiple-kind headers depending upon how they have been
 * rewritten, and those do tack together for each recipients (rp->)
 * There
 *  rp->newmsgheader    is a pointer to an element on rp->desc->msgheaders[]
 *  rp->newmsgheadercvt is respectively an element on rp->desc->msgheaderscvt[]
 *
 * The routine-collection   mimeheaders.c  creates converted headers,
 * if the receiving system needs them. Converted header data is created
 * only once per  rewrite-rule group, so there should not be messages which
 * report  "Received: ... convert XXXX convert XXXX convert XXXX; ..."
 * for as many times as there there are recipients for the message.
 * [mea@utu.fi] - 25-Jul-94
 */

#include "hostenv.h"
#ifdef HAVE_STDARG_H
# include <stdarg.h>
#else
# include <varargs.h>
#endif
#include <sys/types.h>
#include <sys/stat.h>
#ifdef HAVE_STRING_H
#include <string.h>
#else
#ifdef HAVE_STRINGS_H
#include <strings.h>
#endif
#endif

#include "ta.h"

#include "zmalloc.h"
#include "libz.h"

#ifndef strdup
extern char *strdup();
#endif
#ifndef strchr
extern char *strchr();
#endif
extern void *emalloc();
extern void *erealloc();
extern char *getzenv();

#ifndef	__
# ifdef __STDC__
#  define __(x) x
# else
#  define __(x) ()
# endif
#endif

extern char * dupnstr __((const char *str, const int len));

static int mime_received_convert __((struct rcpt *rp, char *convertstr));

/* extern FILE *verboselog; */

#define istokenchar(x) (isalnum(x) || x == '-' || x == '_' || x == '$' || \
			x == '#'   || x == '%' || x == '+')

/* strqcpy() -- store the string into buffer with quotes if it
   		is not entirely of alphanums+'-'+'_'.. */

static int strqcpy __((char **buf, int startpos, int *buflenp, const char *str));

static int  /* FIXME: ??? LEAKS ?? */
strqcpy(bufp, startpos, buflenp, str)
	char **bufp;
	const char *str;
	int *buflenp;
{
	const char *s = str;
	char *buf = *bufp;
	int needquotes = 0;
	int buflen = *buflenp -2;
	int pos = startpos;

	/* Copy while scanning -- redo if need quotes.. */
	for (; *s; ++s) {
	  char c = *s;

	  if (pos >= buflen) {
	    *buflenp += 32;
	    buflen = *buflenp -2;
	    buf = realloc(buf, buflen + 5);
	  }

	  if (!(('0' <= c && c <= '9') ||
		('A' <= c && c <= 'Z') ||
		('a' <= c && c <= 'z') ||
		c == '-' || c == '_' || c == '.')) {
	    needquotes = 1;
	    break;
	  }
	  buf[pos] = c;
	  ++pos;
	}
	if (!needquotes) { /* Space left, not need quotes */
	  buf[pos] = 0;
	  *bufp = buf;
	  return (pos);
	}

	/* Ok, need quotes.. */
	pos = startpos;
	s = str;

	if (pos+5 >= buflen) {
	  *buflenp = pos + 32;
	  buflen = *buflenp -2;
	  buf = realloc(buf, buflen + 5);
	}

	buf[pos] = '"';
	++pos;

	for (; *s; ++s) {
	  char c = *s;

	  if (pos >= buflen) {
	    *buflenp = pos + 32;
	    buflen = *buflenp - 4;
	    buf = realloc(buf, buflen + 5);
	  }

	  if (c == '"' || c == '\\') {
	    buf[pos] = '\\';
	    ++pos;
	  }

	  buf[pos] = c;
	  ++pos;

	}

	if (pos+3 >= buflen) {
	  *buflenp += pos+3;
	  buflen = *buflenp;
	  buf = realloc(buf, buflen +2);
	}

	buf[pos] = '"';
	++pos;
	buf[pos] = 0;

	*bufp = buf;

	return (pos);
}


void cvtspace_free(rp)
     struct rcpt *rp;
{
	char ***ss;

	if (!rp->newmsgheadercvt) return;

	ss = rp->newmsgheadercvt;
}


int cvtspace_copy(rp)
     struct rcpt *rp;
{
	int hdrcnt = 0;
	char **probe = *(rp->newmsgheader);
	char **newcvt;

	/* Count how many lines */
	while (*probe) {
	  ++probe;
	  ++hdrcnt;
	}

	/* Allocate, and copy ! */

	/* XX: FIXME: ?? LEAKS ? */
	newcvt = (char **)malloc(sizeof(char *)*(hdrcnt+1));
	if (newcvt != NULL) {

	  char **ss = newcvt;
	  probe = *(rp->newmsgheader);

	  while (*probe) { /* Copy over */
	    int len = strlen(*probe)+1;
	    /* XX: FIXME: ?? LEAKS ? */
	    *ss = (char *)malloc(len);
	    if (! *ss) return 0;
	    memcpy(*ss,*probe,len);
	    ++hdrcnt;
	    ++probe;
	    ++ss;
	  }
	  *ss = NULL;

	  *(rp->newmsgheadercvt) = newcvt;

	} else
	  return 0;	/* Out of memory ? */

	return 1;
}

#ifdef HAVE_STDARG_H
#ifdef __STDC__
int
append_header(struct rcpt *rp, const char *fmt, ...)
#else /* Not ANSI-C */
int
append_header(rp, fmt)
	struct rcpt *rp;
	const char *fmt;
#endif
#else
int
append_header(va_alist)
	va_dcl
#endif
{
	va_list pvar;
	char lbuf[2000]; /* XX: SHOULD be enough..  damn vsprintf()..*/
	u_int linelen;
	u_int linecnt;
	char ***hdrpp, **hdrp2;

#ifdef HAVE_STDARG_H
	va_start(pvar,fmt);
#else
	struct rcpt *rp;
	char *fmt;

	va_start(pvar);
	rp  = va_arg(pvar, struct rcpt*);
	fmt = va_arg(pvar, char *);
#endif
	lbuf[0] = 0;

#ifdef HAVE_VSNPRINTF
	vsnprintf(lbuf, sizeof(lbuf)-1, fmt, pvar);
#else
	vsprintf(lbuf, fmt, pvar);
#endif
	va_end(pvar);

	linelen = strlen(lbuf);
	if (linelen > sizeof(lbuf)) {
	  exit(240); /* BUG TIME! */
	}

/*
	if (*(rp->newmsgheadercvt) == NULL)
	  if (cvtspace_copy(rp) == 0)
	    return -1;
*/
	hdrpp   = rp->newmsgheadercvt;
	if (*hdrpp == NULL) /* Not copied ? */
	  hdrpp = rp->newmsgheader;
	linecnt = 0;

	while ((*hdrpp)[linecnt] != NULL) ++linecnt;

	hdrp2 = (char**)realloc(*hdrpp,sizeof(char **) * (linecnt+3));

	if (!hdrp2) return -1;
	hdrp2[linecnt] = (char*) malloc(linelen+3);
	if (! hdrp2[linecnt]) zmalloc_failure = 1;
	else {
	  memcpy(hdrp2[linecnt],lbuf,linelen+2);
	  hdrp2[++linecnt] = NULL;
	  *hdrpp = hdrp2;
	}
	return linecnt;
}

void
output_content_type(rp,ct,old)
     struct rcpt *rp;
     struct ct_data *ct;
     char **old;
{
	/* Output the  Content-Type: with  append_header()
	   Sample output:
		Content-Type: text/plain; charset=ISO-8859-1;
			boundary="12fsjdhf-j4.83+712";
			name="ksjd.ksa";
			attr_1="83r8310032 askj39";
			attr_2="ajdsh 8327ead"
	 */

	char **lines;
	int  *linelens;
	int  linecnt = 0, i;
	int  linespace = 0;
	char **newmsgheaders;
	char **h1, **o2, ***basep;
	int  hdrlines;
	char *bp;
	char **unk = ct->unknown;
	int bufsiz, len, totlen = 0;
	char *buf;

	bufsiz  = 18;
	if (ct->basetype)
	  bufsiz += strlen(ct->basetype);
	if (ct->subtype)
	  bufsiz += strlen(ct->subtype) + 4;
	if (ct->charset)
	  bufsiz += strlen(ct->charset) + 14;

	if (ct->boundary) {
	  int siz2 = strlen(ct->boundary) + 16 ;
	  if (siz2 > bufsiz) bufsiz = siz2;
	}
	if (ct->name) {
	  int siz2 = strlen(ct->name) + 10 ;
	  if (siz2 > bufsiz) bufsiz = siz2;
	}

	buf = malloc(bufsiz);

	linespace = 6;
	lines    = malloc(sizeof(lines[0]   ) * (linespace+2));
	linelens = malloc(sizeof(linelens[0]) * (linespace+2));

	sprintf(buf,"Content-Type:\t%s",ct->basetype);
	bp = buf + strlen(buf);

	if (ct->subtype) {
	  sprintf(bp,"/%s",ct->subtype);
	  bp = bp + strlen(bp);
	}
	if (ct->charset) {
	  strcat(bp,"; charset=");
	  bp = bp + strlen(bp);
	  strqcpy(&buf, bp - buf, &bufsiz, ct->charset);
	  bp = bp + strlen(bp);
	  if (ct->boundary || ct->name || unk)
	    strcat(bp, ";");
	} else if (ct->boundary || ct->name || unk)
	  strcat(bp, ";");

/*if (verboselog) fprintf(verboselog,"CT_out: '%s'\n",buf);*/
	len = strlen(buf);
	linelens[linecnt] = len;
	lines[linecnt++] = dupnstr(buf, len);
	totlen += len+1;

	*buf = 0; bp = buf;

	if (ct->boundary) {
	  strcat(bp,"\tboundary=");
	  bp = bp + strlen(bp);
	  strqcpy(&buf, bp - buf, &bufsiz, ct->boundary);
	  bp = bp + strlen(bp);
	  if (ct->name || unk)
	    strcat(bp, ";");

/*if (verboselog) fprintf(verboselog,"CT_out: '%s'\n",buf);*/
	  len = strlen(buf);
	  linelens[linecnt] = len;
	  lines[linecnt++] = dupnstr(buf, len);
	  totlen += len + 1;
	  *buf = 0; bp = buf;
	}

	if (ct->name) {
	  strcat(bp,"\tname=");
	  bp = bp + strlen(bp);
	  strqcpy(&buf, bp - buf, &bufsiz, ct->name);
	  bp = bp + strlen(bp);
	  if (unk)
	    strcat(bp, ";");

/*if (verboselog) fprintf(verboselog,"CT_out: '%s'\n",buf);*/
	  len = strlen(buf);
	  linelens[linecnt] = len;
	  lines[linecnt++] = dupnstr(buf, len);
	  totlen += len +1;
	  *buf = 0; bp = buf;
	}

	while (unk && *unk) {
	  int ulen = strlen(*unk);
	  int pos = bp - buf;
	  if (bufsiz < (pos + 2 + ulen)) {
	    bufsiz = pos + 2 + ulen + 8;
	    buf = realloc(buf, bufsiz);
	    bp = buf + pos;
	  }
	  if ((pos + 2 + ulen) >= 78 && *buf) {
	    /* There is something already, and more wants to push in,
	       but would overflow the 78 column marker! */
	    memcpy(bp, ";", 2);
/*if (verboselog) fprintf(verboselog,"CT_out: '%s'\n",buf);*/
	    len = strlen(buf);
	    if (linecnt >= linespace) {
	      linespace <<= 1;
	      lines    = realloc(lines,
				 sizeof(lines[0]   ) * (linespace+2));
	      linelens = realloc(linelens,
				 sizeof(linelens[0]) * (linespace+2));
	    }
	    linelens[linecnt] = len;
	    lines   [linecnt] = dupnstr(buf, len);
	    ++linecnt;

	    totlen += len +1;
	    *buf = 0;
	    bp = buf;
	    pos = 0;
	  }
	  *bp++ = '\t';
	  memcpy(bp, *unk, ulen);
	  bp += ulen;
	  *bp = 0;
	  ++unk;
	}

	if (*buf != 0) {
/*if (verboselog) fprintf(verboselog,"CT_out: '%s'\n",buf);*/
	  len = strlen(buf);
	  linelens[linecnt] = len;
	  lines[linecnt++] = dupnstr(buf, len);
	  totlen += len +1;
	}

	/* The lines are formed, now save them.. */

	basep = (rp->newmsgheader);
	if (*(rp->newmsgheadercvt) != NULL)
	  basep = (rp->newmsgheadercvt);
	hdrlines = 0;
	h1 = *basep;
	for (; *h1; ++h1, ++hdrlines) ;

	newmsgheaders = (char**)malloc(sizeof(char*)*(hdrlines +2));
	if (! newmsgheaders) {
	  zmalloc_failure = 1;
	  if (buf) free(buf);
	  return;
	}
	o2 = newmsgheaders;
	h1 = *basep;
	while (*h1 && h1 != old) {
	  *o2++ = *h1++;
	}
	if (h1 == old) {
	  /* Found the old entry ?  Skip over it.. */
	  ++h1;
	}
	buf = realloc(buf, totlen + 4);
	bp = buf;
	for (i = 0; i < linecnt; ++i) {
	  memcpy(bp, lines[i], linelens[i]);
	  free(lines[i]);
	  bp += linelens[i];
	  if (i+1 < linecnt)
	    *bp++ = '\n';
	}
	*bp = 0;
	*o2++ = buf; /* DO NOT FREE buf HERE! */

	free(lines);
	free(linelens);

	while (*h1)
	  *o2++ = *h1++;
	*o2 = NULL;
	/* Whew...  Copied them over.. */
	/* Scrap the old one: */
	free(*basep);
	/* And replace it with the new one */
	*basep = newmsgheaders;
}

static const char * skip_822linearcomments __((const char *p));

#if 0 /* This code not needed */
static const char * skip_822dtext __((const char *p));
static const char * skip_822dtext(p)
     const char *p;
{
	/* This shall do skipping of RFC 822:
	   3.1.4: Structured Field Bodies  defined ATOMs */

	const char *s = p;

#warning "skip_822dtext() code missing!"

	return s;
}

static const char * skip_822domainliteral __((const char *p));
static const char * skip_822domainliteral(p)
     const char *p;
{
	/* This shall do skipping of RFC 822: DOMAIN LITERALs */

	const char *s = p;

#warning " skip_822dliteral() missing code!"

	return s;
}
#endif

static const char * skip_822qtext __((const char *p));
static const char * skip_822qtext(p)
     const char *p;
{
	/* This shall do skipping of RFC 822:  qtext  items */

	const char *s = p;

	while (*s && *s != '"' && *s != '\\') ++s;

	return s;
}

static const char * skip_822qpair __((const char *p));
static const char * skip_822qpair(p)
     const char *p;
{
	/* This shall do skipping of RFC 822:  quoted-pair  items */

	const char *s = p;

	++s; /* We come in with '\\' at *s */
	/* End-of-header ?? */
	if (*s != 0)
	  ++s;

	return s;
}

static const char * skip_822quotedstring __((const char *p));
static const char * skip_822quotedstring(p)
     const char *p;
{
	/* This shall do skipping of RFC 822:  quoted-string  items */

	const char *s = p;

	/* At arrival:  *s == '"' */
	++s;

	while (*s && *s != '"') {
	  if (*s == '\\')
	    s = skip_822qpair(s);
	  else
	    s = skip_822qtext(s);
	}
	if (*s == '"')
	  ++s;

	return s;
}

static const char * skip_comment __((const char *p));
static const char * skip_comment(p)
     const char *p;
{
	/* This shall do skipping of RFC 822:
	   3.1.4: Structured Field Bodies
	   defined COMMENTS */

	const char *s = p;

	++s; /* We are called with *s == '(' */
	while (*s && *s != ')') {

	  if (*s == '\\') { /* Quoted-Pair */
	    s = skip_822qpair(s);
	    continue;
	  }
	  if (*s == '(') {
	    s = skip_comment(s);
	    continue;
	  }
	  /* Anything else, just advance... */
	  ++s;
	}
	if (*s == ')')
	  ++s;

	return s;
}

static const char * skip_822linearcomments(p)
     const char *p;
{
	/* This shall do skipping of RFC 822:
	   3.1.4: Structured Field Bodies
	   defined LINEAR-WHITESPACES and COMMENTS */

	const char *s = p;

	while (*s) {

	  /* To be exact we should scan over only SPC, TAB, CR and LF
	     chars, but lets skip all controls just in case.. */
	  while (0 <= *s && *s <= ' ') ++s; /* ASCII assumption! */

	  if (*s == '(') {
	    s = skip_comment(s);
	    continue;
	  }

	  if (*s != 0) /* Not line-end */
	    break; /* Anything else is worth ending the scan ? */
	}

	return s;
}

static const char * _skip_822atom __((const char *p, int tspecial));
static const char * _skip_822atom(p, tspecial)
     const char *p;
     int tspecial;
{
	/* This shall do skipping of RFC 822:
	   3.1.4: Structured Field Bodies  defined ATOMs

	   - Sequence of non-control/space characters which
	     end up at control/space/ATOM-special
	   - ATOM-special chars

	*/

	const char *s = p;
	int isdelim = 0;

	while (*s) {
	  char c;

	  c = *s;

	  /* We should *not* be called with DEL/Control/SPC! */
	  if (c == 127 || (0 <= c && c <= ' '))
	    break; /* Delimiters (controls, SPACE) */

	  if (c == '.' && !tspecial) /* RFC 822 at odds with RFC 2045 */
	    isdelim = 1;

	  switch (c) { /* Any of specials ? */
	  case '/':
	  case '?':
	  case '=': /* TokenSpecial as defined at MIME / RFC 2045 et.al. */
	    if (!tspecial) break;
	  case '(':
	  case ')':
	  case '\\':
	  case '<':
	  case '>':
	  case '@':
	  case ',':
	  case ';':
	  case ':':
	  case '"':
	  case '[':
	  case ']':
	    isdelim = 1;
	  default:
	    break;
	  }

	  /* If we have been scanning over non-atom special texts,
	     and now found an atom-special, we break out! */
	  if (s > p && isdelim) break;

	  /* Now advance the character-in-question */
	  ++s;

	  /* We didn't have any gathered string so far, but if it
	     happens to be special, we count them as single chars! */
	  if (c == ':' && *s == ':')
	    ++s; /* Consider '::' as single atom -- DECNET token */

	  if (isdelim) break;
	}

	/* Just in case we got here with bad input, always
	   return at least one char long sequence. */
	if (s == p) ++s;

	return s;
}

static const char * skip_mimetoken __((const char *p));
static const char * skip_mimetoken(p)
     const char *p;
{
  return _skip_822atom(p, 1);
}


static char * foldmalloccopy __((const char *start, const char *end));
static char * foldmalloccopy (start, end)
     const char *start;
     const char *end;
{
	int len = end - start;
	int space = len + 1;
	char *b;

	b = malloc(space);
	if (!b) return NULL; /* UARGH! */

	if (*start == '"' && len > 2) {
	  /* QUOTED-STRING, which we UNQUOTE here */
	  /* Our caller allows only quoted-strings, not
	     quoted-localparts! */
	  memcpy(b, start+1, len -2);
	  len -= 2;
	} else
	  memcpy(b, start, len);
	b[len] = 0;
	return b;
}


struct ct_data *
parse_content_type(ct_line)
     const char *ct_line;
{
	const char *s, *p;
	struct ct_data *ct = (struct ct_data*)malloc(sizeof(struct ct_data));
	int unknowncount = 0;

static const char *debugptrs[20];


	if (!ct) return NULL; /* Failed to parse it! */

	ct->basetype = NULL;
	ct->subtype  = NULL;
	ct->charset  = NULL;
	ct->boundary = NULL;
	ct->name     = NULL;
	ct->unknown  = NULL;

	s = ct_line;
	s += 13;	/* "Content-Type:" */

	debugptrs[0] = s;

	p = skip_822linearcomments(s);
	debugptrs[1] = p;
	s = skip_mimetoken(p);
	debugptrs[2] = s;

	ct->basetype = foldmalloccopy(p, s);
	debugptrs[3] = ct->basetype;

	p = skip_822linearcomments(s);
	debugptrs[4] = p;

	if (*p == '/') {	/* Subtype defined */
	  ++p;
	  p = skip_822linearcomments(p);
	  debugptrs[5] = p;
	  s = skip_mimetoken(p);
	  debugptrs[6] = p;

	  ct->subtype = foldmalloccopy(p, s);
	}

	while (*s) {
	  /* Check for possible parameters on the first and/or continuation
	     line(s) of the header line... */
	  char *paramname;
	  char *parval;

	  debugptrs[7] = s;

	  s = skip_822linearcomments(s);
	  debugptrs[8] = s;

	  if (!*s) break;
	  if (*s == ';') ++s;
	  else {
	    /* Not found semicolon at expected phase ?? Whoo..
	       Now shall we scan towards the end, and HOPE for the best,
	       or what shall we do ? */
	  }
	  debugptrs[9] = s;
	  p = skip_822linearcomments(s);
	  debugptrs[10] = p;
	  if (!p || !*p) break;
	  s = skip_mimetoken(p);
	  debugptrs[11] = s;
	  if (p == s && *s == 0) break; /* Nothing anymore */

	  paramname = foldmalloccopy(p, s);
	  debugptrs[12] = paramname;

	  /* Picked up a param name, now scan the value */

	  /* Have seen cases where there was:
		charset = "foo-bar"
	     That is, it had whitespaces around the "=" sign. */

	  s = skip_822linearcomments(s);
	  debugptrs[13] = s;

	  if (*s == '=') {	    /* What if no `=' ?? */
	    ++s;
	  }
	  debugptrs[14] = s;
	  p = skip_822linearcomments(s);
	  debugptrs[15] = p;

	  if (*p == '"') {
	    s = skip_822quotedstring(p);
	  } else {
	    s = skip_mimetoken(p);
	  }
	  debugptrs[16] = s;

	  parval = foldmalloccopy(p, s);
	  debugptrs[17] = parval;

	  if (CISTREQ("charset",paramname)) {
	    /* Parameter:  charset="..." */
	    ct->charset = parval;
	  } else if (CISTREQ("boundary",paramname)) {
	    /* Parameter:  boundary="...." */
	    ct->boundary = parval;
	  } else if (CISTREQ("name",paramname)) {
	    /* Parameter:  name="...." */
	    ct->name     = parval;
	  } else {
	    /* Unknown parameter.. */
	    int unkpos;
	    char *unk;
	    int unklen = strlen(parval)+5+strlen(paramname);

	    ct->unknown = (char**)realloc(ct->unknown,
					  sizeof(char*)*(unknowncount+2));
	    /* FIXME: malloc problem check ?? */

	    unk = malloc(unklen);
	    /* FIXME: malloc problem check ?? */

	    sprintf(unk, "%s=", paramname);
	    unkpos = strlen(unk);

	    strqcpy(&unk, unkpos, &unklen, parval);

	    ct->unknown[unknowncount++] = unk;
	    ct->unknown[unknowncount  ] = NULL;
	    free (parval);
	  }
	  if (paramname)
	    free(paramname);
	}
	return ct;
}

void free_content_type(ct)
     struct ct_data *ct;
{
	int i;

	if (ct->basetype) free(ct->basetype);
	if (ct->subtype)  free(ct->subtype);
	if (ct->charset)  free(ct->charset);
	if (ct->boundary) free(ct->boundary);
	if (ct->name)     free(ct->name);
	if (ct->unknown) {
	  for (i = 0; ct->unknown[i]; ++i)
	    free(ct->unknown[i]);
	  free(ct->unknown);
	}

	free(ct);
}

struct cte_data *
parse_content_encoding(cte_line)
     const char *cte_line;
{
	const char *s;
	struct cte_data *cte = malloc(sizeof(struct cte_data));

	if (!cte) return NULL;

	s = cte_line + 26;
	/* Skip over the 'Content-Transfer-Encoding:' */
	s = skip_822linearcomments(s);
	if (*s == '"') {
	  const char *p = skip_822quotedstring(s);
	  cte->encoder = foldmalloccopy(s, p);
	  /* FIXME: malloc problem check ?? */
	  s = p;
	} else {
	  const char *p = skip_mimetoken(s);
	  cte->encoder = foldmalloccopy(s, p);
	  /* FIXME: malloc problem check ?? */
	  s = p;
	}

	/* while (*s == ' ' || *s == '\t') ++s; */
	/* XX: if (*s) -- errornoeus data */

	return cte;
}



void free_content_encoding(cte)
     struct cte_data *cte;
{
	if (cte->encoder) free(cte->encoder);
	free(cte);
}


/*
 *  Check for  "Content-conversion: prohibited" -header, and return
 *  non-zero when found it. ---  eh, "-1", return '7' when QP-coding
 *  is mandated.. (test stuff..)
 */
int
check_conv_prohibit(rp)
     struct rcpt *rp;
{
	char **hdrs = *(rp->newmsgheader);
	if (!hdrs) return 0;

	while (*hdrs) {
	  if (CISTREQN(*hdrs,"Content-conversion:", 19)) {
	    const char *s = *hdrs + 19;
	    const char *p = skip_822linearcomments(s);
	    if (*p == '"') ++p;
	    if (CISTREQN(p,"prohibited",10)) return -2;
	    if (CISTREQN(p,"forced-qp",9)) return 7;
	    /* Prohibits (?) the content conversion.. */
	  }
	  ++hdrs;
	}
	return 0;	/* No "Content-Conversion:" header */
}

static const char *cCTE = "Content-Transfer-Encoding:";
static const char *cCT  = "Content-Type:";

int
cte_check(rp)
     struct rcpt *rp;
{	/* "Content-Transfer-Encoding: 8BIT" */

	char **hdrs = *(rp->newmsgheader);
	int cte = 0;
	int mime = 0;

	/* if (*(rp->newmsgheadercvt) != NULL)
	   hdrs = *(rp->newmsgheadercvt); */
	/* here we check the ORIGINAL headers.. */

	if (!hdrs) return 0;

	while (*hdrs && (!mime || !cte)) {
	  const char *buf = *hdrs;
	  if (!cte && CISTREQN(buf,cCTE,26)) {
	    buf += 26;
	    buf = skip_822linearcomments(buf);
	    if (*buf == '"') ++buf;
	    if (*buf == '8' /* 8BIT */) cte = 8;
	    else if (*buf == '7' /* 7BIT */) cte = 7;
	    else if (*buf == 'Q' || *buf == 'q') cte = 9; /*QUOTED-PRINTABLE*/
	    else cte = 1; /* Just something.. BASE64 most likely .. */
	  } else if (!mime && CISTREQN(buf,"MIME-Version:",13)) {
	    mime = 1;
	  }
	  ++hdrs;
	}
	if (mime && cte == 0) cte = 2;
	if (!mime) cte = 0;
	return cte;
}

char **  /* Return a pointer to header line pointer */
has_header(rp,keystr)
     struct rcpt *rp;
     const char *keystr;
{
	char **hdrs = *(rp->newmsgheader);
	int keylen = strlen(keystr);

	if (*(rp->newmsgheadercvt))
	  hdrs = *(rp->newmsgheadercvt);

	if (hdrs)
	  while (*hdrs) {
	    if (CISTREQN(*hdrs,keystr,keylen)) return hdrs;
	    ++hdrs;
	  }
	return NULL;
}

void
delete_header(rp,hdrp)	/* Delete the header, and its possible
			   continuation lines */
     struct rcpt *rp;
     char **hdrp;
{
	char **h1 = hdrp;
	char **h2 = hdrp+1;
	ctlfree(rp->desc,*hdrp);
	while (*h2)
	  *h1++ = *h2++;
	/* And one more time.. To copy the terminating NULL ptr. */
	*h1++ = *h2++;
}

int
downgrade_charset(rp, verboselog)
     struct rcpt *rp;
     FILE *verboselog;
{
	char **CT   = NULL;
	char **CTE  = NULL;
	struct ct_data *ct;

	/* Convert IN PLACE! -- if there is a need.. */
	CT = has_header(rp,cCT);
	CTE  = has_header(rp,cCTE);
	if (CT == NULL || CTE == NULL) return 0; /* ??? */

	ct = parse_content_type(*CT);

	if (ct->basetype == NULL ||
	    ct->subtype  == NULL ||
	    !CISTREQ(ct->basetype,"text") ||
	    !CISTREQ(ct->subtype,"plain")) return 0; /* Not TEXT/PLAIN! */

	if (ct->charset &&
	    !CISTREQN(ct->charset,"ISO-8859",8) &&
	    !CISTREQN(ct->charset,"KOI8",4)    ) return 0; /* Not ISO-* */

	if (ct->charset)
	  free(ct->charset);

	strcpy(*CTE, "Content-Transfer-Encoding: 7BIT");

	ct->charset = strdup("US-ASCII");

	/* Delete the old one, and place there the new version.. */
	output_content_type(rp,ct,CT);

	if (ct) free_content_type(ct);

	return 1;
}

int
downgrade_headers(rp, convertmode, verboselog)
     struct rcpt *rp;
     CONVERTMODE convertmode;
     FILE *verboselog;
{
	char ***oldmsgheader;
	char **CT   = NULL;
	char **CTE  = NULL;
	char **MIME = NULL;
	struct ct_data *ct;
	int lines;
	int is_textplain;
	int newlen;
	int outnum;
	int oldidx, newidx;

	if (*(rp->newmsgheadercvt) != NULL)
	  return 0; /* Already converted ! */

	if (*(rp->newmsgheadercvt) == NULL)
	  if (!cvtspace_copy(rp)) return 0; /* XX: auch! */

	oldmsgheader = rp->newmsgheadercvt;

	lines = 0;
	if (oldmsgheader) /* Count them! */
	  while ((*oldmsgheader)[lines]) ++lines;

	MIME = has_header(rp,"MIME-Version:");
	CT   = has_header(rp,cCT);
	CTE  = has_header(rp,cCTE);

	if (verboselog)
	  fprintf(verboselog,"Header conversion control code: %d\n",convertmode);

	if (convertmode == _CONVERT_UNKNOWN) {
	  /* We downgrade by changing it to Q-P as per RFC 1428/Appendix A */
	  static const char *warning_lines[] = {
"X-Warning: Original message contained 8-bit characters, however during\n\
\t   the SMTP transport session the receiving system did not announce\n\
\t   capability of receiving 8-bit SMTP (RFC 1651-1653), and as this\n\
\t   message does not have MIME headers (RFC 2045-2049) to enable\n\
\t   encoding change, we had very little choice.\n\
X-Warning: We ASSUME it is less harmful to add the MIME headers, and\n\
\t   convert the text to Quoted-Printable, than not to do so,\n\
\t   and to strip the message to 7-bits.. (RFC 1428 Appendix A)\n\
X-Warning: We don't know what character set the user used, thus we had to\n\
\t   write these MIME-headers with our local system default value.\n\
MIME-Version: 1.0\n",
"Content-Transfer-Encoding: QUOTED-PRINTABLE\n",
NULL };

	  int outspc = lines + 8;
	  char **newmsgheaders = (char**)malloc(sizeof(char**) * outspc);
	  char *defcharset = getzenv("DEFCHARSET");
	  char *newct;

	/* FIXME: malloc problem check ?? */

	  if (!defcharset)
	    defcharset = "ISO-8859-1";
	  newct = malloc(strlen(defcharset)+2+sizeof("Content-Type: TEXT/PLAIN; charset="));
	/* FIXME: malloc problem check ?? */
	  sprintf(newct,"Content-Type: TEXT/PLAIN; charset=%s",defcharset);

	  if (!newmsgheaders) return 0; /* XX: Auch! */

	  outnum = 0;
	  oldidx = 0;
	  if ((*oldmsgheader)[0]) {
	    newmsgheaders[outnum++] = (*oldmsgheader)[oldidx++];
	  }

	  for (newidx = 0; warning_lines[newidx]; ++newidx)
	    newmsgheaders[outnum++] = strdup(warning_lines[newidx]);
	  newmsgheaders[outnum++] = newct;

	  if (CT)	/* XX: This CAN be wrong action for
			       some esoteric SysV mailers.. */
	    delete_header(rp,CT);
	  /* These most probably won't happen, but the delete_header()
	     does scram the pointers anyway.. */
	  if (MIME) {
	    MIME = has_header(rp,"MIME-Version:");
	    delete_header(rp,MIME);
	  }
	  if (CTE) {
	    CTE  = has_header(rp,cCTE);
	    delete_header(rp,CTE);
	  }

	  for (; (*oldmsgheader)[oldidx]; ++oldidx)
	    newmsgheaders[outnum++] = (*oldmsgheader)[oldidx];
	  newmsgheaders[outnum] = NULL;

	  free(*oldmsgheader); /* Free the old one.. */
	  *oldmsgheader = newmsgheaders;
	  return 0;
	}

	/* Now look for the  Content-Transfer-Encoding:  header */

	if (CTE == NULL) return 0; /* No C-T-E: ??? */

	/* strlen("Content-Transfer-Encoding: QUOTED-PRINTABLE") == 43
	   strlen("Content-Transfer-Encoding: 7BIT") == 31		*/

	/* Allocate space for the new value of  C-T-E */
	newlen = 31;
	if (convertmode == _CONVERT_QP) newlen = 43;

	*CTE = (char *)ctlrealloc(rp->desc,*CTE,newlen+2);

	/* Ok, this was C-T-E: 7BIT, turn charset to US-ASCII if it
	   was  ISO-*  */

	if (CT == NULL) { /* ???? Had C-T-E, but no C-T ?? */
	  if (verboselog)
	    fprintf(verboselog,"Had Content-Transfer-Encoding -header, but no Content-Type header ???  Adding C-T..\n");
	  append_header(rp,"Content-Type: TEXT/PLAIN; charset=US-ASCII");
	  return 0;
	}

	ct = parse_content_type(*CT);

	is_textplain = (ct->basetype != NULL &&

			ct->subtype  != NULL &&
			CISTREQ(ct->basetype,"text") &&
			CISTREQ(ct->subtype,"plain"));

	if (ct->charset && is_textplain &&
	    (convertmode != _CONVERT_QP) &&
	    /* Change to US-ASCII for known 7-bit clean
	       inputs where the claimed charset(prefix) has
	       all its charsets equal to US-ASCII in the
	       low 128 characters. */
	    (CISTREQN(ct->charset,"ISO-8859",8) ||
	     CISTREQN(ct->charset,"KOI8",4)        )) {

	  if (ct->charset)
	    free(ct->charset);

	  ct->charset = strdup("US-ASCII");
	  strcpy(*CTE, "Content-Transfer-Encoding: 7BIT");

	}

	/* Delete the old one, and place there the new version.. */
	output_content_type(rp,ct,CT);

	if (convertmode == _CONVERT_QP) {

	  /* Preceding 'output_content_type()' call scrambled lists,
	     we have to reget the CTE pointer */
	  CTE  = has_header(rp,cCTE);

	  strcpy(*CTE, "Content-Transfer-Encoding: QUOTED-PRINTABLE");
	  mime_received_convert(rp," convert rfc822-to-quoted-printable");

	}

	if (ct) free_content_type(ct);

	return 1; /* Non-zero for success! */
}


/* Returns the END of decoded string! */
static char * decodeXtext __((const char *, char *, int));
static char *
decodeXtext(xtext, obuf, obuflen)
	const char *xtext;
	char *obuf;
	int obuflen;
{
	char *s = obuf;
	--obuflen;
	for ( ; *xtext && obuflen > 0; --obuflen) {
	  if (*xtext == '+') {
	    int c = '?';
	    sscanf(xtext+1,"%02X",&c);
	    *s = c;
	    if (*xtext) ++xtext;
	    if (*xtext) ++xtext;
	  } else {
	    *s = *xtext;
	  }
	  ++s;
	  ++xtext;
	}
	*s = '\0';
	return s;
}



int
header_received_for_clause(rp, rcptcnt, verboselog)
     struct rcpt *rp;
     int rcptcnt;
     FILE *verboselog;
{
	int semicindex, receivedlen;
	char *newreceived;
	const char *newreceivedend;
	const char *top_received, *sc;

	char fchead[10], forclause[1024], fctail[20], *s;
	int clauselen;
	int col;

	char **inhdr;

	static int no_for_clause = -1;

	if (no_for_clause < 0) {
	  if (getzenv("NORECEIVEDFORCLAUSE"))
	    no_for_clause = 1;
	  else
	    no_for_clause = 0;
	}
	if (no_for_clause) return 0; /* Forbidden.. */



	if (*(rp->newmsgheadercvt) == NULL)
	  if (!cvtspace_copy(rp)) return 0; /* XX: auch! */

	inhdr = *(rp->newmsgheadercvt);


	/* We have one advantage: The "Received:" header we
	   want to fiddle with is the first one of them. */

	if (!inhdr || !CISTREQN(*inhdr,"Received:",9)) {
	  /* if (verboselog)fprintf(verboselog, "first *inhdr = '%s'\n",*inhdr ? *inhdr:"<NUL>"); */
	  return 0; /* D'uh ??  Not 'Received:' ??? */
	}



	/* Begin at the indented line start.. */
	/* strcpy(s, "\n\t"); s += 2; */

	if (rp->orcpt) {

	  strcpy(fchead, "(ORCPT");
	  s = forclause;
	  *s++ = '<';
	  s = decodeXtext(rp->orcpt, s, 800);
	  *s++ = '>';
	  *s = 0;
	  clauselen = s - forclause;

	  /* ORCPT data tail, just ending closing parenthesis.. */
	  if (rcptcnt > 2)
	    sprintf(fctail, "+ %d others)", rcptcnt-1);
	  else if (rcptcnt > 1)
	    strcpy(fctail, "+ 1 other)");
	  else {
	    *fctail = 0;
	    /* Close the comment on the ORCPT value! */
	    strcat(s, ")");
	    ++clauselen;
	  }

	} else { /* No  ORCPT  tail,  just 'for'-clause */

	  strcpy(fchead, "for");
	  sprintf(forclause, "<%.800s>", rp->addr->user);
	  clauselen = strlen(forclause);

	  if (rcptcnt > 2)
	    sprintf(fctail, "(+ %d others)", rcptcnt-1);
	  else if (rcptcnt > 1)
	    strcpy(fctail, "(+ 1 other)");
	  else
	    *fctail = 0;
	}



	top_received = *inhdr;


	/* Look for the LAST semicolon in this Received: header.. */

	sc = strrchr(top_received, ';');
	if (sc) {
	  semicindex = sc - top_received;
	} else {
	  /* No semicolon at all, last NEWLINE then ? */
	  semicindex = strlen(top_received);
	  sc = top_received + semicindex;
	  if (sc[-1] == '\n') { --sc; --semicindex; }
	}

	{
	  /* Find out the column of the cut-point.. */
	  const char *p = top_received;
	  col = 0;
	  for ( ; *p && (p <= sc) ; ++p) {
	    const char c = *p;
	    if ('\t' == c) col += 8; else ++col;
	    if (('\n' == c) || ('\r' == c)) {
	      col = 0;
	    }
	  }
	}


	receivedlen = strlen(top_received);
	newreceived = realloc(rp->top_received,
			      receivedlen + clauselen + 30); /* fchead[] +
								fctail[] +
								various
								newlines.. */
	rp->top_received = newreceived;
	if (!newreceived) return 0; /* Failed malloc.. */

	newreceivedend = newreceived + (receivedlen + clauselen + 30);


	/* Begin.. */
	s = newreceived;
	memcpy(s, top_received, semicindex);
	s += semicindex;

	if ((col + 1 + strlen(fchead)) >= 78) {
	  *s++ = '\n';
	  *s++ = '\t';
	  col = 8;
	} else {
	  *s++ = ' ';
	  ++col;
	}

	/* For clause head */
	{
	  int fcheadlen = strlen(fchead);
	  memcpy(s, fchead, fcheadlen +1);
	  s   += fcheadlen;
	  col += fcheadlen;
	}

	if ((col + 1 + clauselen) < 78) {
	  *s++ = ' ';
	  ++col;
	} else {
	  *s++ = '\n';
	  *s++ = '\t';
	  col = 8;
	}
	*s = 0; /* this is not absolutely required... */

	/* For clause */
	memcpy(s, forclause, clauselen+1);
	s += clauselen;

	col += clauselen;

	/* Tail */

	if (*fctail) {
	  if ((col + 1 + strlen(fctail)) >= 78) {
	    *s++ = '\n';
	    *s++ = '\t';
	    col = 8;
	  } else {
	    *s++ = ' ';
	    ++col;
	  }

	  /* For clause tail */
	  {
	    int fctaillen = strlen(fctail);
	    memcpy(s, fctail, fctaillen +1);
	    s   += fctaillen;
	    col += fctaillen;
	  }
	}


	/* SEMIC + DATETIME tail */
#if 1
	if (verboselog) {
	  fprintf(verboselog,"col=%d fc[] = '%s'(%d) ",
		  col, forclause, clauselen);
	  fprintf(verboselog,"sc[] = '%s' (%d)\n",
		  sc, strlen(sc));
	}
#endif

	if ( (strlen(sc) + col) < 78) {
	  /* Shrink the tail a bit, unnecessary folding away. */
	  *s++ = ';';
	  *s++ = ' ';
	} else {
	  *s++ = ';';
	  *s++ = '\n';
	  *s++ = '\t';
	}
	*s = 0; /* this is not absolutely required... */

	++sc; /* Skip the semicolon (or NEWLINE) .. and white-spaces */
	while (*sc && (*sc == '\n' || *sc == '\r' || *sc == ' ' || *sc == '\t')) ++sc;
	strcpy(s, sc);
#if 0
	if (verboselog) fprintf(verboselog,"sc[] = '%s'\n", sc);
#endif
	if (*sc == 0) strcat(s, "\n");

	s += strlen(s);
	if (s >= newreceivedend) abort();


#if 1
	if (verboselog) {
	  fprintf(verboselog,"Rewriting 'Received:' headers.  ");
	  fprintf(verboselog,"The new line is:\n%s\n",rp->top_received);
	}
#endif

	return 1;
}



static int /* Return non-zero for success */
mime_received_convert(rp, convertstr)
	struct rcpt *rp;
	char *convertstr;
{
	int convertlen = strlen(convertstr);
	int semicindex, receivedlen;
	char *newreceived;

	char **inhdr = *(rp->newmsgheadercvt);
	char *sc;

	/* We have one advantage: The "Received:" header we
	   want to fiddle with is the first one of them. */

	if (!inhdr || !CISTREQN(*inhdr,"Received:",9)) {
	  return 0; /* D'uh ??  Not 'Received:' ??? */
	}

	/* Look for the LAST semicolon in this Received: header.. */

	sc = strrchr(*inhdr, ';');
	if (sc) {
	  semicindex = sc - *inhdr;
	} else {
	  semicindex = strlen(*inhdr);
	  sc = *inhdr + semicindex;
	}

	receivedlen = strlen(*inhdr);
	newreceived = malloc(receivedlen + convertlen + 1);

	if (!newreceived) return 0; /* Failed malloc.. */

	memcpy(newreceived, *inhdr, semicindex);
	memcpy(newreceived+semicindex, convertstr, convertlen);
	if (semicindex < receivedlen)
	  memcpy(newreceived+semicindex+convertlen, (*inhdr) + semicindex,
		 receivedlen - semicindex);
	newreceived[receivedlen + convertlen] = '\0';

	ctlfree(rp->desc,*inhdr);
	*inhdr = newreceived;

	/* if (verboselog) {
	   fprintf(verboselog,"Rewriting 'Received:' headers.\n");
	   fprintf(verboselog,"The new line is: '%s'\n",*inhdr);
	   }
	*/

	return 1;
}

/* [mea] Now change  C-T-E: QUOTED-PRINTABLE  to  C-T-E: 8BIT -- in place.. */
int /* Return non-zero for success */
qp_to_8bit(rp)
	struct rcpt *rp;
{
	char **inhdr;
	const char *p;
	char *hdr;
	char **CTE, **CT;
	struct ct_data *ct;

	if (*(rp->newmsgheadercvt) == NULL)
	  if (!cvtspace_copy(rp))
	    return 0;	/* Failed to copy ! */

	inhdr = *(rp->newmsgheadercvt);

	CTE = has_header(rp,cCTE);
	CT  = has_header(rp,cCT);

	if (!CTE || !CT) return 0; /* No C-T-E header! */

	ct = parse_content_type(*CT);

	if (ct->basetype == NULL ||
	    ct->subtype  == NULL ||
	    !CISTREQ(ct->basetype,"text") ||
	    !CISTREQ(ct->subtype,"plain") ) {
	  
	  free_content_type(ct);

	  return 0; /* Not TEXT/PLAIN! */
	}

	free_content_type(ct);

	hdr = *CTE;

	p = hdr + 26;
	p = skip_822linearcomments(p);

	if (*p == 'Q' || *p == 'q') {
	  if (strlen(hdr+26) >= 5)
	    strcpy(hdr+26," 8BIT");
	  else { /* No room ??  What junk ?? */
	    delete_header(rp,CTE);
	    append_header(rp,"Content-Transfer-Encoding: 8BIT");
	  }

	  if (!mime_received_convert(rp," convert rfc822-to-8bit"))
	    return 0;	/* "Received:" conversion failed! */

	} /* else probably already decoded */

	return 1;
}


/* Return non-zero for any 8-bit char in the headers */
int
headers_need_mime2(rp)
	struct rcpt *rp;
{
	char **inhdr = *(rp->newmsgheader);
	while (inhdr && *inhdr) {
	  u_char *hdr = (u_char *)*inhdr;
	  for ( ; *hdr != 0 ; ++hdr)
	    if (*hdr != '\t' && *hdr != '\n' && *hdr != '\r'
		&& (*hdr < ' ' || *hdr > 126))
	      return 1;
	  ++inhdr;
	}
	return 0; /* No 8-bit chars in the headers */
}


syntax highlighted by Code2HTML, v. 0.9.1