/*
 *    Copyright 1988 by Rayan S. Zachariassen, all rights reserved.
 *      This will be free software, but only when it is finished.
 */
/*
 *    Several extensive changes by Matti Aarnio <mea@nic.funet.fi>
 *      Copyright 1991-1999.
 */
/*
 * Zmailer SMTP-server divided into bits
 *
 * The basic commands:
 *
 *  - DATA (RFC 821)
 *  - BDAT (RFC 1830)
 *
 */

/* XX: for anti-spam hack */
/* #define USE_ANTISPAM_HACKS */
/* #define USE_STRICT_MSGID_FREEZING */

#define FREEZE__X_ADVERTISEMENT_FOUND                   951
#ifdef  USE_ANTISPAM_HACKS
#define FREEZE__X_UIDL_FOUND                            952
#define FREEZE__IMPROBABLE_RECEIVED_HEADER_FOUND        952
#define FREEZE__MALFORMED_MESSAGE_ID_HEADER             953
#endif

#include "smtpserver.h"

#ifdef USE_TRANSLATION
#include <libtrans.h>
#endif				/* USE_TRANSLATION */

static int mvdata __((SmtpState *, char *));
static int mvbdata __((SmtpState *, char *, long));

int smtp_data(SS, buf, cp)
SmtpState *SS;
const char *buf, *cp;
{
    int filsiz;
    long tell = 0;
    time_t mtime;
    int ino;
    char msg[2048];

    while (!strict_protocol && (*cp == ' ' || *cp == '\t')) ++cp;
    if (strict_protocol && *cp != 0) {
      type(SS, 501, m554, "Extra junk after 'DATA' verb");
      return 0;
    }

    if (SS->state != RecipientOrData) {
	switch (SS->state) {
	case Hello:
	    cp = "Waiting for HELO command";
	    break;
	case Mail:
	case MailOrHello:
	    cp = "Waiting for MAIL command";
	    break;
	case Recipient:
	    cp = "Waiting for RCPT command";
	    break;
	case BData:
	    cp = "Must not intermix BDAT and DATA in same transaction!";
	    break;
	default:
	    cp = NULL;
	    break;
	}
	type(SS, 503, m552, cp);
	typeflush(SS);
	return 0;
    }
    if (ferror(SS->mfp)) {
	type(SS, 452, m430, (char *) NULL);
	typeflush(SS);
	clearerr(SS->mfp);
	mail_abort(SS->mfp);
	SS->mfp = NULL;
	reporterr(SS, tell, "message file error");
	return 0;
    }
    if (SS->sender_ok == 0) {
	type(SS, 550, "5.1.7", "No valid sender, rejecting all recipients");
	typeflush(SS);
	SS->state = MailOrHello;
	if (SS->mfp)
	  mail_abort(SS->mfp);
	SS->mfp = NULL;
	return 0;
    }
    if (SS->rcpt_count == 0) {
	/* No valid recipients! */
	type(SS, 550, "5.1.3", "No valid recipients at RCPT addresses, or no RCPT addresses at all");
	typeflush(SS);
	SS->state = MailOrHello;
	mail_abort(SS->mfp);
	SS->mfp = NULL;
	return 0;
    }
    if ((SS->from_box != 0) && (SS->rcpt_count > MaxErrorRecipients)) {
	/* Too many recipients for a  "MAIL FROM:<>" */
	type(SS, 550, "5.7.1", "SPAM trap -- too many recipients for an empty source address!");
	typeflush(SS);
	SS->state = MailOrHello;
	mail_abort(SS->mfp);
	SS->mfp = NULL;
	return 0;
    }
    SS->rcpt_count = 0;		/* now we can zero it.. */
    type(SS, 354, NULL, (char *) NULL);
    typeflush(SS);
    fputs("env-end\n", SS->mfp);

    if (msa_mode && SS->authuser != NULL ) {
      fprintf(SS->mfp, "X-Comment: RFC 2476 MSA function at %s logged sender identity as: %s\n", SS->myhostname, SS->authuser);
    }

    /* We set alarm()s inside the mvdata() */
    *msg = 0;
    filsiz = mvdata(SS, msg);
    alarm(0);			/* cancel the alarm() */
    tell = ftell(SS->mfp);

    report(SS, "Got '.'; tell=%ld", tell);

    availspace = fd_statfs(FILENO(SS->mfp));
    if (availspace < 0)
	availspace = 2000000000;	/* Over 2G ? */
    availspace -= minimum_availspace;

    if (*msg != 0) {
	mail_abort(SS->mfp);
	SS->mfp = NULL;
	type(SS, 452, m430, "%s", msg);
	typeflush(SS);
    } else if (s_feof(SS)) {
	/* [mea@utu.fi] says this can happen */
	if (STYLE(SS->cfinfo,'D')) {
	  /* Says: DON'T DISCARD -- aka DEBUG ERRORS! */
	  mail_close_alternate(SS->mfp,"public",".DATA-EOF");
	} else
	  mail_abort(SS->mfp);
	SS->mfp = NULL;
	reporterr(SS, tell, "premature EOF on DATA input");
	typeflush(SS);
	return -1;
    } else if (availspace < 0 || ferror(SS->mfp)) {
	type(SS, 452, m430, NULL); /* insufficient system storage */
	typeflush(SS);
	reporterr(SS, tell, ferror(SS->mfp) ? "write to spool file failed" : "system free storage under limit");
	clearerr(SS->mfp);
	mail_abort(SS->mfp);
	SS->mfp = NULL;
    } else if (maxsize > 0 && filsiz > maxsize) {
	mail_abort(SS->mfp);
	SS->mfp = NULL;
	type(SS, 552, "5.3.4", "Size of this message exceeds the fixed maximum size of  %ld  chars for received email ", maxsize);
	typeflush(SS);
    } else {

	/* Things have been good thus far, now we store
	   the resulting file into router spool area;
	   pending a few things we do at first.. */

	/* Lets see what the content-policy will tell now ? */
	char *fname = mail_fname(SS->mfp);
	SS->policyresult = contentpolicy(policydb, &SS->policystate, fname);

	if (SS->policyresult < 0) {
	  char *ss = policymsg(policydb, &SS->policystate);
	  type(NULL,0,NULL,
	       "Content-policy analysis ordered message rejection. (code=%d)", SS->policyresult);
	  type(SS, -552,m571, "Content-Policy analysis rejected this message");
	  type(SS, 552, m571, "Content-Policy msg: %s", ss ? ss : "rejected");
	  mail_abort(SS->mfp);
	  SS->mfp = NULL;
	} else if (SS->policyresult > 0) {
	  char polbuf[20];
	  struct stat stbuf;
	  char *ss = policymsg(policydb, &SS->policystate);

	  fflush(SS->mfp);
	  fstat(FILENO(SS->mfp), &stbuf);
	  runasrootuser();
	  sprintf(polbuf,"policy-%d",SS->policyresult);
	  if (mail_close_alternate(SS->mfp, FREEZERDIR, polbuf) != 0) {
	    type(NULL,0,NULL,
		 "mail_close_alternate(..'FREEZER','%s') failed, errno=%d (%s)",
		 polbuf, errno, strerror(errno));
	    type(SS, 452, m430, "Message file disposition failed");
	    typeflush(SS);
	    SS->mfp = NULL;
	    reporterr(SS, tell, "message file close failed");
	  } else {
	    static int freezecnt = 1;
	    freezecnt <<= 1;
	    sleep(freezecnt);
	    type(SS, 250, "2.6.0", "message accepted; into freezer[%d] area; %s", SS->policyresult, ss ? ss : "");
	    typeflush(SS);
	    SS->mfp = NULL;
	    zsyslog((LOG_INFO, "accepted id %d (%dc) from %s/%d into freeze[%d]",
		     (int) stbuf.st_ino, (int) stbuf.st_size,
		     SS->rhostname, SS->rport, SS->policyresult));
	  }
	  runastrusteduser();
	} else {

	  /*  Ok, we didn't have smtp-policy defined freezer action,
	      lets see if we do it some other way.. */

	  if (_mail_close_(SS->mfp, &ino, &mtime) == EOF) {
	    type(SS, 452, m430, (char *) NULL);
	    typeflush(SS);
	    SS->mfp = NULL;
	    reporterr(SS, tell, "message file close failed");
	  } else {
	    /* Ok, build response with proper "spoolid" */
	    char fnam[20], taspid[30];
	    sprintf(fnam, "%d", ino);
	    taspoolid(taspid, mtime, (long)ino);

	    SS->mfp = NULL;
	    type(SS, 250, "2.6.0", "%s message accepted", taspid);
	    typeflush(SS);

	    if (smtp_syslog)
	      zsyslog((LOG_INFO,
		       "%s: (%ldc) accepted from %s/%d", taspid, tell,
		       SS->rhostname, SS->rport));
		
	    type(NULL,0,NULL,"%s: %ld bytes", taspid, tell);
	    if (logfp)
	      fflush(logfp);
	  }
	}
    }

    SS->state = MailOrHello;
    typeflush(SS);
    return 0;
}

int smtp_bdata(SS, buf, cp)
SmtpState *SS;
const char *buf, *cp;
{
    int filsiz, rc;
    long tell;
    char msg[2048];
    long bdata_chunksize;
    int bdata_last;

    if (SS->state == RecipientOrData) {
	SS->state = BData;
	SS->bdata_blocknum = 0;
	SS->mvbstate = -1;
    }
    *msg = 0;
    rc = sscanf(cp, "%ld %7s %7s", &bdata_chunksize, msg, msg + 20);
    SS->bdata_blocknum += 1;
    bdata_last = CISTREQ(msg, "LAST");
    if (!(bdata_chunksize > 0L
	  && (rc == 1 || (rc == 2 && bdata_last)))) {
	type(SS, 501, m552, NULL);
	typeflush(SS);
	return 0;
    }
    if (SS->bdata_blocknum == 1 && SS->mfp) {
	fputs("env-end\n", SS->mfp);
	if (msa_mode && SS->authuser != NULL ) {
	  fprintf(SS->mfp, "X-Comment: RFC 2476 MSA function at %s logged sender identity as: %s\n", SS->myhostname, SS->authuser);
	}
    }
    /* We set alarm()s inside the mvbdata() */
    *msg = 0;
    filsiz = mvbdata(SS, msg, bdata_chunksize);
    alarm(0);			/* cancel the alarm() */
    if (SS->mfp)
	tell = ftell(SS->mfp);
    else
	tell = 0;

    report(SS, "BDAT; last=%d tell=%ld", bdata_last, tell);

    if (SS->state != BData) {
	switch (SS->state) {
	case Hello:
	    cp = "Waiting for HELO command";
	    break;
	case Mail:
	case MailOrHello:
	    cp = "Waiting for MAIL command";
	    break;
	case Recipient:
	    cp = "Waiting for RCPT command";
	    break;
	default:
	    cp = NULL;
	    break;
	}
	type(SS, 503, m552, cp);
	typeflush(SS);
	if (SS->mfp)
	    mail_abort(SS->mfp);
	SS->mfp = NULL;
	return 0;
    }
    if (SS->bdata_blocknum == 1) {
	if (SS->sender_ok == 0 || SS->rcpt_count == 0) {
	    cp = "No valid sender, rejecting all recipients";
	    if (SS->sender_ok != 0)
		cp = "No valid recipient at RCPT addresses, or no RCPT addresses at all";
	    type(SS, 550, "5.1.3", cp);
	    typeflush(SS);
	    SS->state = MailOrHello;
	    if (SS->mfp)
	      mail_abort(SS->mfp);
	    SS->mfp = NULL;
	    return 0;
	}
	if ((SS->from_box != 0) && (SS->rcpt_count > MaxErrorRecipients)) {
	  /* Too many recipients for a  "MAIL FROM:<>" */
	  type(SS, 550, "5.7.1", "SPAM trap -- too many recipients for an empty source address!");
	  typeflush(SS);
	  SS->state = MailOrHello;
	  mail_abort(SS->mfp);
	  SS->mfp = NULL;
	  return 0;
	}
	SS->rcpt_count = 0;	/* now we can zero them.. */
	SS->sender_ok = 0;
    }

    availspace = fd_statfs(FILENO(SS->mfp));
    if (availspace < 0)
	availspace = 2000000000;	/* Over 2G ? */
    availspace -= minimum_availspace;

    /* The common typeflush() is at the end... */
    if (SS->mfp == NULL) {
	type(SS, 503, m552, "BDAT block discarded due to earlier error");
    } else if (*msg != 0) {
	mail_abort(SS->mfp);
	SS->mfp = NULL;
	type(SS, 452, "%s", msg);
    } else if (s_feof(SS)) {
	/* [mea@utu.fi] says this can happen */
	if (STYLE(SS->cfinfo,'D')) {
	  /* Says: DON'T DISCARD -- aka DEBUG ERRORS! */
	  mail_close_alternate(SS->mfp,"public",".BDAT-EOF");
	} else
	  mail_abort(SS->mfp);
	SS->mfp = NULL;
	reporterr(SS, tell, "premature EOF on BDAT input");
	typeflush(SS);
	return -1;
    } else if (availspace < 0 || ferror(SS->mfp)) {
	type(SS, 452, m400, (char *) NULL);
	reporterr(SS, tell, ferror(SS->mfp) ? "write to spool file failed" : "system free storage under limit");
	clearerr(SS->mfp);
	mail_abort(SS->mfp);
	SS->mfp = NULL;
    } else if (maxsize > 0 && tell > maxsize) {
	mail_abort(SS->mfp);
	SS->mfp = NULL;
	type(SS, 552, "5.3.4", "Size of this message exceeds the fixed maximum size of  %ld  chars for received email ", maxsize);
    } else if (bdata_last) {
	time_t mtime;
	int inum;

	/* Things have been good thus far, now we store
	   the resulting file into router spool area;
	   pending a few things we do at first.. */

	/* Lets see what the content-policy will tell now ? */
	char *fname = mail_fname(SS->mfp);
	SS->policyresult = contentpolicy(policydb, &SS->policystate, fname);

	if (SS->policyresult < 0) {
	  char *ss = policymsg(policydb, &SS->policystate);
type(NULL,0,NULL,
  "Content-policy analysis ordered message rejection. (code=%d)", SS->policyresult);
	  type(SS, -552,m571, "Content-Policy analysis rejected this message");
	  type(SS, 552, m571, "Content-Policy msg: %s", ss ? ss : "rejected");
	  mail_abort(SS->mfp);
	  SS->mfp = NULL;
	} else if (SS->policyresult > 0) {
	  struct stat stbuf;
	  char *ss = policymsg(policydb, &SS->policystate);
	  fflush(SS->mfp);
	  fstat(FILENO(SS->mfp), &stbuf);
	  runasrootuser();
	  if (mail_close_alternate(SS->mfp, FREEZERDIR, "policy") != 0) {
	    type(NULL,0,NULL,
		 "mail_close_alternate(..'FREEZER','%s') failed, errno=%d (%s)",
		 "policy", errno, strerror(errno));
	    if (logfp)
	      fflush(logfp);
	    type(SS, 452, m430, "Message file disposition failed");
	    SS->mfp = NULL;
	    reporterr(SS, tell, "message file close failed");
	  } else {
	    type(SS, 250, "2.6.0", "message accepted; into freezer[%d] area; %s", SS->policyresult, ss ? ss : "");
	    SS->mfp = NULL;
	    zsyslog((LOG_INFO, "accepted id %d (%dc) from %s/%d into freeze",
		     (int) stbuf.st_ino, (int) stbuf.st_size,
		     SS->rhostname, SS->rport));
	  }
	  runastrusteduser();
	} else if (_mail_close_(SS->mfp, &inum, &mtime) == EOF) {
	  type(SS, 452, m400, (char *) NULL);
	  SS->mfp = NULL;
	  reporterr(SS, tell, "message file close failed");
	} else {
	  /* Ok, build response with proper "spoolid" */
	  char taspid[30];
	  taspoolid(taspid, mtime, inum);

	  SS->mfp = NULL;
	  type(SS, 250, "2.6.0", "%s Roger, got %ld bytes in the last chunk, stored %ld bytes into spool",
	       taspid, bdata_chunksize, (long) tell);
	  type(NULL,0,NULL,"-- pipeline input: %d bytes",s_hasinput(SS));

	  if (smtp_syslog)
	    zsyslog((LOG_INFO,
		     "%s: (%ldc) accepted from %s/%d", taspid, tell,
		     SS->rhostname, SS->rport));

	  type(NULL,0,NULL,"%s: %ld bytes", taspid, tell);
	  if (logfp)
	    fflush(logfp);
	}
    } else {			/* Not last chunk! */
	type(SS, 250, "2.6.0", "Received %ld bytes", bdata_chunksize);
    }
    if (bdata_last) {
	SS->state = MailOrHello;
    }
    typeflush(SS);
    return 0;
}


/* Implement SMTP DATA filter */

/*
 * The state table is indexed by the current character (across), and
 * the current state (down). Column 0 is for any character that is not
 * a '\r', '\n', or '.'.  The table entries encode the next state, and
 * what to output. An entry of EOF means exit. The next state is encoded
 * in the l.s.byte, and what to output is encoded in the next-l.s.byte.
 * If the next-l.s.byte is null, the current input character is output,
 * if the high bit is set, nothing is output, else the entire byte is
 * output followed by the current character.
 */

#define	O_	(0200 << 8)	/* don't print anything flag */
#define N_	('\r' << 8)	/* print '\r' then current input */
#define X_	~0		/* exit. must have (X_&O_) != 0 */

static int states[] =
{
/*        current input character       */
/*      *       '\r'    '\n'    '.'     EOF        states */
    0, O_ | 15, 10, 0, X_,	/* 0: during line */
    0, O_ | 20, X_, 0, X_,	/* 5: "^." */
    0, O_ | 15, 10, O_ | 5, X_,	/* 10: "^" (start state) */
    N_ | 0, 15, 10, N_ | 0, X_,	/* 15: seen a \r */
    N_ | 0, 15, X_, N_ | 0, X_,	/* 20: "^.\r" */
};

/*
 * Quick way of getting the column number of the state table,
 * that corresponds to the input character.
 */

static char indexnum[256 + 1] =
{
  /*
     #if 0
     idxnum['\r'] = 1;
     idxnum['\n'] = 2;
     idxnum['.'] = 3;
     #if EOF == -1
     idxnum[EOF] = 4;
     #endif
     #endif
   */
    4,				/* EOF */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 1, 0, 0,	/* ...'\n'..'\r'.. */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0,	/* ... '.' .. */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};

/*
 * BASE64 DECODER index table, and ENCODER map array...
 */
static int base64decode_index[128] = {
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
	52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
	-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
	15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
	-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
	41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1
};
#define decodechar64(c)  (((c) < 0 || (c) > 127) ? -1 : base64decode_index[(c)])

static char base64encode_array[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

int decodebase64string(instr,inlen,outstr,outspc,inleftover)
     const char * instr;
     char * outstr;
     const char ** inleftover;
     int inlen, outspc;
{
    int b64decoding = 1, outlen = 0;
    int b64eod = 0, b64i = 0, i, c;
    char b64c[4];

    while (inlen > 0 && outlen < outspc && !b64eod) {
      c = *instr++; --inlen;

      if (c == '=' || !b64decoding) {
	b64eod = 1;
	continue;
      }
      i = decodechar64(c);
      if (i < 0) continue;

      b64c[b64i++] = i;
      if (b64i < 2)
	continue;
      if (b64i == 2) {
	c = (b64c[0] << 2) | ((b64c[1] & 0x30) >> 4);
      } else if (b64i == 3) {
	c = ((b64c[1] & 0x0f) << 4) | ((b64c[2] & 0x3c) >> 2);
      } else {
	c = (b64c[2] << 6) | b64c[3];
	b64i = 0;
	if (b64eod)
	  b64decoding = 0;
      }

      outstr[outlen] = c;
      ++outlen;
    }
    if (outlen < outspc)
      outstr[outlen] = 0;
    while (*instr == '=') ++instr;
    if (inleftover)
      *inleftover = instr;
    return outlen;
}

int encodebase64string(instr,inlen,outstr,outspc)
     const char * instr;
     char *outstr;
     int inlen, outspc; /* Always positive values .. */
{
    /* Build groups of 3 bytes, encode them in 4 chars; if some byte
       is not filled, mark the incomplete byte with '=' */
    u_char b64out[4];
    int b64i, outlen = 0, b64val;
    while (inlen > 0 && outlen < outspc) {
      b64i = inlen > 3 ? 3 : inlen;

      b64val    = ((unsigned char) instr[0]) << 16;
      if (b64i > 1)
	b64val |= ((unsigned char) instr[1]) << 8;
      if (b64i > 2)
	b64val |= ((unsigned char) instr[2]);

      b64out[3] = base64encode_array[ b64val & 63 ]; b64val >>= 6;
      b64out[2] = base64encode_array[ b64val & 63 ]; b64val >>= 6;
      b64out[1] = base64encode_array[ b64val & 63 ]; b64val >>= 6;
      b64out[0] = base64encode_array[ b64val & 63 ];

      switch(b64i) {
      case 1:
	b64out[2] = '=';
      case 2:
	b64out[3] = '=';
      }

      instr += b64i;
      inlen -= b64i;

      b64i = (b64i == 1) ? 3 : 4;
      if ((outlen + b64i) < outspc) {	/* Can fit in .. */
	memcpy(outstr+outlen, b64out, b64i);
	outlen += b64i;
      } else {				/* Can't fit in :-( */
	memcpy(outstr+outlen, b64out, outspc-outlen);
	outlen = outspc;
      }
    }
    return outlen;
}

/*
 * Copy bytes from stdin to out, obeying sensible SMTP DATA input heuristics.
 *
 * Rayan back in 1988:
 *  "If you can improve on this heavily optimized routine, I'd like to see it.
 *   This version goes at better than 100kB/cpu-sec on a Sun 3/180."
 */

static int /* count of bytes */ mvdata(SS, msg)
SmtpState *SS;
char *msg;
{
    register int c, state, *sts, endstate, cnt;
    register char *idxnum;
    int timercnt = 0;

#ifdef NO_INCOMING_HEADER_PROCESSING
    idxnum = indexnum + 1;

    state = 10;
    endstate = X_;
    sts = states;
    cnt = 0;

    alarm(SMTP_DATA_TIME_PER_LINE);
#else
    char linebuf[4000], *s, *eol;
    int col;
    int insubject = 0;
    int has8bit = 0;		/* In headers */
    int has8bitsum = 0;
    /* int from__err = 0; */
    int linecnt = 0;
#ifdef USE_TRANSLATION
    int wi;
    char hdr_cte[4000], hdr_ct[4000];
    int delay_cte = 0, delay_ct = 0, append_hdr_ct = 0, append_hdr_cte = 0;
    int ct_is_text = 1;
#define CTE_8BIT 0
#define CTE_BASE64 1
#define CTE_QP 2
    int cte = CTE_8BIT;
    int do_decode = 0, do_translate = 0;
    int qp_chars = 0, qp_hex = 0;
    int b64decoding = 1, b64eod = 0, b64i = 0;
    char b64c[4];
#endif				/* USE_TRANSLATION */

    typeflush(SS);

    idxnum = indexnum + 1;

    state = 10;
    endstate = X_;
    sts = states;
    cnt = 0;
    col = 0;

    alarm(SMTP_DATA_TIME_PER_LINE);

    /* ================ Input the email headers ================ */
    /*           and analyze them a bit (Precedence:)            */
    /*        ... that only "Subject:" has 8-bit chars ...       */
    mail_priority = _MAILPRIO_NORMAL;
    for (;;) {
	if (--timercnt <= 0) {
	    /* Re-arm for every kilobyte */
	    alarm(SMTP_DATA_TIME_PER_LINE);
	    timercnt = 1024;
	}
	c = s_getc(SS);
	/* An EOF in here is an error! */
#if EOF != -1
	if (c == EOF)
	    return EOF;
#else
	if (c < 0)
	    return EOF;
#endif
	++cnt;
	state = sts[state + idxnum[c]];
	if (state & ~0xff) {
	    if (state & O_) {
		if (state == endstate) {
		    if (col > 0) {
#ifdef USE_TRANSLATION
			if (has8bit && !X_8bit)
			    header_to_mime(linebuf, &col, sizeof(linebuf));
			else
			    header_from_mime(linebuf, &col, sizeof(linebuf));
			for (wi = 0; wi < col; ++wi)
			    fputc(TR_IN(linebuf[wi]), SS->mfp);
#else				/* USE_TRANSLATION */
			if (has8bit)
			    header_to_mime(linebuf, &col, sizeof(linebuf));
			fwrite(linebuf, 1, col, SS->mfp);
#endif				/* USE_TRANSLATION */
		    }
		    return cnt;
		}
		state = state & 0xFF;
		continue;
	    }
	    if (col < (sizeof(linebuf) - 1))
		linebuf[col++] = (state >> 8);
	    state = state & 0xFF;
	}
	if (0x80 & c)
	    has8bit = 1;

	if (col < (sizeof(linebuf) - 1))
	    linebuf[col++] = c;

	/* LF, or something else ?  Here the else.. */
	if (c != '\n')
	    continue;

	if (col < sizeof(linebuf))
	    linebuf[col] = 0;

	/* We have a string - header line - in ``col'' first
	   char positions of the ``linebuf'' array  */

	/* See what this header is about -- or if the body starts.. */
	if (col > sizeof(linebuf) - 1)
	    col = sizeof(linebuf) - 1;

	eol = linebuf + col;
	*eol = 0;

	/* See if the line is all white-space: */
	for (s = linebuf; s < eol; ++s)
	    if (*s != ' ' && *s != '\t' &&
		*s != '\r' && *s != '\n')
		break;
	if (s == eol) {		/* All-blank line */
#ifdef USE_TRANSLATION
	    /* We have all info from the headers, time to make decision */
	    if (X_8bit && ct_is_text && cte)
		do_decode = cte;
	    if (X_translation && X_8bit && ct_is_text && (X_settrrc == 0))
		do_translate = 1;

	    type(NULL,0,NULL,"(8bit decode: %s, translate: %s) [%s%s,%s]",
		 do_decode ? "YES" : "NO", do_translate ? "YES" : "NO",
		 X_translation ? "-X " : "",
		 X_8bit ? "-8" : "",
		 ct_is_text ? "text" : "non-text");

	    /* write out content-type and content-transfer-encoding */
	    if (delay_ct) {
		if (do_translate) {
		    /* Remove "charset=xxx".  It is mismatching anyway */
		    char *p, *q, *r;
		    p = hdr_ct;
		    while (*p && (*p != ';'))
			p++;	/* skip to attrs */
		    q = p;
		    while (*p == ';') {		/* check attributes */
			r = p + 1;
			while (*r && ((*r == ' ') || (*r == '\t')))
			    r++;
			if (CISTREQN(r, "CHARSET=", 8)) {	/* skip it */
			    p++;
			    while (*p && (*p != ';') && (*p != '\n'))
				p++;
#ifdef USE_TRANSLATION
			    /* if forced charset specified, insert it. */
			    r = "; CHARSET=";
			    while (*r)
				*(q++) = *(r++);
			    r = USE_TRANSLATION;
			    while (*r)
				*(q++) = *(r++);
#endif				/* USE_TRANSLATION */
			} else {	/* copy it */
			    *(q++) = *(p++);
			    while (*p && (*p != ';'))
				*(q++) = *(p++);
			}
		    }
		    while (*p)
			*(q++) = *(p++);
		    *q = '\0';
		}
		fwrite(hdr_ct, 1, strlen(hdr_ct), SS->mfp);
	    }
	    if (delay_cte) {
		if (do_decode) {
		    /* Transfer-encoding changed to 8bit */
		    fputs("Content-Transfer-Encoding: 8bit\n", SS->mfp);
		} else {
		    fwrite(hdr_cte, 1, strlen(hdr_cte), SS->mfp);
		}
	    }
#endif				/* USE_TRANSLATION */
	    if (col > 0)
		fwrite(linebuf, 1, col, SS->mfp);
	    break;		/* Into the body processing */
	}
	++linecnt;
	if (*linebuf == ' ' || *linebuf == '\t') {
#ifdef USE_TRANSLATION
	    if (append_hdr_ct) {
		strcat(hdr_ct, linebuf);
		col = 0;
		continue;
	    }
	    if (append_hdr_cte) {
		strcat(hdr_cte, linebuf);
		col = 0;
		continue;
	    }
	    if (has8bit && !X_8bit)
		header_to_mime(linebuf, &col, sizeof(linebuf));
	    else
		header_from_mime(linebuf, &col, sizeof(linebuf));
	    if (col > 0)
		for (wi = 0; wi < col; wi++)
		    fputc(TR_IN(linebuf[wi]), SS->mfp);
#else				/* USE_TRANSLATION */
	    if (has8bit)
		header_to_mime(linebuf, &col, sizeof(linebuf));
	    if (col > 0)
		fwrite(linebuf, 1, col, SS->mfp);
#endif				/* USE_TRANSLATION */
	    has8bit = 0;
	    col = 0;
	    continue;		/* continuation line.. */
	}
#ifdef USE_TRANSLATION
	append_hdr_ct = 0;
	append_hdr_cte = 0;
#endif				/* USE_TRANSLATION */

	/* ================ PROCESS THE HEADERS! ================ */
	if (CISTREQN(linebuf, "Subject:", 8)) {
	    insubject = 1;
	} else {
	    if (!insubject) {
#if 0
		if (has8bit)
		    sprintf(msg, "Header line \"%.200s\" contains illegal 8-bit chars", linebuf);
#endif
		has8bitsum += has8bit;
	    }
	}
	/* XX: The anti-spam hacks. To differentiate between the auto-freeze of
	 * allegedly-spam messages and freezes resulting from user-specified
	 * smtp-policy.src, we do not use 1 to freeze the messages.
	 */
	if (CISTREQN(linebuf, "X-Advertisement:",16)) {
	  /* Gee... Only SPAMmers (Cyberpromo!) use this .. (I hope..) */
	  SS->policyresult = FREEZE__X_ADVERTISEMENT_FOUND;
	  type(NULL,0,NULL,"Found X-Advertisement header");
	}
	if (CISTREQN(linebuf, "X-Advertisment:",15)) {
	  /* Gee... Only SPAMmers (Cyberpromo!) use this .. (I hope..) */
	  SS->policyresult = FREEZE__X_ADVERTISEMENT_FOUND;
	  type(NULL,0,NULL,"Found X-Advertisment header");
	}
#ifdef USE_ANTISPAM_HACKS
	if (strncmp(linebuf, "X-UIDL:", 7)==0) {
	  /* Sigh... SPAMmers use this .. but it is valid AOL too.. */
	  SS->policyresult = FREEZE__X_UIDL_FOUND;
	}
#endif
#ifdef USE_ANTISPAM_HACKS
	/* This test probably doesn't work */
	if (CISTREQN(linebuf, "Received:", 9)) {
	  /* Scan for "(really ". Anything with this string in the Received
	   * header is highly likely to have a forged Received header
	   * characteristic of spams. Unfortunately this string may
	   * also be a result of pure coincidence.
	   */
	  s = linebuf + 11;
	  while (*s) {
	    if (CISTREQN(s, "(really ", 8)) {
	      SS->policyresult = FREEZE__IMPROBABLE_RECEIVED_HEADER_FOUND;
	      type(NULL,0,NULL,"Improbable Received: header");
	      break;
	    }
	    ++s;
	  }
	}
#endif
#ifdef USE_ANTISPAM_HACKS
	if (CISTREQN(linebuf, "Message-ID:", 11)) {
	  /* Freeze any mail with no message id in the message-id header,
	   * or a message id with obvious syntax errors, or message id
	   * with junk after it. These are highly likely to be spam, though
	   * they might only be a result of buggy software. (MS Exchange?)
	   */
	  s = linebuf + 11;
	  while (*s == ' ' || *s == '\t')
	    ++s;
	  if (*s != '<') {
	    SS->policyresult = FREEZE__MALFORMED_MESSAGE_ID_HEADER;
	    type(NULL,0,NULL,"No <> around Message-Id");
	  } else if (s[1] == '@') {
	    SS->policyresult = FREEZE__MALFORMED_MESSAGE_ID_HEADER;
	    type(NULL,0,NULL,"Source route in Message-Id:");
	  } else if (s[1] == '>') {
	    SS->policyresult = FREEZE__MALFORMED_MESSAGE_ID_HEADER;
	    type(NULL,0,NULL,"Empty Message-Id:");
	  } else {
	    const char *t = rfc821_path(s, 1);
	    if (s == t) { /* error */
#ifdef USE_STRICT_MSGID_FREEZING
	      SS->policyresult = FREEZE__MALFORMED_MESSAGE_ID_HEADER;
	      type(NULL,0,NULL,"Message-Id: syntax error");
#endif
	    } else {
	      while (*t == ' ' || *t == '\t' || *t == '\r' || *t == '\n')
		++t;
	      if (*t) {
		SS->policyresult = FREEZE__MALFORMED_MESSAGE_ID_HEADER;
		type(NULL,0,NULL,"Spurious junk after Message-Id:");
	      }
	    }
	  }
	}
#endif
	if (CISTREQN(linebuf, "Precedence:", 11)) {
	    s = linebuf + 11;
	    while (*s == ' ' || *s == '\t')
		++s;
	    if ((eol - s) < 4)
		continue;	/* Hmm.. */
	    if (CISTREQN("high", s, 4))
		mail_priority = _MAILPRIO_HIGH;
	    else if (CISTREQN("junk", s, 4))
		mail_priority = _MAILPRIO_JUNK;
	    else if (CISTREQN("bulk", s, 4))
		mail_priority = _MAILPRIO_BULK;
	    else if (((eol - s) >= 6) && CISTREQN("normal", s, 6))
		mail_priority = _MAILPRIO_NORMAL;
	}
#if 0 /* Nice in theory - impractical in reality */
	if (msa_mode && CISTREQN(linebuf, "Sender:", 7)) {
	    if ( SS->authuser != NULL ) {
	      fprintf(SS->mfp, "Sender: %s@%s\n", SS->authuser, SS->myhostname);
	      fprintf(SS->mfp, "Old-");
	    }
        }
#endif
#ifdef USE_TRANSLATION
	if (X_translation && (X_settrrc == 0)) {
	    if (CISTREQN(linebuf, "Content-Transfer-Encoding:", 26)) {
		if (1) {
		    strcpy(hdr_cte, linebuf);
		    delay_cte = 1;
		    append_hdr_cte = 1;
		    col = 0;
		    has8bit = 0;
		    s = linebuf + 26;
		    while (*s == ' ' || *s == '\t')
			++s;
		    if ((eol - s) < 4)
			continue;	/* Hmm.. */
		    if (CISTREQN("8bit", s, 4))
			cte = CTE_8BIT;
		    else if (CISTREQN("base64", s, 6))
			cte = CTE_BASE64;
		    else if (CISTREQN("quoted-printable", s, 16))
			cte = CTE_QP;
		    continue;	/* do not write out this one */
		}
	    } else if (CISTREQN(linebuf, "Content-Type:", 13)) {
		if (1) {
		    strcpy(hdr_ct, linebuf);
		    delay_ct = 1;
		    append_hdr_ct = 1;
		    col = 0;
		    has8bit = 0;
		    s = linebuf + 13;
		    while (*s == ' ' || *s == '\t')
			++s;
		    if ((eol - s) < 10)
			continue;	/* Hmm.. */

		    /* Must ALWAYS check for C-T: TEXT/any ! */
		    /* #ifdef PARANOID_TRANSLATION */
		    if (CISTREQN("text", s, 4))
			ct_is_text = 1;
		    else
			ct_is_text = 0;
		    /* #endif */ /* PARANOID_TRANSLATION */

		    continue;	/* do not write out this one */
		}
	    }
	}
#endif				/* USE_TRANSLATION */

	if (linecnt == 1 && (strncmp("From ", linebuf, 5) == 0 ||
			     strncmp(">From ", linebuf, 6) == 0)) {
#if 0
	    from__err = 1;
	    sprintf(msg, "Message starts with illegal \"%.200s\" line", linebuf);
#endif
	    /* DO NOT WRITE THIS LINE OUT! */
	    col = 0;
	    has8bit = 0;
	    continue;
	}
#ifdef USE_TRANSLATION
	if (has8bit && !X_8bit)
	    header_to_mime(linebuf, &col, sizeof(linebuf));
	else
	    header_from_mime(linebuf, &col, sizeof(linebuf));
	/* Write the line out */
	if (col > 0)
	    for (wi = 0; wi < col; ++wi)
		fputc(TR_IN(linebuf[wi]), SS->mfp);
#else				/* USE_TRANSLATION */
	if (has8bit)
	    header_to_mime(linebuf, &col, sizeof(linebuf));
	/* Write the line out */
	if (col > 0)
	    fwrite(linebuf, 1, col, SS->mfp);
#endif				/* USE_TRANSLATION */
	has8bit = 0;
	col = 0;
    }
    if (verbose)
      type(NULL,0,NULL,"(mail_priority=%d)", mail_priority);
#endif

    /* ================ Normal email BODY input.. ================ */
    for (;;) {
	c = s_getc(SS);
#if EOF != -1
	if (c == EOF)		/* a little slower... */
	    break;
#endif

	--timercnt;
	if (timercnt <= 0) {
	    /* Re-arm for every kilobyte */
	    alarm(SMTP_DATA_TIME_PER_LINE);
	    timercnt = 1024;
	}
	++cnt;
	state = sts[state + idxnum[c]];
	if (state & ~0xff) {
	    if (state & O_) {
		if (state == endstate)
		    break;
		state = (char) state;
		continue;
	    }
	    if (!ferror(SS->mfp))
		fputc((state >> 8), SS->mfp);
	    state = state & 0xFF;
	}
	if (!ferror(SS->mfp)) {
#ifdef USE_TRANSLATION
	    if (do_decode == CTE_QP) {
		if (!qp_chars && c == '=') {
		    qp_chars = 2;
		    qp_hex = 0;
		    continue;
		}
		if (qp_chars && c == '\n') {
		    qp_chars = 0;
		    continue;
		}
		if (qp_chars == 2 && (c == ' ' || c == '\t')) {
		    continue;
		}
		if (qp_chars && ((c >= '0' && c <= '9') ||
				 (c >= 'A' && c <= 'F') ||
				 (c >= 'a' && c <= 'f'))) {
		    qp_hex <<= 4;
		    if (c >= '0' && c <= '9')
			qp_hex += (c - '0');
		    if (c >= 'A' && c <= 'F')
			qp_hex += (c - 'A' + 10);
		    if (c >= 'a' && c <= 'f')
			qp_hex += (c - 'a' + 10);
		    --qp_chars;
		    if (!qp_chars)
			c = qp_hex;
		    else
			continue;
		} else if (qp_chars)
		    qp_chars = 0;
	    } else if (do_decode == CTE_BASE64) {
		if (b64decoding) {
		    if ((c == ' ') || (c == '\t') ||
			(c == '\n') || (c == '\r'))
			continue;
		    b64c[b64i++] = decodechar64(c);
		    if (c == '=') {
			b64eod = 1;
			continue;
		    }
		    if (b64i < 2)
			continue;
		    if (b64i == 2) {
			c = (b64c[0] << 2) |
			    ((b64c[1] & 0x30) >> 4);
		    } else if (b64i == 3) {
			c = ((b64c[1] & 0x0f) << 4) |
			    ((b64c[2] & 0x3c) >> 2);
		    } else {
			c = (b64c[2] << 6) |
			    b64c[3];
			b64i = 0;
			if (b64eod)
			    b64decoding = 0;
		    }
		}
	    }
	    if (do_translate)
		fputc(TR_IN(c), SS->mfp);
	    else
#endif				/* USE_TRANSLATION */
		fputc(c, SS->mfp);
	}
    }
    typeflush(SS);
    return cnt;
}

/*
 *  BDAT -- For ESMTP CHUNKING extension (rfc 1830)
 */
static int /* count of bytes */ mvbdata(SS, msg, incount)
SmtpState *SS;
char *msg;
register long incount;
{
    register int c, cnt;
    int timercnt = 0;

    cnt = 0;

    /* XX: header processing REMOVED from BDAT processing */

    /* ================ Normal email BODY input.. ================ */
    for (; incount > 0; --incount) {
	if (--timercnt <= 0) {
	    /* Re-arm for every kilobyte */
	    alarm(SMTP_DATA_TIME_PER_LINE);
	    timercnt = 1024;
	}
	c = s_getc(SS);
	if (c == EOF)
	    break;
	++cnt;
	/* Canonize CR+LF --> LF (UNIX style) */
	if (c == '\r') {	/* Suspend sending, this is our 'mvbstate' */
	    /* do nothing, 'mvbstate = c' is done after this if-else */
	} else if (SS->mvbstate == '\r') {
	    if (c != '\n') {
		/* Suspended lone CR */
		if (SS->mfp) {	/* We just discard it, if no output stream */
		    if (!ferror(SS->mfp))
			fputc(SS->mvbstate, SS->mfp);
		    if (!ferror(SS->mfp))
			fputc(c, SS->mfp);
		}
	    } else {
		/* CR + LF -- forget the CR */
		if (SS->mfp && !ferror(SS->mfp))
		    fputc(c, SS->mfp);
	    }
	} else {
	    /* Anything else, just output it! */
	    if (SS->mfp && !ferror(SS->mfp))
		fputc(c, SS->mfp);
	}
	SS->mvbstate = c;
    }
    return cnt;
}
