/*
 * Copyright (c) 1996-2007, OpenFWTK Development Group
 * All rights reserved. See LICENSE.
 */

/*
 * This is independant code branch modified to fit fwtk configuration and
 * logging style by ArkanoiD, 2001
 *
 * Original copyright info follows.
 */
	
/*
 * smtpd, Obtuse SMTP daemon, storing agent. does simple collection of
 * mail messages, for later forwarding by smtpfwdd.
 *
 * Copyright (c) 1996, 1997 Obtuse Systems Corporation. All rights
 * reserved.
 *   
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *  notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by Obtuse Systems 
 *      Corporation and its contributors.
 * 4. Neither the name of the Obtuse Systems Corporation nor the names
 *    of its contributors may be used to endorse or promote products 
 *    derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY OBTUSE SYSTEMS CORPORATION AND
 * CONTRIBUTORS ``AS IS''AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL OBTUSE SYSTEMS CORPORATION OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 */

char *obtuse_copyright =
"Copyright 1996 - Obtuse Systems Corporation - All rights reserved.";

#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <pwd.h>
#include <grp.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <sys/file.h>
#include <sys/socket.h>
#include <arpa/nameser.h>
#include <sys/wait.h>
#include <unistd.h>
#include <time.h>

#ifdef IRIX_BROKEN_INCLUDES
/* IRIX 5.3 defines EX_OK (see sysexits.h) as something very strange in unistd.h :-) */
#ifdef EX_OK
#undef EX_OK
#endif
#endif

#ifndef NS_MAXDNAME
#define NS_MAXDNAME MAXDNAME
#endif

#include <syslog.h>
#include <errno.h>
#include <limits.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/utsname.h>
#include <string.h>
#include <sysexits.h>
#include <ctype.h>
#ifdef NEEDS_STRINGS_H
#include <strings.h>
#endif
#ifdef NEEDS_FCNTL_H
#include <fcntl.h>
#endif
#ifdef NEEDS_BSTRING_H
#include <bstring.h>
#endif
#ifdef NEEDS_SELECT_H
#include <sys/select.h>
#endif

#include "smtp.h"
#include "smtpd.h"
#include "firewall.h"
#include "firewall2.h"
#include "fwfunc.h"

static char* moduleId ATTR_UNUSED = "$Id: smtpd.c,v 1.10 2007/09/22 15:59:43 arkenoi Exp $";

extern	struct	fwstats	proxy_stats;
extern	char	proxy_chroot[];
extern	Cfg*	proxy_confp;
extern	int	proxy_timeout;
extern	int	proxy_uid, proxy_gid;

#ifndef SPOOLSUBDIR
#define SPOOLSUBDIR "."
#endif

#define SPANBLANK(p)	while (isspace(*p)) p += 1

struct sockaddr_in my_sa;

char *my_name = NULL;
char *current_from_mailpath = NULL;
char *client_claimed_name = "UNKNOWN";
char *spoolfile = NULL;
char *spooldir = SPOOLSUBDIR;		/* this is relative to our chroot. */
long maxsize = 0;
int maxheader;
int maxrecip;
int outfd, replyfd;
#ifdef SUNOS_GETOPT
extern char *optarg;
extern int optind;
#else
char *optarg;
int optind;
#endif
struct smtp_mbuf *input_buf, *output_buf, *reply_buf;
int exiting = 0;
int VerboseSyslog = 0;
char* my_clean_reverse_name = NULL;
static	int dscp = 0;

static	fwparm	options[] = {
		{FWPARM_BOOL,	"-log",	(char*) &VerboseSyslog	},
		{FWPARM_DSCP,	"-client-dscp", (char*) &dscp 	},
		{FWPARM_DSCP,	"-dscp", 	(char*) &dscp 	},
		{0,		0,	0 			}};


extern int matches_spam_db(char *sender, char *hostname, char *addr, Cfg *config);
extern int valid_recipient(char *rcpt, char *addr, Cfg *config);
extern int valid_sender(char *sender, Cfg *configTable);
extern int matches_maps_rbl(char *addr, Cfg *configTable);

/*
 * Signal handler that shuts us down if a read on the socket times out
 */

static
void
read_alarm_timeout(int s)
{
  if (s != SIGALRM) {
    syslog(LLEV, "fwtksyserr: read timeout alarm handler called with signal %d!, (not SIGALRM!) - Aborting!", s);
    abort();
  }
  syslog(LLEV, "Network timeout signal after %d seconds", proxy_timeout);
  smtp_exit(EX_OSERR);
}

#ifdef NO_MEMMOVE
/*
 * Use bcopy on platforms that don't support the newer memmove function
 */

void
memmove(void *to, void *from, int len)
{
  bcopy(from, to, len);
}

#endif

/*
 * Return to the initial state
 */

void
reset_state(smtp_state state)
{
  if (test_state(OK_HELO, state)) {
    zap_state(state);
    set_state(OK_HELO, state);
  } else {
    zap_state(state);
  }

  /*
   * we must throw away anything in the output buffer 
   */
  output_buf->tail = output_buf->data;
  output_buf->offset = 0;
}

/*
 * returns the index of the start of the first end-of-command token *
 * in buf. returns len if it couldn't find one. 
 */

int
crlf_left(unsigned char *buf, size_t len)
{
  int i;

  i = 0;
  while (i < len) {
#ifdef CRLF_PEDANTIC
    /*
     * This is how the RFC says the world should work.
     * Unfortunately, it doesn't.
     */
    if (buf[i] == CR)
      if (i < (len - 1))
	if (buf[i + 1] == LF)
	  return (i);
#else
    /*
     * The world works like this.
     */
    if (buf[i] == LF) {
      if (i > 0 && buf[i - 1] == CR) {
	return (i - 1);
      } else {
	return (i);
      }
    }
#endif
    i++;
  }
/*
 * couldn't find one 
 */
  return (len);
}

/*
 * find next crlf in buf, starting at offset. On finding one, replaces 
 * the first byte of crlf with \0, sets offset to first byte after end 
 * of crlf, and returns start of string if we don't find one we return 
 * NULL 
 */

char *
smtp_get_line(struct smtp_mbuf *mbuf, size_t * offset)
{
  int i;
  size_t len;
  unsigned char *buf;

  buf = mbuf->data + *offset;
  len = mbuf->offset - *offset;

  if (len == 0) {
    return (NULL);
  }
  i = crlf_left(buf, len);
  if (i < (len)) {
    buf[i] = '\0';
    /*
     * jump over end of line token 
     */
    i++;

    if ((i < len) && (buf[i] == LF)) {
      i++;
    }
    *offset += i;

    return ((char*) buf);
  }
  return (NULL);
}

/*
 * flush len bytes of an mbuf to file descriptor fd.
 */

void
flush_smtp_mbuf(struct smtp_mbuf *buf, int fd, int len)
{
  int foo = 0;
  static int deaththroes=0;


  if (deaththroes) {
    return; /* We've already had a write barf. Don't try again */ 
  }
  if (len <= buf->offset) {
    while (foo < len) {
      int i;
      
      i = write(fd, (buf->data) + foo, len);
      if (i < 0) {
	syslog(LLEV, "write failed: (%s)", strerror(errno));
	deaththroes=1;
	smtp_exit(EX_OSERR);
      }
      foo += i;

      /*
       * ok. reset the mbuf. 
       */
      if (foo == buf->offset) {
	buf->offset = 0;
	buf->tail = buf->data;
      } else {
	clean_smtp_mbuf(buf, foo);
      }
    }
  } else {
    syslog(LLEV, "fwtksyserr: you can't write %d bytes from a buffer with only %d in it!", len, (int) buf->offset);
  }
}

/*
 * Strip some data out of an smtp_mbuf
 */

void
clean_smtp_mbuf(struct smtp_mbuf *buf, int len)
{
  if (len > buf->offset) {
    abort();
  }
  if (len < buf->offset) {
    memmove(buf->data, (buf->data) + len, (buf->offset) - len);
    buf->offset = buf->offset - len;
    buf->tail = (buf->data) + (buf->offset);
  } else {
    buf->offset = 0;
    buf->tail = buf->data;
  }
}

/*
 * Allocate and initialize an smtp_mbuf
 */

struct smtp_mbuf *
alloc_smtp_mbuf(size_t size)
{
  struct smtp_mbuf *newbuf;
  newbuf = (struct smtp_mbuf *) malloc(sizeof(struct smtp_mbuf));

  if (newbuf == NULL) {
    return (NULL);
  }
  newbuf->data = (unsigned char *) malloc(sizeof(unsigned char) * size);

  if (newbuf->data == NULL) {
    free(newbuf);
    return (NULL);
  }
  newbuf->size = size;
  newbuf->offset = 0;
  newbuf->tail = newbuf->data;
  return (newbuf);
}

/*
 * Grow data area by "bloat" preserving everything else 
 */

int
grow_smtp_mbuf(struct smtp_mbuf *tiny, size_t bloat)
{
  unsigned char *newdata;

  newdata = (unsigned char *) malloc(tiny->size + bloat);
  if (newdata == NULL)
    return (0);
  memcpy((void *) newdata, (void *) tiny->data, tiny->offset);
  free(tiny->data);
  tiny->data = newdata;
  tiny->size += bloat;
  tiny->tail = tiny->data + tiny->offset;
  return (1);
}

/*
 * write len bytes from data into buffer mbuf. growing if necessary.
 * return 1 if successful, 0 for failure. 
 */

int
write_smtp_mbuf(struct smtp_mbuf *mbuf,
		char *data,
		size_t len)
{
  if (len > (mbuf->size - mbuf->offset)) {
    /*
     * we need a bigger buffer 
     */
    if (!(grow_smtp_mbuf(mbuf, ((len / 1024) + 1) * 1024))) {
      /*
       * let's hope there is enough to syslog :-) 
       */
      syslog(LLEV, "fwtksyserr: malloc said no to a %d byte buffer!",
	    (int)(mbuf->size + len + 1024));
      return (0);
    }
  }

  /*
   * buffer is now big enough 
   */
  memcpy((void *) mbuf->tail, (void *) (data), len);
  mbuf->tail += len;
  mbuf->offset += len;
  return (1);
}

/*
 * read up to len bytes from fd into buffer mbuf. growing if
 * neccessary.
 * return amount read if successful.
 *  set errno and return -1 for failure. 
 */
int
read_smtp_mbuf(struct smtp_mbuf *mbuf,
	       int fd,
	       size_t len)
{
  int howmany;

  if (len > (mbuf->size - mbuf->offset)) {
    /*
     * we need a bigger buffer 
     */
    if (!(grow_smtp_mbuf(mbuf, ((len / 1024) + 1) * 1024))) {
      /*
       * let's hope there is enough to syslog :-) 
       */
      syslog(LLEV, "fwtksyserr: malloc said no to a %d byte buffer!",
	    (int)(mbuf->size + len + 1024));
      errno = ENOMEM;
      return (-1);
    }
  }
  /*
   * buffer is now big enough 
   */
 
  fflush(NULL); 
  signal(SIGALRM, read_alarm_timeout);
  alarm(proxy_timeout);
  howmany = read(fd, mbuf->tail, len);
  alarm(0);
  signal(SIGALRM, SIG_DFL);
  if (howmany > 0) {
    mbuf->tail += howmany;
    mbuf->offset += howmany;
  }
  return (howmany);
}

/*
 * Write a possibly multi-segement reply into mbuf "outbuf"
 */
int
writereply(struct smtp_mbuf *outbuf,
	   int code,
	   int more,
	   ...)
{
  int ok;
  char message[5];
  va_list ap;
  char *msg;

  va_start(ap, more);
  snprintf(message, 5, "%3d%s", code, (more) ? "-" : " ");
  ok = write_smtp_mbuf(outbuf, message, strlen(message));
  while (ok && (msg = va_arg(ap, char *)) != NULL) {
    ok = write_smtp_mbuf(outbuf, msg, strlen(msg));
  }
  if (ok) {
    ok = write_smtp_mbuf(outbuf, CRLF, 2);
  }
  va_end(ap);
  return (ok);
}

/*
 * open a new spoolfile with appropriate lock and permissions 
 */
int
smtp_open_spoolfile()
{
  int fd;
  int len;

  if (spoolfile != NULL) {
    syslog(LLEV, "fwtksyserr: attempt to open new spoolfile with %s already open - aborting",
	   spoolfile);
    abort();
  }
  putenv(xstrdup("TMPDIR=/"));	/*
				 * Linux's tempnam() requires this kludge
				 * or we have to make a /tmp in our
				 * chrootdir. In it's wisdom it decides
				 * that if /tmp doesn't exist we can't
				 * have a tmpname anywhere.. Grumble.. 
				 */

  /* If someone does manage to misconfigure us so people have
   * access to the spool dir they probably have worse things to 
   * worry about than the race condition but.. Oh well, keeps gcc 
   * from complaining. 
   */
  spoolfile= (char *) xmalloc(len = (strlen(spooldir)+13));
  strlcpy(spoolfile, spooldir, len);
  strlcat(spoolfile, "/smtpdXXXXXX", len);
  if ((fd = mkstemp(spoolfile)) < 0) {
    syslog(LLEV, "fwtksyserr: couldn't create spool file %s!", spoolfile);
    free(spoolfile);
    spoolfile=NULL;
    smtp_exit(EX_CONFIG);
  }

  if (lock_fd(fd) != 0) {
    syslog(LLEV, "fwtksyserr: couldn't lock spool file %s", spoolfile);
    smtp_exit(EX_TEMPFAIL);
  }
  return (fd);
}

/*
 * close spoolfile, unlock, and open permissions 
 */
void
smtp_close_spoolfile(int fd)
{
  if (spoolfile == NULL) {
    syslog(LLEV, "fwtksyserr: attempt to close NULL spoolfile!");
    smtp_exit(EX_CONFIG);
  }
  if (locktest_fd(fd) == 0)
    if (lockun_fd(fd) != 0) {
      syslog(LLEV, "fwtksyserr: couldn't unlock spool file %s", spoolfile);
      smtp_exit(EX_OSERR);
    }
  close(fd);
  chmod(spoolfile, 0750);	/*
				 * Mark file as 'complete' 
				 */
  free(spoolfile);
  spoolfile = NULL;
}

/*
 * unlock spoolfile and remove it 
 */
void
smtp_nuke_spoolfile(int fd)
{
  if (spoolfile == NULL) {
    syslog(LLEV, "fwtksyserr: attempt to remove NULL spoolfile!");
    smtp_exit(EX_SOFTWARE);
  }
  if (unlink(spoolfile) != 0) {
    syslog(LLEV, "fwtksyserr: couldn't remove spool file %s! (%s)", spoolfile, strerror(errno));
    free(spoolfile);
    spoolfile = NULL;
    smtp_exit(EX_CONFIG);
  }
  if (locktest_fd(fd) == 0)
    if (lockun_fd(fd) != 0) {
      syslog(LLEV, "fwtksyserr: couldn't unlock spool file %s using lockf! (%s)", spoolfile, strerror(errno));
      free(spoolfile);
      spoolfile = NULL;
      smtp_exit(EX_OSERR);
    }
  close(fd);
  free(spoolfile);
  spoolfile = NULL;
}

/*
 * Try to say something meaningful to our client and then exit.
 */

void
smtp_exit(int val)
{
  if (val != 0) {
    /*
     * we're leaving the client hanging. attempt to tell them we're
     * going away 
     */
    if (exiting++<1) {
       writereply(reply_buf, 421, 0, m421msg, NULL);
    }
     
    /*
     * if we have an open spool file that's unclosed, blast it out of
     * existence 
     */
    if (exiting++<2) {
       if (spoolfile != NULL) {
	      smtp_nuke_spoolfile(outfd);
       }
    }
  } else {
    if (exiting++<1) {
       if (spoolfile != NULL) {
	  smtp_close_spoolfile(outfd);
	  exiting++;
       }
    }
  }
  if (exiting++<3) {
	  flush_smtp_mbuf(reply_buf, replyfd, reply_buf->offset);
  }

  if (!VerboseSyslog) {
    accumlog(LOG_INFO, 0);		/* flush? */
  }
     
  syslog(LLEV,"exit host=%.128s/%.128s bytes=%u",proxy_stats.rladdr,proxy_stats.riaddr,proxy_stats.outbytes);
  exit(val);
}

/*
 * clean up things (mostly hostnames) that will go into the syslogs 
 */

unsigned char *
cleanitup(const unsigned char *s)
{
  static unsigned char *bufs[20];
  static int first = 1, next_buffer;
  unsigned char *dst, *buffer_addr;
  const unsigned char *src;
  int saw_weird_char, saw_bad_char, saw_high_bit = 0;

  if (first) {
    int i;

    for (i = 0; i < 20; i += 1) {
      bufs[i] = malloc(1024);
      if (bufs[i] == NULL) {
	syslog(LLEV, "fwtksyserr: malloc (1024 bytes) failed in cleanitup");
	abort();
      }
    }
    first = 0;
  }
  src = s;
  dst = bufs[(next_buffer++) % 20];
  buffer_addr = dst;
  saw_weird_char = 0;
  saw_bad_char = 0;
  saw_high_bit = 0;

  while (*src != '\0') {
    unsigned char xch, ch;

    xch = *src++;
    ch = xch & 0x7f;

    if (ch != xch) {
      saw_high_bit = 1;
      *dst++ = '^';
      *dst++ = '=';
    }
    if (isalnum(ch) || strchr(" -/:=.@_[]", ch) != NULL) {

      *dst++ = ch;

    } else if (strchr("\\`$|;()*", ch) != NULL) {

      saw_bad_char = 1;

      *dst++ = '^';
      *dst++ = 'x';
      *dst++ = "0123456789abcdef"[(xch >> 4) & 0xf];
      *dst++ = "0123456789abcdef"[(xch) & 0xf];

    } else {

      saw_weird_char = 1;

      switch (ch) {
      case '\n':
	*dst++ = '^';
	*dst++ = 'n';
	break;
      case '\r':
	*dst++ = '^';
	*dst++ = 'r';
	break;
      case '\t':
	*dst++ = '^';
	*dst++ = 't';
	break;
      case '\b':
	*dst++ = '^';
	*dst++ = 'b';
	break;
      case '^':
	*dst++ = '^';
	*dst++ = '^';
	break;
      default:
	*dst++ = '^';
	*dst++ = 'x';
	*dst++ = "0123456789abcdef"[(xch >> 4) & 0xf];
	*dst++ = "0123456789abcdef"[(xch) & 0xf];
      }

    }

    if (dst - buffer_addr > 1024 - 10) {
      syslog(LLEV, "INFO(cleanitup) - buffer overflow - chopping");
      break;
    }
  }

  *dst = '\0';


  if (saw_bad_char) {
    syslog(LLEV, "securityalert: (cleanitup) - saw VERY unusual character (cleaned string is \"%s\")", buffer_addr);
  }
  if (saw_weird_char) {
    syslog(LLEV, "INFO(cleanitup) - saw unusual character (cleaned string is \"%s\")", buffer_addr);
  }
  if (saw_high_bit) {
    syslog(LLEV, "INFO(cleanitup) - saw character with high bit set (cleaned string is \"%s\")", buffer_addr);
  }
  return (buffer_addr);
}

/*
 * less paranoid version of cleanitup that tries to handle mail addresses
 * without mangling them.
 */
unsigned char *
smtp_cleanitup(const unsigned char *s)
{
  static unsigned char *bufs[20];
  static int first = 1, next_buffer;
  unsigned char *dst, *buffer_addr;
  const unsigned char *src;
  int firstone, arg_attempt, saw_weird_char, saw_bad_char, saw_high_bit = 0;

  if (first) {
    int i;

    for (i = 0; i < 20; i += 1) {
      bufs[i] = malloc(1024);
      if (bufs[i] == NULL) {
	syslog(LLEV, "fwtksyserr: CRITICAL - malloc (1024 bytes) failed in smtp_cleanitup");
	abort();
      }
    }
    first = 0;
  }
  src = s;
  dst = bufs[(next_buffer++) % 20];
  buffer_addr = dst;
  saw_weird_char = 0;
  saw_bad_char = 0;
  saw_high_bit = 0;
  arg_attempt = 0;

  firstone = 1;
  while (*src != '\0') {
    unsigned char xch, ch;

    xch = *src++;
    ch = xch & 0x7f;

    if (ch != xch) {
      saw_high_bit = 1;
      *dst++ = '^';
      *dst++ = '=';
    }
    /*
     * <sjg> RFC822 allows both ' and " in local-part.
     * " is infact _required_ if local-part contains spaces as is
     * common in x400 (yuk).
     */
    if (isalnum(ch) || strchr(" -,:=.@_!<>()[]/+%'\"", ch) != NULL) {
      if (firstone && (ch == '-')) {
	arg_attempt = 1;
	*dst++ = '^';
	*dst++ = '-';
      } else {
	*dst++ = ch;
      }

    } else if (strchr("\\`$|*;", ch) != NULL) {

      saw_bad_char = 1;

      *dst++ = '^';
      *dst++ = 'x';
      *dst++ = "0123456789abcdef"[(xch >> 4) & 0xf];
      *dst++ = "0123456789abcdef"[(xch) & 0xf];

    } else {

      saw_weird_char = 1;

      switch (ch) {
      case '\n':
	*dst++ = '^';
	*dst++ = 'n';
	break;
      case '\r':
	*dst++ = '^';
	*dst++ = 'r';
	break;
      case '\t':
	*dst++ = '^';
	*dst++ = 't';
	break;
      case '\b':
	*dst++ = '^';
	*dst++ = 'b';
	break;
      case '^':
	*dst++ = '^';
	*dst++ = '^';
	break;
      default:
	*dst++ = '^';
	*dst++ = 'x';
	*dst++ = "0123456789abcdef"[(xch >> 4) & 0xf];
	*dst++ = "0123456789abcdef"[(xch) & 0xf];
      }

    }

    if (dst - buffer_addr > 1024 - 10) {
      syslog(LLEV, "INFO(smtp_cleanitup) - buffer overflow - chopping");
      break;
    }
    firstone = 0;
  }


  *dst = '\0';

  if (arg_attempt) {
    syslog(LLEV, "securityalert: (smtp_cleanitup) - '-' as first character in address (cleaned string is \"%s\")", buffer_addr);
  }
  if (saw_bad_char) {
    syslog(LLEV, "securityalert: (smtp_cleanitup) - saw VERY unusual character (cleaned string is \"%s\")", buffer_addr);
  }
  if (saw_weird_char) {
    syslog(LLEV, "INFO(smtp_cleanitup) - saw unusual character (cleaned string is \"%s\")", buffer_addr);
  }
  if (saw_high_bit) {
    syslog(LLEV, "INFO(smtp_cleanitup) - saw character with high bit set cleaned string is \"%s\")", buffer_addr);
  }
  return (buffer_addr);
}


/*
 * is smtp command "cmd" legal in state "state" 
 */
int
cmd_ok(int cmd, smtp_state state)
{
  if (sane_state(state)) {
    switch (cmd) {
    case HELO:
      return (!(test_state(OK_HELO, state)));
#if EHLO_KLUDGE
    case EHLO:
      return(test_state(OK_EHLO, state));
#endif
    case MAIL:
      if (test_state(OK_HELO, state) && (!test_state(OK_MAIL, state)))
	return (1);
      else
	return (0);
    case RCPT:
      if (test_state(OK_MAIL, state) && (!test_state(SNARF_DATA, state)))
	return (1);
      else
	return (0);
    case DATA:
      if (test_state(OK_RCPT, state))
	return (1);
      else
	return (0);
    default:
      return (1);
    }
  }
  return (0);
}

/*
 * is this state legal? returns 1 if so, 0 if not 
 */
int
sane_state(smtp_state state)
{
  if (test_state(OK_MAIL, state) && !test_state(OK_HELO, state)) {
    syslog(LOG_DEBUG, "Bad state. can't be OK_MAIL and not OK_HELO");
    return (0);
  }
  if (test_state(OK_RCPT, state) && !test_state(OK_MAIL, state)) {
    syslog(LOG_DEBUG, "Bad state. can't be OK_RCPT and not OK_MAIL");
    return (0);
  }
  if (test_state(SNARF_DATA, state) && !test_state(OK_RCPT, state)) {
    syslog(LOG_DEBUG, "Bad state. can't be SNARF_DATA and not OK_RCPT");
    return (0);
  }
  return (1);
}

/*
 * state change engine. given "state", change state after processing * 
 * command "cmd" with status "status", 
 */
void
state_change(smtp_state state, int cmd, int status)
{
  /*
   * basic state sanity checks 
   */

  if (!sane_state(state)) {
    reset_state(state);
    return;
  }
  switch (cmd) {
  case HELO:
    switch (status) {
    case SUCCESS:
      set_state(OK_HELO, state);	/*
					 * we got a helo 
					 */
      return;
    case ERROR:
      clear_state(OK_HELO, state);
      return;
    case FAILURE:
      reset_state(state);
      return;

    default:
      syslog(LLEV, "fwtksyserr: hey, I shouldn't be here (Bad HELO status in change_state)!\n");
      abort();
    }
#if EHLO_KLUDGE
  case EHLO:
    switch (status) {
    case SUCCESS:
      set_state(OK_EHLO, state);	/*
					 * we got a ehlo 
					 */
      return;
    case ERROR:
      clear_state(OK_EHLO, state);
      return;
    case FAILURE:
      reset_state(state);
      return;

    default:
      syslog(LLEV, "fwtksyserr: hey, I shouldn't be here (Bad EHLO status in change_state)!\n");
      abort();
    }
#endif
  case MAIL:
    switch (status) {
    case SUCCESS:
      set_state(OK_MAIL, state);
      return;
    case ERROR:
      /*
       * no state change 
       */
      return;
    case FAILURE:
      reset_state(state);
      return;
    default:
      syslog(LLEV, "fwtksyserr: hey, I shouldn't be here (Bad MAIL status in change_state)!\n");
      abort();
    }
  case RCPT:
    switch (status) {
    case SUCCESS:
      set_state(OK_RCPT, state);
      return;
    case ERROR:
      /*
       * no state change 
       */
      return;
    case FAILURE:
      reset_state(state);
      return;
    default:
      syslog(LLEV, "fwtksyserr: hey, I shouldn't be here (Bad RCPT status in change_state)!\n");
      abort();
    }
  case NOOP:
    switch (status) {
    case SUCCESS:
      return;
    default:
      syslog(LLEV, "fwtksyserr: hey, I shouldn't be here (Bad NOOP status in change_state)!\n");
      abort();
    }
  case DATA:
    switch (status) {
    case SUCCESS:
      set_state(SNARF_DATA, state);
      return;
    case ERROR:
      /*
       * hmm. hard to do this 
       */
      return;
    case FAILURE:
      reset_state(state);
      return;
    default:
      syslog(LLEV, "fwtksyserr: hey, I shouldn't be here (Bad DATA status in change_state)!\n");
      abort();
    }
  case UNKNOWN:
    switch (status) {
    case SUCCESS:
      return;
    case ERROR:
      return;
    case FAILURE:
      reset_state(state);
      return;
    default:
      syslog(LLEV, "fwtksyserr: hey, I shouldn't be here (Bad UNKNOWN status in change_state)!\n");
      abort();
    }
  case QUIT:
    /*
     * one can always quit 
     */
    smtp_exit(EX_OK);
    break;
  case RSET:
    /*
     * one can always reset 
     */
    reset_state(state);
    break;
  default:
    /*
     * shouldn't get here on valid input. 
     */
    syslog(LLEV, "fwtksyserr: hey, I shouldn't be here (end of change_state)!\n");
    abort();
  }
}

/*
 * parse a single smtp command in inbuf.
 *
 * PRE: "inbuf" contains one read line, \0 terminated without CRLF
 * at the end, and a non-whitespace character at the start. initial
 * state pointer passed in as "state". "outbuf" is our buffer for output
 * we're keeping, "replybuf" is our buffer for replies to the client.
 *
 * POST: any output from the command is output to the end of outbuf,
 * any replys to the client are output to the end of replybuf.
 * state is changed accordingly.
 *
 */

void
smtp_parse_cmd(unsigned char *inbuf,
	       struct smtp_mbuf *outbuf,
	       struct smtp_mbuf *replybuf,
	       smtp_state state)
{
  unsigned char *buf, *cp;
  size_t ilen;
  unsigned char verb[5];
  int currecip = 0;

  ilen = strlen((char*) inbuf);
  if (ilen < 4) {
    if (ilen == 3) {
      memcpy(verb, inbuf, 3);
      verb[3] = '\0';
      if (strcasecmp((char*)verb, "WIZ") == 0) {
	syslog(LLEV,
	       "securityalert: wizard command attempted from address %.128s/%.128s, name %.256s",
	       proxy_stats.rladdr, proxy_stats.riaddr, client_claimed_name);
	writereply(replybuf, 250, 0, m250msg, NULL);
	state_change(state, NOOP, SUCCESS);
	return;
      }
    } else {
      /*
       * we need at least one complete verb. 
       */
      writereply(replybuf, 500, 0, m500msg, NULL);
      state_change(state, UNKNOWN, ERROR);
      return;
    }
  }
  proxy_update_operation((char*)inbuf);
  memcpy(verb, inbuf, 4);
  verb[4] = '\0';
  buf = inbuf + 4;

  /* The basic vanilla SMTP commands, minimum as specified in RFC 821;
   * HELO, MAIL, RCPT, DATA, RSET, NOOP, QUIT. Added minimal VRFY
   * after rumors (never substantiated) that some mail agents might
   * try it thanks to RFC 1123.  We don't bother checking address
   * <domain> parameter syntax rigidly like RFC 1123 says we should,
   * leaving that up to the MTA invoked at the end. I don't believe
   * the added code complexity is worth any practical benefit here
   * when we are invoking the MTA after. Feel free to convince me
   * otherwise. 
   */

  /*
   * HELO 
   */

#if EHLO_KLUDGE
  if ((strcasecmp((char*)verb, "HELO") == 0) ||
      (cmd_ok(EHLO, state) && (strcasecmp((char*)verb, "EHLO") == 0))) {
#else
  if (strcasecmp((char*)verb, "HELO") == 0) {
#endif

    /*
     * Hello hello.. a-la RFC 821 
     */
    if (!cmd_ok(HELO, state)) {
      writereply(replybuf, 503, 0, m503msg, NULL);
      state_change(state, HELO, FAILURE);
      return;
    }
    /*
     * at this point I shouldn't have anything bigger than a hostname
     * left. 
     */
    SPANBLANK(buf);
    if (strlen((char*)buf) > SMTP_MAXFQNAME) {
      /*
       * someone gave us a *big* name for themselves. draw them to
       * our attention, and fail. 
       */
      syslog(LLEV,
	     "securityalert: more than %u bytes on HELO from %.128s/%.128s).",
	     SMTP_MAXFQNAME, proxy_stats.rladdr, proxy_stats.riaddr);
      state_change(state, HELO, FAILURE);
      return;
    }
    client_claimed_name = xstrdup((char*)cleanitup(buf));
    if (strcmp((char*) buf, client_claimed_name) != 0) {
      	syslog(LLEV, "securityalert: suspicious characters in HELO: hostname from host %.128s/%.128s, exiting",
	     proxy_stats.rladdr, proxy_stats.riaddr);
	smtp_exit(EX_PROTOCOL);
    }
    writereply(replybuf, 250, 0,
	       my_clean_reverse_name,
	       " ",
	       m250helook,
	       " ",
	       client_claimed_name,
	       NULL);
    /*
     * log the connection 
     */
    if (VerboseSyslog) {
      syslog(LLEV, "SMTP HELO from %.128s/%.128s as \".256%s\"",
	     proxy_stats.rladdr,
	     proxy_stats.riaddr, client_claimed_name);
    }
    else {
      accumlog(LLEV, 0);		/* flush anything left */
      accumlog(LLEV, "relay=%s/%s",
	       proxy_stats.rladdr,proxy_stats.riaddr);
      if (strcasecmp(proxy_stats.rladdr, client_claimed_name))
	accumlog(LLEV, " as \"%s\"", client_claimed_name);
    }     
    state_change(state, HELO, SUCCESS);
  } else if (strcasecmp((char*)verb, "MAIL") == 0) {
    if (!cmd_ok(MAIL, state)) {
      writereply(replybuf, 554, 0, m554msg, NULL);
      state_change(state, MAIL, ERROR);
      return;
    }
    /*
     * at this point I shouldn't have anything bigger than a return *
     * address and a FROM: left. 
     */
    if (strlen((char*)buf) > SMTP_MAX_MAILPATH + 7) {
      /*
       * someone gave us a *big* name * for themselves. draw them to 
       * our attention, and fail. 
       */
      syslog(LLEV,
	     "securityalert: more than %d bytes on MAIL from address %.128s/%.128s",
	     SMTP_MAX_MAILPATH, proxy_stats.rladdr, proxy_stats.riaddr);
      state_change(state, MAIL, FAILURE);
      return;
    }
    SPANBLANK(buf);
    if (strncasecmp((char*) buf, "FROM:", 5) != 0) {
      writereply(replybuf, 501, 0, m501msg, NULL);
      state_change(state, MAIL, ERROR);
      return;
    }
    buf += 5;
    SPANBLANK(buf);
    /*
     * <sjg> if local-part contains ", then spaces are allowed
     */
    cp = NULL;
    if (buf[0] == '"' || buf[1] == '"')
      cp = (unsigned char*) strrchr((char*)buf, '"'); /* REVISIT: find last " */
    if (cp == NULL)
      cp = buf+1;
    cp = (unsigned char*) strchr((char*)cp+1, ' ');
    if (cp != NULL) {
      /* stuff on the end */
      *cp = '\0';
      cp++;
      SPANBLANK(cp);
      if (*cp != '\0') {
	/* We could deal with ESMTP SIZE here. If so it's either
	 * OK or bogus, in which we have to return 555. 
	 *  
	 * Without ESMTP. this is crud on the end, and we give 501
	 */
	writereply(replybuf, 501, 0, m501msg, NULL);
	state_change(state, MAIL, ERROR);
	return;
      }
    }
    current_from_mailpath = xstrdup((char*)smtp_cleanitup(buf));
       
    if (strcmp((char*)buf, current_from_mailpath) != 0) {
      	syslog(LLEV,"securityalert: suspicious characters in FROM: address from host  %.128s/%.128s, exiting",
	       proxy_stats.rladdr, proxy_stats.riaddr);
	smtp_exit(EX_PROTOCOL);
    }
    if (matches_spam_db(current_from_mailpath,proxy_stats.rladdr,
			proxy_stats.riaddr,proxy_confp)) {
	syslog(LLEV,"spam ignored address=%s host=%.512s/%.20s (DB)",
		    current_from_mailpath,proxy_stats.rladdr,
		    proxy_stats.riaddr);
	state_change(state, MAIL, FAILURE);
	return;
    }
    if (!valid_sender(current_from_mailpath,proxy_confp)) {
	syslog(LLEV,"sender's domain does not have an NS record: from=%.512s host=%.512s/%.20s",
		    current_from_mailpath,proxy_stats.rladdr,
		    proxy_stats.riaddr);
	state_change(state, MAIL, FAILURE);
	return;
    }
    writereply(replybuf, 250, 0,
	       "sender is ",
	       current_from_mailpath,
	       ", ",
	       m250fromok,
	       NULL);

    /*
     * log the connection 
     */
    if (VerboseSyslog) {
      syslog(LLEV, "mail from %s", current_from_mailpath);
    } else {
      accumlog(LLEV, " from=%s", current_from_mailpath);
    }

    /*
     * put our output in the outbuf 
     */
    if (write_smtp_mbuf(outbuf, "FROM ", strlen("FROM ")) &&
	write_smtp_mbuf(outbuf, current_from_mailpath,
			strlen(current_from_mailpath)) &&
	write_smtp_mbuf(outbuf, "\n", 1)) {
      state_change(state, MAIL, SUCCESS);
    } else {
      state_change(state, MAIL, FAILURE);
    }
  } else if (strcasecmp((char*)verb, "RCPT") == 0) {
    char *victim;
    int badrcpt=0;

    if (!cmd_ok(RCPT, state)) {
      writereply(replybuf, 554, 0, m554nofrom, NULL);
      state_change(state, RCPT, ERROR);
      return;
    }
    /*
     * at this point I shouldn't have anything bigger than a return *
     * address and a RCPT: left. 
     */
    if (strlen((char*)buf) > SMTP_MAX_MAILPATH + 1) {
      /*
       * someone gave us a *big* name for themselves. * draw them to 
       * our attention, and fail. 
       */
      syslog(LLEV,
	     "securityalert: more than %d bytes on RCPT from address %.128s/%.128s",
	     SMTP_MAX_MAILPATH, proxy_stats.rladdr, proxy_stats.riaddr);
      state_change(state, RCPT, FAILURE);
      return;
    }
    SPANBLANK(buf);
    if ((strlen((char*)buf) < 3) || strncasecmp((char*)buf, "TO:", 3) != 0) {
      writereply(replybuf, 501, 0, m501msg, NULL);
      state_change(state, RCPT, ERROR);
      return;
    }
    buf += 3;
    SPANBLANK(buf);
    /*
     * <sjg> if local-part contains ", then spaces are allowed
     */
    cp = NULL;
    if (buf[0] == '"' || buf[1] == '"')
      cp = (unsigned char*) strrchr((char*)buf, '"'); /* REVISIT: find last " */
    if (cp == NULL)
      cp = buf;
    cp = (unsigned char*) strchr((char*) cp+1, ' ');
    if (cp != NULL) {
      /* stuff on the end */
      *cp = '\0';
      cp++;
      SPANBLANK(cp);
      if (*cp != '\0') {
	/*  
	 * Without ESMTP. this is crud on the end, and we give 501
	 */
	writereply(replybuf, 501, 0, m501msg, NULL);
	state_change(state, RCPT, ERROR);
	return;
      }
    }
    victim = xstrdup((char*)smtp_cleanitup(buf));
    if (strcmp((char*)buf, victim) != 0) {
      	syslog(LLEV, "securityalert: suspicious characters in RCPT: address from host %.128s/%.128s, exiting",
	     proxy_stats.rladdr, proxy_stats.riaddr);
	smtp_exit(EX_PROTOCOL);
    }
    if (maxrecip && (++currecip > maxrecip)) {
     writereply(replybuf, 550, 0, "too many recipients", NULL);
     syslog(LLEV,"exiting - too many recipients");
     smtp_exit(1);
    }
    if (!badrcpt) {
     writereply(replybuf, 250, 0,
		 "recipient ",
		 victim,
		 ", ",
		 m250rcptok,
		 NULL);
      
      /*
       * log the recipient.
       */
      if (VerboseSyslog) {
	syslog(LLEV, "Recipient %s", victim);
      } else {
	accumlog(LLEV, " to=%s",  victim);
      }	
      if (write_smtp_mbuf(outbuf, "RCPT ", strlen("RCPT ")) &&
	  write_smtp_mbuf(outbuf, victim, strlen(victim)) &&
	  write_smtp_mbuf(outbuf, "\n", 1)) {
	state_change(state, RCPT, SUCCESS);
      } else {
	state_change(state, RCPT, FAILURE);
      }
    }
    free(victim);
  } else if (strcasecmp((char*)verb, "NOOP") == 0) {
    writereply(replybuf, 250, 0, m250msg, NULL);
    state_change(state, NOOP, SUCCESS);
  } else if (strcasecmp((char*)verb, "VRFY") == 0) {
    writereply(replybuf, 252, 0, m252msg, NULL);
    state_change(state, NOOP, SUCCESS);
  } else if (strcasecmp((char*)verb, "HELP") == 0) {
    writereply(replybuf, 214, 1, "Commands", NULL);
    writereply(replybuf, 214, 1, "HELO    MAIL    RCPT    DATA    RSET", NULL);
    writereply(replybuf, 214, 0, "NOOP    QUIT    HELP    VRFY    EXPN", NULL);
    state_change(state, NOOP, SUCCESS);
  } else if (strcasecmp((char*)verb, "ONEX") == 0) {
    writereply(replybuf, 200, 0, "Only one transaction", NULL);
    state_change(state, NOOP, SUCCESS);
  } else if (strcasecmp((char*)verb, "VERB") == 0) {
    writereply(replybuf, 200, 0, "Verbose mode", NULL);
    state_change(state, NOOP, SUCCESS);
  } else if (strcasecmp((char*)verb, "DEBU") == 0) {
    syslog(LLEV,
	       "securityalert: wizard command attempted from address %.128s/%.128s, name %.256s",
	   proxy_stats.rladdr, proxy_stats.riaddr, client_claimed_name);
    writereply(replybuf, 250, 0, m250msg, NULL);
    state_change(state, NOOP, SUCCESS);
  } else if (strcasecmp((char*)verb, "RSET") == 0) {
    writereply(replybuf, 250, 0, m250msg, NULL);
    state_change(state, RSET, SUCCESS);
  } else if (strcasecmp((char*)verb, "QUIT") == 0) {
    writereply(replybuf, 221, 0, m221msg, NULL);
    state_change(state, QUIT, SUCCESS);
  } else if (strcasecmp((char*)verb, "DATA") == 0) {
    if (cmd_ok(DATA, state)) {
      writereply(replybuf, 354, 0, m354msg, NULL);
      state_change(state, SNARF_DATA, SUCCESS);
    } else {
      writereply(replybuf, 554, 0, m554norcpt, NULL);
      state_change(state, SNARF_DATA, ERROR);
    }
#if EHLO_KLUDGE
  } else if (strcasecmp((char*)verb, "EHLO") == 0) {
    writereply(replybuf, 500, 0, m500msg, NULL);
    state_change(state, EHLO, SUCCESS);
#endif
  } else {
    /*
     * if we get here our verb don't look like a verb. this means that
     * we should fire off a syntax error to the client
     */
    writereply(replybuf, 500, 0, m500msg, NULL);
    state_change(state, UNKNOWN, ERROR);
  }
  return;
}

/*
 * Read a message body.
 * return values: * 1 - everything OK * 2 - read failed or connection
 * died before we got it all * 3 - size exceeded * 4 - no space on
 * write device * 5 - not enough memory 
 */

int
snarfdata(int in, int out, long *size, int bin)
{
  struct smtp_mbuf *buf;
  struct smtp_mbuf *outbuf;
  int snarfed;
  int dot = 0;
  int i;
  long max, outbytes;
  int body = 0;

  /*
   * initial message size 
   */
  max = (*size ? *size : LONG_MAX);


  /*
   * Initialize the smtp_mbuf's.
   * We start of with absurdly small sizes in order to ensure that the
   * code which grows an mbuf gets exercised (i.e. if it is broken then
   * the program will probably die and the bug will (hopefully) get fixed).
   */

  buf = alloc_smtp_mbuf(1024);
  if (buf == NULL) {
    syslog(LLEV, "Couldn't allocate input buffer for data command");
    return (5);
  }
  outbuf = alloc_smtp_mbuf(1024);
  if (outbuf == NULL) {
    syslog(LLEV, "Couldn't allocate output buffer for data command");
    return (5);
  }
  outbytes = 0;
  while (1) {
    int linestart;
    int lineend;

    linestart = lineend = 0;

    snarfed = read_smtp_mbuf(buf, in, 1024);
    if (snarfed < 0) {
      if (VerboseSyslog) {	
	syslog(LLEV, "read error receiving message body: %s", strerror(errno));
      } else {
	accumlog(LLEV, " read error receiving message body: %s",
		 strerror(errno));
      }
      return (2);
    }
    if (snarfed == 0) {
      if (VerboseSyslog) {      
	syslog(LLEV, "EOF while receiving message body");
      } else {
	accumlog(LLEV, " EOF while receiving message body");
      }
      return (2);
    }
    if (outbuf->size < buf->size) {
      if (grow_smtp_mbuf(outbuf, (buf->size - outbuf->size)) == 0) {
	syslog(LLEV, "Couldn't grow #1");
	return (5);
      }
    }
    for (i = 0; i < buf->offset; i++) {
      switch (buf->data[i]) {
      case LF:
	/*
	 * we got an LF 
	 */
	if (dot == 1) {
	  /*
	   * Lonesome Dot sings: "We're done!" 
	   */
	  *size = outbytes;
	  return (1);
	  break;
	} else {
	  /*
	   * write out from linestart to lineend (inclusive) 
	   */
	  buf->data[lineend] = LF;	/*
					 * I must at least write
					 * out this LF 
					 */
	  /*
	   * check for unusual headers.
	   * these form the basis of a number of more interesting attacks
	   * 	- Julian Assange <proff@suburbia.net>
	   */
	  if (!body) {
	    	if (buf->data[linestart] == LF ||
	    	    (buf->data[linestart] == '\r' && buf->data[linestart+1] == LF))
                {
			body = 1;
                } else
		{
			char *p;
		        int off=0;
			int unprintable = 0;
			for (p=(char*)buf->data+linestart; *p != LF; p++)
			{
				if (!isalpha(*p) && !isprint(*p) &&
				    !isspace(*p) && ((*p & 0x7f) < 0x20))
				{
				        syslog(LOG_DEBUG, "Unprintable character value=%d in message header at offset %d", (int)*p, off);
					*p='?';
					unprintable++;
				}
		                off++;
			}
			if (unprintable)
			{
			    buf->data[lineend]='\0';
			    syslog(LLEV, "%d unprintable characters in \"%.255s\"", unprintable, buf->data+linestart);
			    buf->data[lineend]=LF;
			    syslog(LLEV, "securityalert: abandoning session from %.128s/%.128s) due to unprintable message header",
			           proxy_stats.rladdr,proxy_stats.riaddr);
			    smtp_exit(EX_PROTOCOL);
	                }
			if (lineend - linestart > maxheader) {
			    syslog(LLEV, "securityalert: unusually long header (trucated) [%d bytes] = \"%.255s\"...", lineend-linestart, buf->data+linestart);
			    buf->data[linestart+maxheader]=LF;
			    lineend = linestart + maxheader;
			}
			if (((lineend - linestart) > 7) && 
			    !strncmp((char*)buf->data+linestart,"From: ",6)) {
				char envfrom[1024];
				strncpy(envfrom,(char*)buf->data+linestart+6, 
					lineend - (linestart+6));
				envfrom[lineend - (linestart+6)] = '\0';
			}
		}
	  }
	  while (linestart <= lineend) {
	    int j;
	    j = write(out, (buf->data) + linestart,
		      ((lineend - linestart) + 1));
	    if (j < 0) {
	      /*
	       * we can't write to the out fd. return
	       * indicating that.
	       */
	      syslog(LLEV, "fwtksyserr: write failed to spoolfile! (%s)", strerror(errno));
	      return (4);
	    } else if (j == 0) {
	      syslog(LLEV, "fwtksyserr: zero length write to file - bye!");
	      exit(EX_CONFIG);
	    }
	    outbytes += j;
	    linestart += j;
	    if (outbytes >= max) {
	      /*
	       * we've blown over our maxsize limit. 
	       */
	      syslog(LLEV,
		     "Message body exceeds maximum size of %ld", max);
	      return (3);
	    }
	  }
	}
	dot = 0;
	linestart = i + 1;
	lineend = i + 1;
	break;
      case CR:
	/*
	 * we got a CR. if it's at the end of a line, (right
	 * before an LF), we ignore it, and it goes away. Any
	 * other character will advance lineend past it, meaning
	 * it gets picked up as data. Dot is also unchanged
	 * since this could be the start of a crlf after we saw
	 * a first character dot. The next character will bring
	 * enlightenment. 
	 */
	break;
      case '.':
	if (i == (linestart)) {
	  if (dot == 0) {
	    /*
	     * this is a dot at start of line. It could mean
	     * we're finished. We're either finished, or we
	     * do not replicate this first dot in the output.
	     * (RFC 821, 4.5.2) 
	     */
	    dot = 1;
	    /*
	     * if we aren't finished, then this dot can't
	     * appear, so increment linestart by one 
	     */
	    linestart++;
	  } else {
	    /*
	     * this is a second dot, after we saw one last
	     * time and moved linestart. This one stays, and 
	     * this ain't Lonsesome Dot 
	     */
	    dot = 0;
	  }
	} else {
	  /*
	   * this is a plain ordinary dot with no pretensions,
	   * it's like any other character. Clear dot in order 
	   * to properly handle ".\r." case at the start of a
	   * line (i.e ".\r." is NOT the same as ".."  even if
	   * it appears at the start of a line). 
	   */
	  dot = 0;
	}
	lineend = i + 1;
	break;
      default:
	dot = 0;
	lineend = i + 1;
	break;
      }
    }
    /*
     * we had part of a line left in the buffer. Keep it and throw
     * away the rest. 
     */
    clean_smtp_mbuf(buf, linestart);
  }
}

/*
 * The brains of this operation
 */

int
main(int argc, char **argv)
{
  int i, k;
  smtp_state_set last_state_s, current_state_s;	 /* The real state vector. */
  smtp_state last_state, current_state; /* Pointers to the state vector. */

  struct sigaction new_sa;
  Cfg	*cfp;
  socklen_t slen;
  struct hostent *tmp_he;

  proxy_init(argc,argv);
  proxy_update_status();
  if (dscp)
	proxy_set_dscp(0,dscp);

  if ((cfp = proxy_conf_hosts(proxy_confp,proxy_stats.rladdr,
			   proxy_stats.riaddr)))
	proxy_parse_options(cfp,options);
  else {
	printf("550 Access denied\r\n");
	exit(1);
  }

/*
 *	998 is rfc2822 recommended limit for header line length
 */

  maxsize = proxy_conf_int(proxy_confp,"maxbytes", 1, INT_MAX, INT_MAX);
  maxheader = proxy_conf_int(proxy_confp,"maxheaderline",128,1023,998);
  maxrecip = proxy_conf_int(proxy_confp,"maxrecip",1,10000,2048);

  if ((proxy_uid == 0) || (proxy_uid == -1)) {
    syslog(LLEV, "fwtkcfgerr: sorry, I don't want to run as root! It's a bad idea!");
    exit(EX_CONFIG);
  }
  if ((proxy_gid == 0) || (proxy_gid == -1)) {
    syslog(LLEV, "fwtkcfgerr: sorry, I don't want to run as group 0. It's a bad idea!");
    exit(EX_CONFIG);
  }
      
  if (!*proxy_chroot) {
    syslog(LLEV,"fwtkcfgerr: no directory specified");
    abort();
  }

  proxy_chroot_setugid();

  if (spooldir == NULL) {
    syslog(LLEV, "fwtkcfgerr: NULL spool directory! Aborting.");
    abort();
  }

  /* We need to ignore SIGPIPE */
#ifdef BSD_SIGNAL
  signal(SIGPIPE, SIG_IGN);
#else
  memset(&new_sa, 0, sizeof(new_sa));
  new_sa.sa_handler = SIG_IGN;
  (void)sigemptyset(&new_sa.sa_mask);
  new_sa.sa_flags = SA_RESTART;
  if ( sigaction( SIGPIPE, &new_sa, NULL ) != 0 ) {
    syslog(LLEV,"fwtksyserr: CRITICAL - sigaction failed (%s)", strerror(errno));
    exit(EX_OSERR);
  }
#endif

  /*
   * Who's on the other end of this line?
   */


  /*
   * set who we are in case our caller didn't tell us to be someone
   * else 
   */

  slen = sizeof(my_sa);
  if (getsockname(0, (struct sockaddr *) &my_sa, &slen) != 0) {
      syslog(LLEV, "fwtksyserr: getsockname failed (%s) Who am i?", strerror(errno));
      exit(EX_OSERR);
  }
  if (my_clean_reverse_name == NULL) {
      tmp_he = gethostbyaddr((char *) &(my_sa.sin_addr.s_addr),
			     sizeof(my_sa.sin_addr.s_addr),
			     AF_INET);
      if (tmp_he != NULL) {
	my_clean_reverse_name = xstrdup((char*)cleanitup((unsigned char*)tmp_he->h_name));
	if (strcmp(tmp_he->h_name, my_clean_reverse_name) != 0) {
	  syslog(LLEV, "fwtkcfgerr: CRITICAL - Suspicious characters in MY hostname! (for ip=%s) cleaned to %s.\n", proxy_stats.riaddr, my_clean_reverse_name);
	  syslog(LLEV, "CRITICAL - YOUR DNS IS EITHER COMPROMISED OR MISCONFIGURED! INVESTIGATE!");
	  smtp_exit(EX_CONFIG);
	}
      }
  }

  if (my_clean_reverse_name == NULL) {
    /*
     * Our caller didn't say who we're gonna claim to be, and we
     * didn't get one from a getsockname. get our hostname and use
     * that. 
     */
    char hname[MAXHOSTNAMELEN];
    struct hostent *hp;

    if (gethostname(hname, sizeof hname) != 0) {
      syslog(LLEV, "fwtksyserr: gethostname() call failed! (%s) Who am I?", strerror(errno));
      exit(EX_OSERR);
    }
    if ((hp = gethostbyname(hname)) != NULL) {
      my_clean_reverse_name = xstrdup(hp->h_name);
    } else {
      my_clean_reverse_name = xstrdup(hname);
    }
  }
  /*
   * Allocate the mbuf's - start off small to ensure that 'grow mbuf'
   * code gets exercised (detects bugs faster). 
   */
  input_buf = alloc_smtp_mbuf(64);
  if (input_buf == NULL) {
    syslog(LLEV, "fwtksyserr: malloc failed, abandoning session.");
    exit(EX_OSERR);
  }
  output_buf = alloc_smtp_mbuf(64);
  if (output_buf == NULL) {
    syslog(LLEV, "fwtksyserr: malloc failed, abandoning session.");
    exit(EX_OSERR);
  }
  reply_buf = alloc_smtp_mbuf(64);
  if (reply_buf == NULL) {
    syslog(LLEV, "fwtksyserr: malloc failed, abandoning session.");
    exit(EX_OSERR);
  }
  last_state = &last_state_s;
  current_state = &current_state_s;

  zap_state(current_state);
  zap_state(last_state);

  writereply(reply_buf, 220, 0,
	     my_clean_reverse_name,
	     " ",
	     m220msg,
	     NULL);

  replyfd = 1;
  for (;;) {
    char *line;
    size_t offset;
    time_t tt;

    flush_smtp_mbuf(reply_buf, replyfd, reply_buf->offset);
    k = read_smtp_mbuf(input_buf, 0, 1024);
    if (k < 0) {
      syslog(LLEV, "Read failed from client fd (%s) - Abandoning session", strerror(errno));
      smtp_exit(EX_OSERR);
    } else if (k == 0) {
      /*
       * eof 
       */
      if (VerboseSyslog) {
	syslog(LLEV, "EOF on client fd.  At least they could say goodbye!");
      } else {
	accumlog(LLEV, "EOF on client fd.");
      }
      smtp_exit(EX_OSERR);
    }
    offset = 0;
    line = smtp_get_line(input_buf, &offset);
    while (line != NULL) {
      clean_smtp_mbuf(input_buf, offset);
      offset = 0;
      memcpy(last_state, current_state, sizeof(smtp_state_set));
      smtp_parse_cmd((unsigned char*) line, output_buf, reply_buf, current_state);
      flush_smtp_mbuf(reply_buf, replyfd, reply_buf->offset);
      if (sane_state(current_state)) {
	if ((!test_state(SNARF_DATA, last_state)) &&
	    (test_state(SNARF_DATA, current_state))) {
	  char head[512];
	  char *foo;
	  long msize =0;

	  memset(head, 0, sizeof(head));

	  outfd = smtp_open_spoolfile();
	  time(&tt);
	  strlcpy(head, "BODY\nReceived: from ", 512);
	  strlcat(head, proxy_stats.rladdr, 512);
	  strlcat(head, "[", 512);
	  strlcat(head, proxy_stats.riaddr, 512);
	  if (strcasecmp(proxy_stats.rladdr, client_claimed_name) == 0) {
	    strlcat(head, "]", 512);
	  } else {
	    strlcat(head, "], claiming to be \"", 512);
	    strlcat(head, client_claimed_name, 512);
	    strlcat(head, "\"", 512);
	  }
	  strlcat(head, " via smtpd by ", 512);
	  strlcat(head, my_clean_reverse_name, 512);
	  strlcat(head, "\n\tid ", 512);
	  if ((foo = strrchr(spoolfile, '/')) != NULL) {
	    strlcat(head, foo + 1, 512);
	  } else {
	    strlcat(head, spoolfile, 512);
	  }
	  strlcat(head, "; ", 512);
	  strlcat(head, ctime(&tt), 512);
	  if (!write_smtp_mbuf(output_buf, head, strlen(head))) {
	    syslog(LLEV, "fwtksyserr: couldn't write to output buffer, abandoning session");
	    smtp_exit(EX_OSERR);
	  }
	  flush_smtp_mbuf(output_buf, outfd, output_buf->offset);
	  msize = maxsize;
	  i = snarfdata(0, outfd, &msize, 0);
	  proxy_stats.outbytes += msize;
	  proxy_update_status();
	  switch (i) {
	  case 1:
	    /*
	     * success 
	     */
	    smtp_close_spoolfile(outfd);
	    writereply(reply_buf, 250, 0, m250gotit, NULL);
	    flush_smtp_mbuf(reply_buf, replyfd, reply_buf->offset);
	    if (VerboseSyslog) {
	      syslog(LLEV, "Received %ld bytes of message body from %.128s/%.128s",
		     msize, proxy_stats.rladdr,
		     proxy_stats.riaddr);
	    } else {
	      accumlog(LLEV, " bytes=%ld", msize);
	      accumlog(LLEV, 0);	/* flush */
 	    }	    
	    clear_state(SNARF_DATA, current_state);
	    clear_state(OK_RCPT, current_state);
	    clear_state(OK_MAIL, current_state);
	    break;
	  case 2:
	    /*
	     * read failure on input, or something horrific 
	     */
	    writereply(reply_buf, 554, 0, m554msg, NULL);
	    flush_smtp_mbuf(reply_buf, replyfd, reply_buf->offset);
	    smtp_nuke_spoolfile(outfd);
	    clear_state(SNARF_DATA, current_state);
	    clear_state(OK_RCPT, current_state);
	    clear_state(OK_MAIL, current_state);
	    break;
	  case 3:
	    /*
	     * maxsize exceeded 
	     */
	    writereply(reply_buf, 552, 0, m552msg, NULL);
	    flush_smtp_mbuf(reply_buf, replyfd, reply_buf->offset);
	    smtp_nuke_spoolfile(outfd);
	    clear_state(SNARF_DATA, current_state);
	    clear_state(OK_RCPT, current_state);
	    clear_state(OK_MAIL, current_state);
	    break;
	  case 4:
	    /*
	     * No room on spool device 
	     */
	    writereply(reply_buf, 452, 0, m452msg, NULL);
	    flush_smtp_mbuf(reply_buf, replyfd, reply_buf->offset);
	    smtp_nuke_spoolfile(outfd);
	    clear_state(SNARF_DATA, current_state);
	    clear_state(OK_RCPT, current_state);
	    clear_state(OK_MAIL, current_state);
	    break;
	  case 5:
	    /*
	     * malloc barfed 
	     */
	    writereply(reply_buf, 452, 0, m452msg, NULL);
	    flush_smtp_mbuf(reply_buf, replyfd, reply_buf->offset);
	    smtp_nuke_spoolfile(outfd);
	    clear_state(SNARF_DATA, current_state);
	    clear_state(OK_RCPT, current_state);
	    clear_state(OK_MAIL, current_state);
	    break;
	  default:
	    /*
	     * muy trabajo 
	     */
	    writereply(reply_buf, 451, 0, m451msg, NULL);
	    flush_smtp_mbuf(reply_buf, replyfd, reply_buf->offset);
	    smtp_nuke_spoolfile(outfd);
	    smtp_exit(EX_SOFTWARE);
	  }
	}
      } else {
	/*
	 * evil state. 
	 */
	syslog(LLEV, "fwtksyserr: CRITICAL - bad state, aborting");
	abort();
      }
      line = smtp_get_line(input_buf, &offset);
    }
  }
}
