#include <sys/param.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <sys/wait.h>
#include <sys/utsname.h>
#include <time.h>
#include "r.h"
#include "s.h"
#include "h.h"
#include "antibadmail.h"

static
char myid[] = "$Id: antibadmail.c,v 1.43 2016/01/18 22:23:50 yuuji Exp $";
static char *argv0;

#ifdef SYSLOG
#include <syslog.h>
#ifndef LOG_FACI
#define LOG_FACI	LOG_LOCAL1
#endif
#endif


/*
 * References:
 *  res_query:		http://www.bigfield.com/~hiroshi/mxapi2.html
 *  SMTP Reply Code:	http://www.puni.net/~mimori/smtp/reply.html
 *  Enhanced Mail System Status Codes
 *			http://www.puni.net/~mimori/rfc/rfc1893.txt
 *  SMTP-AUTH		http://www.faqs.org/rfcs/rfc2554.html 
 */

static char *controldir = CONTROLDIR;
static char *helo = NULL, *mailfrom = NULL, *rcptto = NULL;
static char *tcpremotehost=NULL, *tcpremoteip=NULL, *tcplocalhost=NULL;
static char *relayclient;
static int rcpttocount = 0;
static int cont_3xx = 0;
static int keepwaiting = 0;
static char *lastcmd = "";
static int rejectit;
static int withqmail = 0;
static int headercheck = 1;
static int relaycheck = 1;

/* Pointers to SMTP command args.  3 is enough */
static STR smtparg0 = {0}, smtparg1 = {0}, smtparg2 = {0};
static STR qualified_request = {0};

/* global file descriptors */
static int fd_daemon;

/* global error code holder for header inspection */
static HMR hresult;

/*
 * Convert rcsid to "NAME VERSION"
 */
char *myname()
{
  char *e = strstr(myid, ".c,v");
  char *s, *t;
  if (e) {
    t = myid, s=myid+5;
    while (*s && s<e)
      *t++ = *s++;
    s = e+4;
    *t++ = *s++;
    while (*s && !isspace((int)*s))
      *t++ = *s++;
    *t = '\0';
  }
  return myid;
}

/*
 * Check if the character CH is safe in SMTP header
 */
int issafe(int ch)
{
  if (isalpha(ch))  return 1;
  if (isdigit(ch))  return 1;
  if (strchr(".@%+/=:-#[]", ch)) return 1;
  if (strchr("_", ch)) return 1;
  if (strchr("\"", ch)) return 1; /* for quoted string */
  return 0;
}

/*
 * Translate unsafe chars to '?'
 */
char *safestr(char *str)
{
  char *p;
  for (p=str; *p; p++) {
    if (!issafe(*p))
      *p = UNSAFEMARK;
  }
  return str;
}

/* SMTP out message MSG with CR+LF */
void smtpout(char *msg)
{
  write(1, msg, strlen(msg));
  //write(1, "\r\n", 2);
}

void finalize()
{
  int st;
#ifdef SYSLOG
  closelog();
#endif
  wait(&st);
}

void bye(int exitcode)
{
  finalize();
  exit(exitcode);
}

void smtpquit(char *msg)
{
  smtpout(msg);
  close(1);
  bye(1);
}

/* Abort with error message.
 *
 */
void smtpabort(char *msg)
{
  char *quit = "quit\r\n";
  write(fd_daemon, quit, strlen(quit));
  close(fd_daemon);
  write(1, "421 ", 4);
  smtpquit(msg);
}

void die_nomem() {smtpabort("Out of memory\r\n");}

/* After freeing allocated pointer OLD, copy string STR to newly
 * allocated area and return its pointer value.
 */
char* copystr(char *old, char *str)
{
  void *mem;
  if (old)
    free(old);
  mem = malloc(1+strlen(str));
  if (NULL == mem)
    die_nomem();
  memcpy(mem, str, 1+strlen(str));
  return (char*)mem;
}

/*
 * Like copystr(), copy substring from start upto end into newly allocated
 * area.
 */
char *copysubstr(char *old, char *start, char *end)
{
  void *mem;
  if (old)
    free(old);
  mem = malloc(1+end-start);
  if (NULL == mem)
    die_nomem();
  strncpy(mem, start, end-start);
  *(char*)(mem+(end-start)) = '\0';
  return (char*)mem;
}

void initialize()
{
  char *hc = "HEADERCHECK";
  struct utsname u;
#ifdef SYSLOG
  openlog("antibadmail", LOG_PID, LOG_FACI);
#endif
  if (!(tcpremotehost=getenv("TCPREMOTEHOST")))
    tcpremotehost = copystr(tcpremotehost, "unknown");
  if (!(tcpremoteip=getenv("TCPREMOTEIP")))
    tcpremoteip = copystr(tcpremoteip, "unknown-address");
  if (!(tcplocalhost=getenv("TCPLOCALHOST"))) {
    uname(&u);
    tcplocalhost = copystr(tcplocalhost, u.nodename);
  }
  if (getenv(hc))
    headercheck = atoi(getenv(hc));
  relayclient = getenv("RELAYCLIENT");
  if (getenv("CONTROLDIR"))
    controldir=getenv("CONTROLDIR");
  keepwaiting = 1;     /* Keep client waiting until greeting msg sent */
}

/*
 * Return true if environment variable ENV is set and it is not equal to "0"
 */
int getenvopt(char *env)
{
  char *e = getenv(env);
  if (e && strcmp("0", e))
    return 1;
  return 0;
}

/*
 * Return true if client is reliable.
 */
int isreliable()
{
  return (relayclient || getenvopt("RELIABLECLIENT"));

          /* || (reason("match", "GOOD") && !reason("match", "(S)"))) */
}

char* reason(char const *job, char *arg)
{
  /* Rejection reason.  Reason is also used as rejection flag */
  static STR reason = {0};
  switch (job[0]) {
  case 'a': case 'A':           /* APPEND */
    if (reason.s[0]) {
      if (!strop(&reason, "+", "/")) die_nomem();
      if (!strop(&reason, "+", arg)) die_nomem();
      
    } else {
      if (!strop(&reason, "=", arg)) die_nomem();
    }
    break;
  case 'd': case 'D':           /* DELETE */
    if (!strop(&reason, "delete", arg)) die_nomem();
    if (!strop(&reason, "replace", "//", "/")) die_nomem();
    break;
  case 'm': case 'M':           /* MATCH */
    return strstr(reason.s, arg);
  case 'r': case 'R':           /* RESET */
    strop(&reason, "=", "");    /* REPLACE */
    break;
  case 'g': case 'G':           /* GET */
    return reason.s;
  case 's': case 'S':           /* SET */
    if (!strop(&reason, "=", arg)) die_nomem();
    break;
  }
  return NULL;
}
char *getreason()
{
  return reason("get", "");
}

int isbad()
{
  char *r = getreason();
  if (r[0]) {
    if (strstr(r, "GOOD"))
      if (strstr(r, "(S)"))
        return 1;
      else
        return 0;
    else
      return 1;
  }
  return 0;
}

int isgood()
{
  return !isbad();
}

char* do_rcptlist(char *op, ...)
{
  static STR rcptlist = {0};
  char *arg;
  char *rc = NULL;
  va_list ap;
  va_start(ap, op);
  switch (op[0]) {
  case 'r': case 'R':           /* RESET */
    rc = strop(&rcptlist, "=", "");
    break;
  case '+': case 'A': case 'a': /* ADD */
    arg = va_arg(ap, char*);
    if (rcptlist.s && rcptlist.s[0]
        && rcptlist.s[rcptlist.len-1] != LOGRCPTDELIM[0])
      strop(&rcptlist, "+", LOGRCPTDELIM);
    rc = strop(&rcptlist, "join", arg, LOGRCPTDELIM, NULL);
    break;
  case 'c': case 'C':           /* chomp */
    if (LOGRCPTDELIM[0] == rcptlist.s[rcptlist.len-1])
      strop(&rcptlist, "chop");   /* chop trailing delimiter */
    rc = rcptlist.s;
    break;
  case 'g': case 'G':           /* get */
    if (rcptlist.s) do_rcptlist("chomp");
    rc = rcptlist.s;
    break;
  }
  va_end(ap);
  return rc;
}
static
char* rcptlist()
{
  return do_rcptlist("get");
}
#ifdef SYSLOG
  void (*prtlog)() = (void*)syslog;
#define TARGET	LOG_INFO
#else
  void (*prtlog)() = (void*)fprintf;
#define TARGET	stderr
#endif

void putlog(char *state)
{
  int reject = rejectit; /* isbad(); */
  static STR log = {0};
  char *r = getreason();
  char *nomem = "No memory space for log string";
#ifdef DEBUG
  fprintf(stderr, "reason=%s, reject=%d, r=%s\n", getreason(), reject, rcptto);
#endif 
  if (!getenv("LOGLOCAL") && !strcmp(tcpremoteip, "127.0.0.1"))
    return;                   /* Do not log connection from localhost */
  if (reason("match", "BY_SMTPD"))
    reject = 1;
  if (!rcptlist())
    return;                   /* Do nothing before rcptto is supplied */
  strop(&log, "=", "");
  if (!strop(&log, "join",
             (state&&state[0] ? state : (reject ? "Rejected:" : "Accepted:")),
             " helo=",		helo,
             ", host=",		tcpremotehost,
             ", remoteip=",	tcpremoteip,
             ", sender=",	(mailfrom==NULL ? "" : mailfrom),
             ", rcpt=",		rcptlist(),
             ", reliable=",	(isreliable() ? "yes" : "no"),
             NULL)) {
    prtlog(TARGET, "%s\n", nomem);
  } else {
    char *memo = getenv("MEMO");
    if (r[0]) {
      if (!strop(&log, "join", ", reason=", r, NULL))
        prtlog(TARGET, "%s\n", nomem);
    }
    if (memo && memo[0]) {
      if (!strop(&log, "join", ", memo=", memo, NULL))
        prtlog(TARGET, "%s\n", nomem);
    }
    prtlog(TARGET, "%s\n", log.s);
  }
}

/*
 * Return non-zero if the word is in dbdir
 *
 */
int strictmatch(char *dbdir, char *word)
{
  static char pathtmp[MAXPATHLEN];
  struct stat t;
  snprintf(pathtmp, sizeof pathtmp, "%s/%s/%s", controldir, dbdir, word);
  if (0 == stat(pathtmp, &t))
    return 1;
  return 0;
}


void sendtodaemon(char *msg)
{
  write(fd_daemon, msg, strlen(msg));
}

void sendtoclient(char *msg)
{
  write(1, msg, strlen(msg));
}

void abort_toolong()
{
  smtpabort("Too long SMTP command line.\r\n");
}


/* Check if (sub)patterns listed in DOMLIST matches with NAME.
 * If third argument STRICT is non-zero, no wildcard matching performed. */
int domainlistmatch(char *domlist, char *name, int strict)
{
  char *left, *right;
  char delimiter = '/';
  char *at = strchr(name, '@');
  char *dot;
#ifdef DEBUG
      char tmp[MAXLINE];
#endif
  if (at && at >= name+strlen(name)-1)
    at = NULL;                  /* if domainpart is too short, reset it */
  left = right = domlist;
  while (right && *right) {
    while (*right == '/')
      right++;                  /* Skip delimiter itself */
    left = right;
    while (*right && *right != delimiter)
      right++;
#ifdef DEBUG
    {
      /* snprintf(tmp, right-left+1, "%s", left); */
      bzero(tmp, sizeof tmp);
      memcpy(tmp, left, right-left);
      
      fprintf(stderr, "Compare with [%s]\n", tmp);
    }
#endif
    if (strlen(name)==right-left && !strncmp(name, left, right-left))
      return 1;                 /* matches with whole string */
    
    if (!strict) {
    
      if (at) {
        /* Compare with @domain */
        if (strlen(at)==right-left && !strncmp(at, left, right-left))
          {
#ifdef DEBUG
            snprintf(tmp, right-left+1, "%s", left);
            fprintf(stderr, "at match widh %s", tmp);
#endif
            return 1;
          }
      }
      dot = (at ? at : strchr(name, '.'));
      /* Compare with wild card matching */
      if (dot) {
        for (; *dot; dot++)
          if (*dot == '.')
            if (strlen(dot)==right-left && !strncmp(dot, left, right-left))
              {
#ifdef DEBUG
                snprintf(tmp, right-left+1, "%s", left);
                fprintf(stderr, "dot match widh %s(dot=%s)", tmp, dot);
#endif
                return 2;
              }
      }
    }
  }
  return 0;
}

/* helocheck's subprocedure */
int helo_domcheck(char *helohost)
{
  int rc=domaincheck(helohost);
  if (rc==DNS_SOFT) {
    reason("append", "HELO_DNS_SOFT");
  } else if (rc==DNS_HARD) {
    /* Most case comes here.  (maybe)Random string. */
    reason("append", "HELO_nxDOMAIN");
  }
  return rc ? 7 : 0;
}
/* check if all octets of IPSTR appear in STRING
 * 
 * Return 0 = IPSTR not found in STRING and IPSTR is valid
 *        1 = IPSTR is found in STRING
 *       -1 = IPSTR is null
 *       -2 = Some octet of IPSTR has more than 3 characters
 *       -3 = Some octet of IPSTR is larger than 255
 */
int ip4instring(char *ipstr, char *string, int skipdots)
{
  int i, oct;
  char *p, *q, octet[4], hex[3];
  if (!ipstr || !ipstr[0]) return -1;
  /* This is extremely simplified algorithm. */
  for (p=ipstr, i=0; i<4; i++, p=q+1) {
    q=p+1;
    while (*q && isdigit((int)*q)) q++;
    if (i < skipdots) continue;
    if (q-p > 3) return -2;     /* an octet too long */
    strncpy(octet, p, q-p);
    octet[q-p] = '\0';
    /*     fprintf(stderr, "Comparing with [%s]\n", octet); */
    if (!strstr(string, octet)) {
      /* If octet not found in STRING, try again with hex-string */
      if ((oct=atoi(octet)) > 255) return -3; /* too large number */
      snprintf(hex, 3, "%02x", oct);
      if (!strstr(string, hex)) return 0;
    }
  }
  fprintf(stderr, "%d octets found in HELO\n", i);
  return 1;
}

int ipinstring(char *ipstr, char *string, int skipdots)
{
  if (strchr(ipstr, ':')) {
    /* ip6instring */
    return 0;
  } else {
    return ip4instring(ipstr, string, skipdots);
  }
}

/* Check HELO/EHLO string
 * NOTE THAT This should be called after mailfromcheck because it depends on
 * 	     the result of $ACCEPTDOMAINS check.
 * Return 0 = Reasonable helo
 *        1 = Listed in badhelo (strict)
 *        2 = Listed in badhelo (wildcard)
 *        3 = Listed in badhelo (wildcard unkown)
 *        4 = no dots in helo
 *        5 = Invalid tld
 *        6 = IP address of HELO-string mismatch with client IP address
 *        7 = DNS lookup failure (for unknown)
 *        8 = IP-address-HELO from unkown host
 *        9 = IP-address digits are in HELO (Maybe dynamically allocated host)
 *       10 = IP-address digits are in remotehost & HELO is only domain name.
 * When returning >0, append_reason() appends the reason string to reason[].
 * (S) for reason string means SEVERE rejection reason which cannot be
 * reverted by GOOD* conditions.
 */
int helocheck(char *helohost)
{
  int rc;
  char *badhelodir = BADHELODIR;
  char *dot;
  char *goodhelo = getenv("GOODHELO");
  char *checkhelodomain = "CHECKHELODOMAIN";
  char *helodomain = checkhelodomain+5; /* is ptr to "HELODOMAIN" */
  int unknownhost = 0;
  int checkhelo = (getenvopt(checkhelodomain)||getenvopt("CHD"));
  /* 1st: Pass reliable clients */
  if (isreliable())
    return 0;
  
  unknownhost = !strcmp(tcpremotehost, "unknown");
  /* First, strict-match with goodhelo */
  if (goodhelo && (rc=domainlistmatch(goodhelo, helohost, 1))) {
    reason("append", "GOODHELO=");
    return 0;
  }
  if ((rc=strictmatch(badhelodir, helohost))) {
    reason("append", "HELO(S)");
    return 1;
  }
  /* Contains dot? */
  dot = strchr(helohost, '.');
  if (getenvopt("REJECTNODOTHELO") && !dot) {
    reason("append", "HELO_REJnoDOT");
    return 4;
  }
  if (!getenvopt("PASSKNOWNNODOTHELO") && !dot && unknownhost) {
    /* no-dot HELO from unknown host, reject it.
       If $PASSKNOWNNODOTHELO set, overlook it from known host. */
    reason("append", "HELO_noDOT");
    return 4;
  }
  /* loose-match with goodhelo */
  if (goodhelo && (rc=domainlistmatch(goodhelo, helohost, 0))) {
    reason("append", "GOODHELO");
    return 0;
  }
  /* HELO string must not be equal to recipient address */
  /* Wildcard match with badhelo db */
  if (dot)
    for (; *dot; dot++)
      if (*dot == '.')
        if (strictmatch(badhelodir, dot)) {
          reason("append", "HELO");
          return 1;
        }

  /* This section checks HELO host's validity  */
  /* RejectIPinHELO moved here 2015-08-19 */
  {
    /* Check if IP-address-like HELO contains his IP address digits */
    if (getenvopt("REJECTIPINHELO")) {
      int dots = atoi(getenv("REJECTIPINHELO"));
      if (0 < (rc=ipinstring(tcpremoteip, helohost, dots))) {
        if (!getenvopt("PERMIT_STATIC") || !strstr(helohost, "static")) {
          char h[12];
          snprintf(h, sizeof h, "IPINHELO(%c)", (dots%10)+'0');
          reason("append", h);
          return 9;
        }
      }
      /* Check if remotehost consists of IP digits
         and HELO string is provider name. */
      if (0 < (rc=ipinstring(tcpremoteip, tcpremotehost, dots))) {
        char *dotp = tcpremotehost;
        while (NULL != (dotp=strchr(dotp, '.'))) {
          dotp++;
          if (0 == strcasecmp(dotp, helohost)) {
            reason("append", "DYN_HELOPARENT");
            return 10;
          }
        }
      }
    }
  }
  {
    /* If HELOHOST is IP-address-like octets (or IPv6) */
    char heloip[MAXIPBYTES];
    memset(heloip, 0, sizeof heloip);
    if (parseipaddr(helohost, heloip)) {
      char remoteip[MAXIPBYTES];
      fputs("helo is ip\n", stderr);
      memset(remoteip, 0, sizeof remoteip);
      if (parseipaddr(tcpremoteip, remoteip)) {
        if (0 == memcmp(heloip, remoteip, sizeof remoteip)) {
          /* 
           * BE AWARE HERE
           * IP-address-HELO from unknown host is MOSTLY from spammer.
           * If you don't want to drop it, set ${PASSUNKOWNIPHELO}.
           */
          if (unknownhost && !getenvopt("PASSUNKOWNIPHELO")) {
            reason("append", "HELO_UNKNOWNIP");
            return 8;
          }
           
          /* OLD Position of REJECTIPINHELO */
          
          
          return 0;             /* IP address matches, GOOD! */
        } else {
          reason("append", "HELO_IPnomatch");
          return 6;             /* IP address mismatch, NG */
        }
      } else {
        /* should not come here */
        reason("append", "TCPREMOTEIP_unset..call_me_via_tcpserver");
        return -1;
        /* smtpabort("Unexpected condition error"); */
      }
    }
    {
      /* TLD can't have digits */
      char *p = strrchr(helohost, '.'); /* Move to last dot */
      if (p)
        for (p++; *p; p++)
          if (isdigit((int)*p)) {
            reason("append", "HELO_ngTLD");
            return 5;
          }
    }
    
    if (checkhelo) {
      /* When $CHECKHELODOMAIN set, check helo-domain's DNS record */
      if ((rc=helo_domcheck(helohost))) {
        reason("append", helodomain);
        return rc;
      }
    }
    /* Return whichever if it is known host */
    if (!unknownhost) {
      /* If remote host has correct PTR record, overlook it */
      if (strchr(helohost, '.'))
        return 0;
      return 4;
    }
    /* Unknown(no PTR record) host, check severely */
    {
      
    }
    {
      /* We can't handle helo string longer than MAXPATHLEN (dbdir) */
      static STR unkohelo;
      char *unko = "unknown";
      char *p = strchr(helohost, '.');
      int clen = strlen(controldir);
      /*
       * If client PTR is unset, check the wildcard entries in
       *     badhelodir/unknown/.<wildcard.domain>
       */
      for (; p && *p; p++)
        if (*p == '.')
          if (1+clen+strlen(unko)+strlen(p)+1 < MAXPATHLEN) {
            if (!strop(&unkohelo, "=", unko)) die_nomem();
            if (!strop(&unkohelo, "join", "/", p, NULL)) die_nomem();
            if ((rc=strictmatch(badhelodir, unkohelo.s))) {
              reason("append", "HELO_unknownbad");
              return 3;         
            }
          }
      /* end of for() */

      /* For unkown host, helo-string must be resolved */
      rc=helo_domcheck(helohost);
      if (rc) {
        reason("append", helodomain);
        return rc;
      }
      /* For unknownhost,
       *     A record of HELO matches with at least 3octets of ip-address. */
      /* not yet */
    }
  }
  
  return 0;
}

/* Check HELO/EHLO part II string comparing with RCPTTO string
 * This should be called after rcptto set.
 * Return 0 = no problem
 *        9 = matches with rcptto
 */
int helocheck2(char *helohost, char *rcpt)
{
  char *at;
  if (!*rcptto)
    return 0;
  if (!strcmp(helohost, rcpt)) {
    reason("append", "HELO=RCPT(S)");
    return 9;
  }
  at = strrchr(rcpt, '@');
  if (!at) return 0;

  if (strlen(helohost)==at-rcpt && !strncmp(helohost, rcpt, at-rcpt)) {
    reason("append", "HELO=RCPTlocal(S)");
    return 9;
  }
  if (isreliable()) return 0;
  /* Check if HELO equals to domain part */
  if (*(at+1) && !strcmp(helohost, at+1)) {
    reason("append", "HELO=RCPTdomain(S)");
    return 9;
  }
  return 0;
}

/* Check MAILFROM string
 * Return 0 = No Problem
 *        1 = Listed in badmailfrom (strict)
 *        2 = Listed in badmailfrom (wildcard)
 *        3 = No domain part
 *        4 = Invalid host
 *        5 = Not matches with $PASSONLY, if $PASSONLY set
 *        6 = Contains unsafe character(converted to ? by safestr())
 * When returning >0, reason("append", ) appends the reason string to reason[].
 * (S) for reason string means SEVERE rejection reason which cannot be
 * reverted by GOOD* conditions.
 */
int mailfromcheck(char *mailfrom)
{
  int rc, len = strlen(mailfrom), verp = 0;
  char *badmailfromdir = BADMAILFROMDIR;
  char *d, *goodmf, *passonly;
  if (strchr(mailfrom, UNSAFEMARK)) {
    reason("append", "MAILFROM_UNSAFE(S)");
    return 6;
  }
  /* Set up special permission from passing-through environments */
  passonly = getenv("PASSONLY");
  if (!passonly && getenv("ADONLY"))
    passonly = getenv("ACCEPTDOMAINS");
  goodmf = getenv("GOODMAILFROM");
  if (!goodmf && !getenv("ADONLY"))
    goodmf = getenv("ACCEPTDOMAINS");

  if (isreliable())
    return 0;
  if ('\0' == *mailfrom)        /* Pass null sender(for error mails) */
    return 0;
  if (goodmf && domainlistmatch(goodmf, mailfrom, 1)) {
    /* Strict-matching with $GOODMAILFROM precedes strict-matching with
       badmailfromdir */
    reason("append", "GOODMAILFROM=");
    return 0;
  }
  if ((rc=strictmatch(badmailfromdir, mailfrom))) {
    reason("append", "MAILFROM(S)");
    return 1;
  }
  if (0 == strcmp(mailfrom+len-4, "#@[]")) {
    return 0;                   /* Pass verp seed */
  }
  verp = (0 == strcmp(mailfrom+len-4, "-@[]"));
  d = (verp ? strrchr(mailfrom, '@') : strchr(mailfrom, '@'));
  if (!d) {
    reason("append", "MAILFROM_no@");
    return 3;
  }
  d++;                          /* Skip to next of `@' */
  if (!*d || (!getenvopt("NOMFDCHECK") && domaincheck(d))) {
    reason("append", "MAILFROM_nxDOMAIN");
    return 4;
  }
  /*
   * Begin wild card matching
   * 
   */
  /* First, allow free-mail mailfrom from valid servers  */
  /* $GOODMAILFROM and $PASSONLY are delimited list of acceptable mail domains.
     Typical values are as follows;
     
      @yahoo.com                : accepts only @yahoo.com domain
      .hotmail.com              : accepts any @*.hotmail.com
      @hotmail.com/.hotmail.com : accepts @hotmail.com and @*.hotmail.com

      multiple domain list is delimited by `/'.

     It would be good to put @hotmail.com and any major free-mail domain
     list in /var/qmail/control/badmailfrom file while setting
     GOODMAILFROM variable in tcpserver's rule file like this;
     
        =.hotmail.com:allow,GOODMAILFROM="@hotmail.com"

     With these settings, qmail-smtpd prevents any fake @hotmail.com.
     The 'true' hotmail.com's mail servers can send @hotmail.com messages.
  */
  
  /*
   * Consideration
   *
   * $GOODMAILFROM precedes @domain wild-carding,
   * whereas $PASSONLY does not precede @domain wild-carding.
   * But $PASSONLY precedes .domain wild-carding.
   *
   */
  
  /* $GOODMAILFROM is authorized MAIL FROM */
  if (domainlistmatch(goodmf, mailfrom, 0)) {
    reason("append", "GOODMAILFROM");
    return 0;
  }


  /* Compare with badmailfromdir as wild card
   * (1) compare with domain-part */
  /* d points to next char. of @ */
  if (strictmatch(badmailfromdir, d-1)) {
    reason("append", "MAILFROM_@");
    return 2;
  }

  /* $PASSONLY for forwarded mail from other smtp servers. */
  if (passonly) {
    /* If $PASSONLY set, reject all but those matches with $PASSONLY */
    if (domainlistmatch(passonly, mailfrom, 0)) {
      reason("append", "GOODPASS");
      return 0;
    }
    reason("append", "PASSONLY");
    return 5;
  }
  
  /* (2) compare with dot-after part for every component */
  for (; *d; d++)
    if (*d == '.')
      if (strictmatch(badmailfromdir, d)) {
        reason("append", "MAILFROM_.");
        return 2;
      }
  
  return 0;
}

/*
 * Check if the rcpient address matches with `soiled address'
 * Return  1: address is soiled (strict match address)
 *         2: address is soiled (wild card match address)
 *         0: address is not soiled
 */
int issoiled(char *rcpt)
{
  char *soiled = SOILEDRCPTTODIR;
  char *at = strrchr(rcpt, '@'), *e;
  static char *local = NULL;
  static STR tmp = {0}, domd = {0}, locp = {0};
  char dash = DASH;
  int isdomdir=0;
  if (!rcpt || !*rcpt || !at)
    return 0;
  /* Check with strict match */
  if (strictmatch(soiled, rcpt))
    return 1;

  local = copysubstr(local, rcpt, at);/* In failure, abort in copysubstr() */
  
  /* Check if the domain named directory exists in soiled-dir */
  if (!strop(&tmp, "=", at)) die_nomem();
  if (!strop(&tmp, "+", "/.")) die_nomem();
  isdomdir = strictmatch(soiled, tmp.s);
  
  /* Check if it matches with .qmail-default addresses.
   * This is effective when antibadmail works with qmail. */
  for (e=local+strlen(local)-1; e; e=strrchr(local, dash)) {
    *(e+1) = '\0';              /* Remove next char of '-' */

    if (isdomdir) {
      /* If matches with domain named dir, check local-part */
      /* Check with REJECT pattern, first */
      if (!strop(&domd, "=", soiled)) die_nomem();
      if (!strop(&domd, "join", "/", at, 0)) die_nomem();
      if (!strop(&locp, "=", SOILED_REJECT)) die_nomem();
      if (!strop(&locp, "+", local)) die_nomem();
      if (strictmatch(domd.s, locp.s)) {
        reason("append", "SDIR_REJECT(S)");
        fprintf(stderr, "SOILED: %s/%s\n", domd.s, locp.s);
        return 0;
      }
      /* Check with EXCLUDE pattern */
      if (!strop(&locp, "=", SOILED_EXCLUDE)) die_nomem();
      if (!strop(&locp, "+", local)) die_nomem();
      if (strictmatch(domd.s, locp.s)) {
        fprintf(stderr, "Soiled: %s/%s\n", domd.s, locp.s);
        return 0;
      }

      /* Check with normal soiled pattern */
      if (!strop(&locp, "=", local)) die_nomem();
      if (strictmatch(domd.s, locp.s)) {
        fprintf(stderr, "soiled: %s/%s\n", domd.s, locp.s);
        return 1;
      }
    }

    /* For backward compatibility */
    if (!strop(&tmp, "=", local)) die_nomem();
    if (!strop(&tmp, "+", at)) die_nomem();
    if (strictmatch(soiled, tmp.s)) {
      fputs("YES!\n", stderr);
      return 2;
    }
    *e = '\0';
  }
  if (isdomdir) {
    if (!strop(&locp, "=", "ALL")) die_nomem();
    fprintf(stderr, "Check %s/%s\n", domd.s, locp.s);
    if (strictmatch(domd.s, locp.s)) {
      fputs("domain match!\n", stderr);
      return 2;
    }
  }
  return 0;
}


/* Check RCPTTO string
 * Return 0 = No Problem
 *        1 = Listed in badrcptto (strict)
 *        2 = Listed in badrcptto (wildcard)
 *        3 = Null sender cannot be sending to multiple rcpients
 *       -1 = Recipient nonexistent
 *       -2 = Recipient on remote nonexistent
 */
int rcpttocheck(char *rcptto)
{
  int rc;
  char *badrcpttodir = BADRCPTTODIR;
  char *at;
  char soiledflag[] = "SOILED";
  STR rq = {0};
  at=strrchr(rcptto, '@');
  /* Relay client? */
  if (relaycheck && !relayclient && at) {
    int ok=0;
    char *rcpthostsdir = RCPTHOSTSDIR;
    if (*(at+1) && strictmatch(rcpthostsdir, at+1))
      ok = 1;
    else {
      char *p;
      for (p=at+1; *p; p++)
        if ('.' == *p)
          if (strictmatch(rcpthostsdir, p)) {
            ok = 1;
            break;
          }
    }
    if (!ok) {
      reason("append", "RELAY");
    }
  }
  /*
   * With qmail, check the existence of recipient address.
   */
  if (withqmail) {
    fprintf(stderr, "checking qmail recipient file\n");
    rc=q_nonexistent(rcptto, controldir);
    if (rc==0) {
      if (!getenvopt("PERMIT_NXRCPT"))
        /* Severe and Absolutely bad */
        reason("append", "RCPTTO_NONEXIST(S)(A)");
      return -1;
    }
  }
  /*
   * Recipient existence check on remote server
   */
  if (rc == Q_NXNONLOCAL && at && *(at+1)) {
    struct stat rqfile;
    strop(&rq, "=", "");
    if (!strop(&rq, "join", controldir, "/rq/", at+1, 0)) die_nomem();
    if (0 == stat(rq.s, &rqfile)) { /* rq file for domain IS */
      rc=rq_nonexistent(rcptto, rq.s);
      if (rc == 0) {
        if (!getenvopt("PERMIT_NXRCPT"))
          reason("append", "R_RCPTTO_NONEXIST");
        return -2;
      }
    }
  }
  /* Here, recipient exists.
   * Check further condition. */
  if (isreliable())
    return 0;
  if ((rc=strictmatch(badrcpttodir, rcptto))) {
    reason("append", "RCPTTO(S)");
    return 1;
  }
  if (at)
    if (*(at+1))
      if ((rc=strictmatch(badrcpttodir, at))) {
        reason("append", "RCPTTO@");
        return 2;
      }
  if (mailfrom[0] == '\0' && rcpttocount>1) {
    reason("append", "RCPTTO_Nby<>");
  }
  /* 
   * Start soiled rcpient check
   */
  {
    int soiled = issoiled(rcptto);
    if (soiled && (soiled==1 || !reason("match", "(S)"))) {
      /* Revert rejection if existing rejection is not severe
         reason or soiled mode==1 (strict match) */
      reason("append", soiledflag);
    } else {
      reason("delete", soiledflag);
    }
  }
  return 0;
}

/*
 * Make final judgement
 */
int judge()
{
  int bad=0;
#if 0
  fprintf(stderr, "reason=%s\n", getreason());
#endif
  if (reason("match", "SOILED"))
    return 0;
  if (reason("match", "(A)"))
    return 1;    /* Absolutely unacceptable (eg. NXRCPT) even if reliable */
  /* $PASSONLY check */
  /* Check if the rejection-reason string exists */
  if (isbad()) {
    bad++;                      /* Reject it */
  }
  if (isreliable()) {  /* Reliable client exempted from further check */
    // reason[0] = '\0';
    return 0;
  }
  if (getenvopt("BADHOST")) {
    reason("append", "BAD_HOST");
    bad++;
  }
  if (getenv("TCPPARANOID") && !reason("match", "GOOD")) {
    reason("append", "BAD_PARANOID");
    bad++;
  }
  if (getenv("REQPTR") &&
      (NULL == getenv("TCPREMOTEHOST")
       || !strcmp("unknown", getenv("TCPREMOTEHOST")))) {
    reason("append", "REQPTR");
    bad++;
  }
  return bad;
}

/* return non-zero if character C is whitespace or ':' */
int isCMDdelim(char c)
{
  return isspace((int)c) || c == ':';
}

int isSMTPCMD(char *mesg, char *cmd)
{
  int len = strlen(cmd);
  if (!memcmp(mesg, cmd, len)
      && strlen(mesg) >= strlen(cmd)) {
    int offset = len;
    while (mesg[offset] && isCMDdelim(mesg[offset]))
      offset++;
    return offset;
  }
  return 0;
}

/* Return the offset of HELO-HOST if the MESG line is HELO string.
   Otherwise return 0. */
int isHELO(char *mesg)
{
  int rc;
  if ((rc=isSMTPCMD(mesg, "helo")))
    return rc;
  return isSMTPCMD(mesg, "ehlo");
}
/* Return the offset of "MAIL FROM" if the MESG line is MAIL-FROM string.
   Otherwise return 0. */
int isMAILFROM(char *mesg)
{
  return isSMTPCMD(mesg, "mail from");
}
/* Return the offset of "RCPT TO" if the MESG line is RCPT-TO string.
   Otherwise return 0. */
int isRCPTTO(char *mesg)
{
  return isSMTPCMD(mesg, "rcpt to");
}

char* regular_buffer(char *str)
{
  static STR buf = {0};
  int i;
  if (!strop(&buf, "=", str)) die_nomem();
  for (i=0; *str && i<MAXLINE; i++, str++) {
    if (isalpha((int)*str)) {
      buf.s[i] = tolower((int)*str);
    } else {
      buf.s[i] = *str;
    }
  }
  return buf.s;
}

/* Parse line and set arg[0], arg[1] and arg[2];
 * Assume arg[2] is enough.
 * Assume SMTP args are not enclosed with quotes.
 */
void parse_smtpargs(char *arg0p, char *arg1p)
{
  char *p = arg0p, *q = arg1p;
  char *lim = p+MAXLINE-1;
  strop(&smtparg0, "=", "");
  strop(&smtparg1, "=", "");
  strop(&smtparg2, "=", "");
  /* make arg[0] */
  //smtparg[0] = p;
  
  while (p < arg1p && !isspace((int)*p) && p < lim)
    p++;
  if (p >= lim)
    abort_toolong();

  if (!strop(&smtparg0, "n=", arg0p, p-arg0p)) die_nomem();
  /* make arg[1] */
  while (*q && isspace((int)*q)) q++;

  if (!*q) return;
  for (p=q; *q && !isspace((int)*q) && p < lim; q++) ;
    
  if (p >= lim)
    abort_toolong();
  if (!strop(&smtparg1, "n=", p, q-p)) die_nomem();

  while (*q && isspace((int)*q)) q++;

  if (!*q) return;
  for (p=q; *q && !isspace((int)*q) && p < lim; q++) ;
  if (p >= lim)
    abort_toolong();
  if (!strop(&smtparg2, "n=", p, q-p)) die_nomem();
}
/* Parse line and get Envelope-From or RCPT-TO.
 * Assume line is `who@doma.in' or `<who@doma.in>'
 */
char* parse_email(char *line)
{
  static STR buf = {0};
  char *p=line, *q;
  while (*p && isspace((int)*p))
    p++;
  if (*p == '<') {
    for (q=++p; *p && !strchr(">\r\n", *p); p++) ;
    if (!strop(&buf, "n=", q, p-q)) die_nomem();
  } else {
    for (q=p ; *p && !strchr(" \r\n", *p); p++) ;
    if (!strop(&buf, "n=", q, p-q)) die_nomem();
  }
  return buf.s;
}

void resetmailfrom()
{
  if (mailfrom) {
    free(mailfrom);
    mailfrom = NULL;
  }
}

void resetrcptto()
{
  if (rcptto) {
    free(rcptto);
    rcptto = NULL;
  }
  do_rcptlist("reset");
  rcpttocount = 0;
}

/* Reset all status variables */
void rset()
{
  rejectit = 0;
  reason("set", "");
  /* HHLO is to be preserved */
  resetmailfrom();
  resetrcptto();
}

/*
 * Make SMTP error reply by destructing second argument s
 */
static
char* mkerrmsg(int ecode, char* emssc, STR* s)
{
  STR tmp = {0};
  char *p, *cr;
  char code[4];
  if (ecode > 999 || ecode < 0) smtpabort("Internal error\r\n");
  if (s->len > 3 && isdigit((int)s->s[0]) && isdigit((int)s->s[1])
      && isdigit((int)s->s[2]))
    return s->s;         /* Already has canonical reply message, maybe */
  snprintf(code, 4, "%3d", ecode);     /* Maximum 3 bytes. */
  if (!strop(&tmp, "=", "")) die_nomem();
  strop(s, "trim");             /* Strip traiilng \r or \n-s */
  for (p=s->s; (cr=strchr(p, '\n')); p=cr+1) {
    if (!strop(&tmp, "n+", code, 3)) die_nomem();
    if (!strop(&tmp, "+", "-")) die_nomem();
    if (!strop(&tmp, "n+", p, cr-p)) die_nomem();
    if (!strop(&tmp, "+", "\r\n")) die_nomem();
  }
  if (!strop(&tmp, "n+", code, 3)) die_nomem();
  if (!strop(&tmp, "join", " ", p, emssc, "\r\n", NULL)) die_nomem();
  if (!strop(s, "=", tmp.s)) die_nomem();
  strop(&tmp, "!");             /* free */
  return s->s;
}

/*
 * Reject client with error messages
 */
void reject()
{
  char embuf[MAXLINE];
  char *errmsg = embuf;
  // static errmsg = {0};
#if 0
  char *rcode_policy = "553 ", *rcode_temp = "451 ";
  char *code, *msgbody, *emssc;
  strop(&errmsg, "=", "");
  char *emssc_policy = "(#5.7.1)", *emmsc_temp = "(#4.3.0)";
#endif
  char *default_msg = "We cannot accept such badmail";
  int ecode = 553, tecode = 451;
  /*
   * We give client non-informative message to discourage spammers
   * from learning what's the wrong settings ;-)
   */
  lastcmd = "";
  if (reason("match", "MAILFROM")) {
    snprintf(embuf, sizeof embuf,
             "%3d Sorry, your sender[%s] is invalid(#5.7.1)\r\n",
             ecode, mailfrom);
  } else if (reason("match", "BAD_HOST")) {
    snprintf(embuf, sizeof embuf,
             "%3d sorry, your host is registered in BADHOST(#5.7.1)\r\n",
             ecode);
  } else if (reason("match", "IPINHELO")) {
    snprintf(embuf, sizeof embuf,
             "%3d Sorry, you seem to be a non-static-ip server(#5.7.1)\r\n",
             ecode);
  } else if (reason("match", "HELO")) {
    snprintf(embuf, sizeof embuf,
             "%3d SMTP-server should HELO correctly.  Fix it.(#5.7.1)\r\n",
             ecode);
  } else if (reason("match", "RCPTTO_Nby<>")) {
    snprintf(embuf, sizeof embuf,
             "%3d Bounce message should not have multiple rcpt(#5.7.1)\r\n",
             ecode);
  } else if (reason("match", "R_RCPTTO_NONEXIST")) {
    snprintf(embuf, sizeof embuf,
             "%3d Sorry, no such mailbox on best MX host(#5.1.1)\r\n",
             450);              /* 450=Action is not acceptable */
  } else if (reason("match", "RCPTTO_NONEXIST")) {
    snprintf(embuf, sizeof embuf,
             "%3d Sorry, no such mailbox here(#5.1.1)\r\n",
             550);              /* 550=Don't process your action */
  } else if (reason("match", "RCPT")) {
    snprintf(embuf, sizeof embuf,
             "%3d sorry, envelope recipient is in badrcptto list(#5.7.1)\r\n",
             ecode);
  } else if (reason("match", "RELAY")) {
    snprintf(embuf, sizeof embuf,
             "%3d sorry, we can't relay to that domain(#5.7.1)\r\n",
             ecode);
  } else if (reason("match", "REQPTR")) {
    snprintf(embuf, sizeof embuf,
             "%3d Correct SMTP server's PTR record.(#5.7.1)\r\n",
             ecode);
  } else if (reason("match", "PARANOID")) {
    snprintf(embuf, sizeof embuf,
             "%3d Your PTR record points to wrong hostname(#5.7.1)\r\n",
             ecode);
  } else if (reason("match", "PASSONLY")) {
    snprintf(embuf, sizeof embuf,
             "%3d I can accept only %s domains from your server(#5.7.1)\r\n",
             ecode, getenv("PASSONLY"));
  } else if (reason("match", "HEADER")) {
    if (hresult.msg->s && (hresult.msg->s)[0])
      errmsg = mkerrmsg(ecode, "(#5.7.1)", hresult.msg);
    else
      snprintf(embuf, sizeof embuf, "%3d %s(#5.7.1)\r\n", ecode, default_msg);
  } else if (reason("match", "DNS_SOFT")) {
    snprintf(embuf, sizeof embuf,
             "%3d DNS Lookup failure on your domain, try later(#4.3.0)\r\n",
             tecode);
  } else {
    snprintf(embuf, sizeof embuf,
             "%3d Sorry, we can't accept your mail(#5.7.1)\r\n", ecode);
  }
  //fprintf(stderr, "reason=[%s]\n", getreason());
  smtpout(errmsg);
  if (getenvopt("NOTERM")||reason("match", "PASSONLY")) {
    /* When $PASSONLY set, client must be polite one.  Keep connection. */
    rset();
    // sendtodaemon("rset\r\n");
  } else {
    sendtodaemon("quit\r\n");
    close(fd_daemon);
    smtpout("221 sarabaja\r\n");
    bye(1);                     /* is normail termination */
  }
}

/*
 * Check if the LINE begins with header-field pattern
 */
int isfield(char *line)
{
  if (*line == ':') return 0;
  while (*line && *line != ':' && !isspace((int)*line)) line++;
  return (*line == ':');
}

/*
 * Send smtp-daemon received header
 *
 */
static
char* myreceived()
{
  char b[30];
  time_t now;
  struct tm *ntm;
  static STR recvd = {0};
  time(&now);
  ntm = localtime(&now);
  strftime(b, sizeof b, "%b %e %T %Z %Y\r\n", ntm);
  if (!strop(&recvd, "=", "Received: ")) die_nomem();
  if (!strop(&recvd, "join",
             "from ",
             tcpremotehost,
             " (HELO ", helo, ") (", tcpremoteip, ")\r\n",
             "  by ", tcplocalhost, " (", myname(), ") with SMTP; ",
             b, NULL)) die_nomem();
  return recvd.s;
}  

/*
 * Check if the line upto CR or LF matches with str
 */
int matchuptocrlf (char *line, char *str)
{
  char *crlf;
  int slen=strlen(str);
  if (!(crlf=strchr(line, '\r')))
    if (!(crlf=strchr(line, '\n')))
      crlf=line+strlen(line);
  return (slen==crlf-line && !strncmp(line, str, slen));
}

/* Check the client information
   Return 0: if not malicious client
          1: maybe malicious client
   This function should set checked request to `qualified_request'
*/
int client_check(char *request)
{
  char *r = request;
  static int inheader;
  static STR header = {0};
  static STR ptnpath = {0};
  static STR headerline = {0};
  if (!ptnpath.s) {
    if (!strop(&ptnpath, "=", controldir)) die_nomem();
    if (!strop(&ptnpath, "+", "/badhdrdir")) die_nomem();
  }
  lastcmd = "";
  if (cont_3xx) {               /* Maybe in the DATA section */
    char *p = request;
    static HM hmatcher = {0};
    if (0 == hmatcher.n) hmatcher.dir = ptnpath.s;

    if (matchuptocrlf(request, ".")) {
      cont_3xx = 0;
      if (!strop(&qualified_request, "+", request)) die_nomem();
    }
    else if (!headercheck) {
      /* if not headercheck mode, pass through request to daemon */
      if (!strop(&qualified_request, "=", request)) die_nomem();
    } else if (inheader == 1) {
      //fprintf(stderr, "H:<<<%s>>>\n", request);
      if (!strop(&header, "+", request)) die_nomem();
      if (matchuptocrlf(request, "")) { /* End of header, accept it!! */
        inheader = 0;
        if (!strop(&qualified_request, "=", header.s)) die_nomem();
        strop(&header, "=", "");
        
        if (headerline.s && headerline.s[0]) {
          strop(&headerline, "lower");
          if (0 < header_match(&hmatcher, headerline.s, &hresult))
            goto header_reject;
        }
        putlog("");
        keepwaiting = 1;
        resetmailfrom();
        /* header_match_delete(&hmatcher); */
#ifdef DEBUG
        fprintf(stderr, "qualified=[%s]\n", qualified_request.s);
#endif
      } else if (strchr(" \t", request[0])) { /* Continuing line */
        
        while (*p && isspace((int)*p)) p++;
        if (!strop(&headerline, "+", p)) die_nomem();
      } else if (isfield(request)) {
        if (headerline.s && headerline.s[0]) { /* Already have some header */
          strop(&headerline, "lower");
          strop(&headerline, "chomp"); /* 2006/2/7 for regexp $ */
          if (0 < header_match(&hmatcher, headerline.s, &hresult)) {
            fprintf(stderr, "MATCH!!!\n");
            #if 0
            /* Logging code here?? */
            #endif
          header_reject:
            if (!strop(&headerline, "=", "HEADER(S)")) die_nomem();
            #if 1
            if (!strop(&headerline, "join",
                       "@", hresult.where, "/", hresult.pname->s, NULL))
              die_nomem();
            #else
            if (!strop(&headerline, "join", "@", hresult.where, NULL))
              die_nomem();
            #endif
            reason("append", headerline.s);
            rejectit = 1;
            putlog("");
            reject();
            return 1;
          }
        }
        /* Remember a new header field */
        if (!strop(&headerline, "=", request)) die_nomem();
      }
    } else {                    /* is not in header */
      //fprintf(stderr, "outH=%s", request);
      if (!strop(&qualified_request, "=", request)) die_nomem();
    }
  } else {
    int rc;
    r = regular_buffer(request);
    if (getenvopt("QUICKREJECT")) {
      if (judge()) {
        putlog("");
        reject();
        return 1;
      }
    }
    if ((rc=isHELO(r))) {
      rset();                   /* HELO/EHLO implies RSET */
      parse_smtpargs(r, r+rc);
      helo = safestr(copystr(helo, smtparg1.s));
      lastcmd = "HELO";
      keepwaiting = 1;
    } else if ((rc=isMAILFROM(r))) {
      if (!helo || !*helo) {
        smtpout("503 HELO first (#5.5.1)\r\n");
        return 1;
      }
      resetrcptto();            /* MAIL FROM should reset rcptto */

      mailfrom = safestr(copystr(mailfrom, parse_email(r+rc)));
      mailfromcheck(mailfrom); /* mailfromcheck change state of isreliable() */
      helocheck(helo);
      lastcmd="MAIL";
      keepwaiting = 1;
    } else if ((rc=isRCPTTO(r))) {
      if (!mailfrom) {          /* *mailfrom=0 is OK (null sender) */
        smtpout("503 MAIL first (#5.5.1)\r\n");
        return 1;
      }
      rcptto = safestr(copystr(rcptto, parse_email(r+rc)));
      helocheck2(helo, rcptto);
      if (getenvopt("QUICKREJECT")) {
        if (judge()) {
          putlog("");
          reject();
          return 1;
        }
      }
      rc=rcpttocheck(rcptto);
      if (getenvopt("PERMIT_NXRCPT") && rc < 0) { /* rcpto does not exist */
        STR loghead = {0};
        if (strop(&loghead, "join", "Rejected-RCPT: ", rcptto, ",", 0)) {
          putlog(loghead.s);
          strop(&loghead, "!");
        }
        smtpout("550 5.1.1 mailbox unknown\r\n");
        return 1;
      } else {
        rcpttocount++;
        rejectit = judge();
        lastcmd = "RCPT";
        keepwaiting = 1;
        do_rcptlist("add", rcptto);
        /*     if (isbad()) {
               reject();
               return 1;
               }
               reject() afterwards...
        */
      }
    } else if ((rc=isSMTPCMD(r, "data"))) {
      lastcmd = "DATA";
      if (rcptlist()[0]) {
        if (headercheck) {
          cont_3xx = 1;
          inheader = 1;
          keepwaiting = 0;
          if (!strop(&header, "=", "DATA\r\n")) die_nomem();
          if (!strop(&header, "+", myreceived())) die_nomem();
          //strop(&headerline, "=", "");
          smtpout("354 Go ahead.\r\n");
          return 0;
        } else {
          putlog("");
        }
      } else {                  /* if rcptlist, empty */
        smtpout("503 Start afresh from MAIL(#5.5.1)\r\n");
        return 1;
      }
    } else if ((rc=isSMTPCMD(r, "rset"))) {
      rset();
      lastcmd = "RSET";
      keepwaiting = 1;
    }
    if (!strop(&qualified_request, "=", request)) die_nomem();
  }
  return 0;                     /* Return no-error by default */
}

void relay_reply_code(char *reply, int clifd)
{
  int my_reject;
  //my_reject = (rcptto && isbad()) ? 1 : 0;
  my_reject = (rcptto ? rejectit : 0);
  //fprintf(stderr, "REPCODE=%s\n", reply);
  /* Assume result code is 3digits */
  if (strlen(reply) > 3
      && isdigit((int)reply[0])
      && isdigit((int)reply[1])
      && isdigit((int)reply[2])) {
    if (reply[3] == '-') {
      /* SMTP Reply code continueing line */
      if (my_reject) {
        /* Do not output reply code to client */
      } else {
        write(clifd, reply, strlen(reply));
      }
      return;
    } else if (isspace((int)reply[3])) {
      /* SMTP Reply code (final) line */
      switch (reply[0]) {
      case '3':
        if ('5' == reply[1] && '4' == reply[2]) {
          cont_3xx = 1;
          // fprintf(stderr, "Go into data mode\n");
          if (!headercheck) {
            write(clifd, reply, strlen(reply));
            sendtodaemon(myreceived());  /* moved to delayed session */
          }
        } else if ('3' == reply[1] && '4' == reply[2]) {
          write(clifd, reply, strlen(reply));
        }
        break;
      case '5': case '4':       /* Rejection by real SMTP daemon */
        if (!strcmp(lastcmd, "RCPT")) {
          char fmt[] = "BY_SMTPD(xxx)";
          snprintf(fmt, sizeof fmt, "BY_SMTPD(%1c%1c%1c)",
                   reply[0], reply[1], reply[2]);
          reason("append", fmt);
          putlog("");
        }
        rset();                 /* We also reset SMTP and output */
        write(clifd, reply, strlen(reply));
        // smtpquit(reply);
        break;
      case '2':
        if ('2' == reply[1] && '0' == reply[2]) /* 220 is greeting msg */
          keepwaiting = 0;
        else if ('3' == reply[1] && '5' == reply[2]) /* 235 auth success */
          relayclient = "True";
        /* fall through */
      default:                    /* Otherwise rejection depends on us. */
        //fprintf(stderr, "reply=%s", reply);
        cont_3xx = 0;
        if (my_reject) {
          putlog("");
          reject();
        } else {
          write(clifd, reply, strlen(reply));
        }
      }
      return;
    }
  }
  /* I don't known other reply notation... Pass them */
  write(clifd, reply, strlen(reply));
}

/*
 * For check the performance of judging accuracy with
 * http://www.gentei.org/~yuuji/software/qmaptch/
 * Read arg file. The format of the file is as below.
 
Jun 27 00:03:56 balius qmail-smtpd[87327]: Accepted: helo=ml.postgresql.jp, host=ml.postgresql.jp, remoteip=128.121.245.6, sender=pgsql-jp-admin@ml.postgresql.jp, rcpt=mr@neko.net, relayclient=no
Jun 27 00:04:06 balius qmail-smtpd[87340]: Rejected: helo=msn.com, host=d53-64-70-225.nap.wideopenwest.com, remoteip=64.53.225.70, sender=suzanne_english_sv@yahoo.com, rcpt=neko@neko.net, relayclient=no
 */
void split(char *logfile)
{
  FILE *log;
  char buf[MAXLINE];
  char *p, *q;
  int result;
  if (!strcmp(logfile, "-") || logfile == NULL) {
    log = stdin;
  } else if (NULL == (log = fopen(logfile, "r"))) {
    fprintf(stderr, "You should prepare logfile [%s]\n", logfile);
    exit(3);
  }
  while (NULL != fgets(buf, sizeof buf, log)) {
    if ((p=strstr(buf, "Accepted: "))) {
      result = 1;
    } else if ((p=strstr(buf, "Rejected: "))) {
      result = 0;
    } else {
      continue;                 /* Skip this line */
    }
    if (NULL == (p=strstr(p, "helo=")))
      continue;
    /* get helo */
    if (NULL == (q=strstr(p, ", host=")))
      continue;
    helo = copysubstr(helo, p+5, q);
    p = q+7;
    if (NULL == (q=strstr(p, ", remoteip=")))
      continue;
    tcpremotehost = copysubstr(tcpremotehost, p, q);
    p = q+11;
    if (NULL == (q=strstr(p, ", sender=")))
      continue;
    tcpremoteip = copysubstr(tcpremoteip, p, q);
    p = q+9;
    if (NULL == (q=strstr(p, ", rcpt=")))
      continue;
    mailfrom = copysubstr(mailfrom, p, q);
    p = q+7;
    if (NULL == (q=strstr(p, ", relayclient=")))
      continue;
    rcptto = copysubstr(rcptto, p, q);
    if (NULL == (p=strstr(q, "client=")))
      continue;
    p += 7;
    if (!strncmp(p, "yes", 3))
      relayclient = "yes";
    else
      relayclient = "no";
    printf("%d %s %s %s %s %s %s\n",
           result, helo, tcpremotehost, tcpremoteip, mailfrom,
           rcptto, relayclient);
  }
}

void showmismatch()
{
  printf("r=%s he=%s mf=%s rt=%s ho=%s ip=%s rs=%s\n",
         getenv("R"), helo, mailfrom, rcptto, tcpremotehost, tcpremoteip,
         getreason());
}

void verify()
{
  int i = atoi(getenv("R"));
  helo = copystr(helo, getenv("HELO"));
  mailfrom = copystr(mailfrom, getenv("FROM"));
  rcptto = copystr(rcptto, getenv("RCPT"));
  mailfromcheck(mailfrom);
  helocheck(helo);
  rcpttocheck(rcptto);
  judge();
  if (isbad()) {
    //printf("rej: from[%s]=%s", reason);
    if (!i) puts("o");
    else 
      showmismatch();
  } else {
    if (i) puts("o"); else showmismatch();
  }
}

void do_it(char **args)
{
  int p2c[2], c2p[2];
  pid_t pid;
  int st;
  char onebyte[1];
  fd_set rfd, rfd2;
  int readable;
  int addcr = 1;
  args++;
  if (!strcmp(args[0], "-s")) {
    split(args[1]);            /* Verify the judge() performance */
    exit(0);
  } else if (!strcmp(args[0], "-v")) {
    verify();
    exit(0);
  }
  if (-1 == pipe(p2c) || -1 == pipe(c2p)) {
    fprintf(stderr, "PIPE error\n");
    bye(2);
  }
  
  if (strstr(*args, "mconnect")) {
    fprintf(stderr, "mconnect");
    addcr = 0;
    relaycheck = 1;
  } else if (strstr(*args, "qmail-smtpd")
             || strstr(*args, "rblsmtpd")
             || getenv("QMAIL")) {
    withqmail = 1;
    relaycheck = 0;
  }
  if (getenv("RELAYCHECK") && getenv("RELAYCHECK")[0])
    relaycheck = atoi(getenv("RELAYCHECK"));
  if ((pid=fork()) == -1) {
    fprintf(stderr, "Cannot fork.\n");
    bye(3);
  } else if (pid != 0) {
    /* parent ... is me */
    close(p2c[0]);
    close(c2p[1]);
   } else {
    /* child ... becomes smtpd */
    close(p2c[1]);
    close(c2p[0]);
    
    close(0);
    dup(p2c[0]);
    close(p2c[0]);              /* p2c[0] turns to stdin */

    close(1);
    dup(c2p[1]);
    close(c2p[1]);              /* c2p[1] turns to stdout */
    setlinebuf(fdopen(1, "w"));

    execvp(*args, args);
    fprintf(stderr, "Cannot exec %s\n", args[1]);
    
  }
  rset();
  fprintf(stderr, "---------------------\n");
  // FD_ZERO(&wfd);
  // FD_SET(1, &wfd);
  FD_ZERO(&rfd);
  FD_SET(0, &rfd);
  FD_SET(c2p[0], &rfd);
  memcpy(&rfd2, &rfd, sizeof rfd);
  fd_daemon = p2c[1];
  while (FD_ISSET(c2p[0], &rfd2)) {
    static STR cbuf = {0};
    struct timeval smtptimeout = {SMTPTIMEOUT, 0};
    int nb, ng;
    memcpy(&rfd, &rfd2, sizeof rfd);
    /* if (keepwaiting && (headercheck && cont_3xx || !helo)) */
    if (keepwaiting && !cont_3xx) {
      /* In headercheck mode, keep client waiting at the end of
         header until smtpd's reply comes. */
      struct timeval headertimeout = {15, 0};
      FD_CLR(0, &rfd);
#ifdef DEBUG812
fprintf(stderr, "!KW!");
#endif
      readable = select(32, &rfd, 0, 0, &headertimeout);
      keepwaiting = 0;
    } else {
      readable = select(32, &rfd, 0, 0, &smtptimeout);
    }
    if (readable < 0) {		/* in case qmail-smtpd abort(?) 2006/9/7 */
      close(c2p[0]);
      close(fd_daemon);
      break;
    }
    if (FD_ISSET(c2p[0], &rfd)) { /* Reply from the daemon */
      strop(&cbuf, "=", "");
      do {
        nb=read(c2p[0], onebyte, 1);
        if (!strop(&cbuf, "addch", (int)*onebyte)) die_nomem();
        FD_ZERO(&rfd); FD_SET(c2p[0], &rfd);
      } while (nb > 0
               && *onebyte != '\n'
               && 0 < select(32, &rfd, 0, 0, 0));
      if (cbuf.len>0)
        relay_reply_code(cbuf.s, 1);
      if (nb <= 0) {
        close(c2p[0]);
        FD_CLR(c2p[0], &rfd2);
      }
    } else if (FD_ISSET(0, &rfd)) { /* string from client */
      /* We cannot use fgets() here because it causes block-state.
       * But child daemon expects its input line-oriented.
       * Therefore we have to do line-buffered reading.
       * We also need to convert LF to CR+LF unless child is mconnect.
       */
      if (NULL != qualified_request.s)
        /* HEADERCHECK mode:
           If we have unsent header lines at previous turn of loop, 
           copy bufferd header lines to current line buffer */
        strop(&cbuf, "=", qualified_request.s);
      else
        strop(&cbuf, "=", "");
      /* fprintf(stderr, "NewSTR=[%s]\n", cbuf.s); */
      do {
        nb=read(0, onebyte, 1);
        if (!strop(&cbuf, "addch", (int)*onebyte)) die_nomem();
        FD_ZERO(&rfd); FD_SET(0, &rfd);
      } while (nb > 0
               && *onebyte != '\n'
               && 0 < select(32, &rfd, 0, 0, &smtptimeout));
#ifdef DEBUG812
if (!cont_3xx) fprintf(stderr, "cli->[%s]", cbuf.s);
#endif
      if (cbuf.len>0) {
          ng = client_check(cbuf.s); /* Check SMTP params (important job) */
            
        if (!ng) {
          char *x = qualified_request.s;
          char *newline;
          while (*x && (newline=strchr(x, '\n'))) {
            
            if (newline-1>=x && *(newline-1)=='\r')
              write(fd_daemon, x, newline-x-1);
            else
              write(fd_daemon, x, newline-x);
            /* Write CR+LF */
            if (addcr)
              write(fd_daemon, "\r", 1);
            write(fd_daemon, "\n", 1);
            if (isSMTPCMD(x, "DATA")) {
              /* in delayed check mode, we should wait GO_AHEAD from server */
              strop(&qualified_request, "<", newline-x+1);
              // fprintf(stderr, "New=[%s]\n", qualified_request.s);
              goto skiploop;
            }
            x = newline+1;
          }
          if (*x) /* Write rest of string, maybe string without newline */
            write(fd_daemon, x, qualified_request.len-(x-qualified_request.s));
          strop(&qualified_request, "=", "");
        skiploop:
          continue;
        }
        
      }
      if (nb <= 0) {
        fprintf(stderr, "ÊÄ¤¸¤Þ¤¹(%d)\n", nb);
        FD_CLR(0, &rfd2);
        close(fd_daemon);
      }
    } else {
      break;
    }
  }
  close(1);
  wait(&st);
  fprintf(stderr, "---------------------\n");
  fprintf(stderr, "ret=%d\n", st);
  fprintf(stderr, "---------------------\n");
}

void usage(char *me)
{
  fprintf(stderr, "Usage: %s SMTP-DAEMON-PROGRAM\n", me);
  exit(0);
}
int main(int argc, char *argv[])
{
  if (NULL == argv[1]) {
    usage(argv[0]);
  }
  copystr(argv0, argv[0]);
  initialize();
  do_it(argv);
  bye(0);
  return 0;
}
