Logo Search packages:      
Sourcecode: dcc version File versions

dccifd.c

/* Distributed Checksum Clearinghouse
 *
 * DCC interface daemon
 *
 * Copyright (c) 2005 by Rhyolite Software, LLC
 *
 * This agreement is not applicable to any entity which sells anti-spam
 * solutions to others or provides an anti-spam solution as part of a
 * security solution sold to other entities, or to a private network
 * which employs the DCC or uses data provided by operation of the DCC
 * but does not provide corresponding data to other users.
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * Parties not eligible to receive a license under this agreement can
 * obtain a commercial license to use DCC and permission to use
 * U.S. Patent 6,330,590 by contacting Commtouch at http://www.commtouch.com/
 * or by email to nospam@commtouch.com.
 *
 * A commercial license would be for Distributed Checksum and Reputation
 * Clearinghouse software.  That software includes additional features.  This
 * free license for Distributed ChecksumClearinghouse Software does not in any
 * way grant permision to use Distributed Checksum and Reputation Clearinghouse
 * software
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND RHYOLITE SOFTWARE, LLC DISCLAIMS ALL
 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RHYOLITE SOFTWARE, LLC
 * BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
 * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
 * SOFTWARE.
 *
 * Rhyolite Software DCC 1.3.42-1.114 $Revision$
 */

#include "dccif.h"
#include "dcc_paths.h"
#include "cmn_defs.h"
#include <signal.h>


static int stopping;

#define MAX_SMTP_LINE

u_char use_ipv6 = 0;

/* incoming proxy and bidirectional ASCII dccifd protocol connection */
static const char *listen_addr;
static u_char listen_family;
static struct sockaddr_un listen_sun;
static const char *raddr_rmask;
static struct in6_addr raddr, rmask;
static SOCKET listen_soc = -1;

/* outgoing proxy connection */
static u_char proxy_out_family;
static struct sockaddr_un proxy_out_sun;
static char proxy_out_host[MAXHOSTNAMELEN];
static u_int16_t proxy_out_port;
static DCC_SOCKU proxy_out_su;

static const char *homedir = 0;
static const char *rundir = DCC_RUNDIR;
static DCC_PATH pidpath;
static const char *progpath = DCC_LIBEXECDIR"/dccifd";

static u_char background = 1;

static u_char proxy;

u_char discard_nok_def = 0;         /* 0=discard on whitelist conflicts */


/* message state or context */
typedef struct {              /* control an input buffer */
    char    *base;
    char    *in;
    char    *out;
    char    *next_line;
    int           size;
    int           line_len;
    int           *socp;
} IN_BC;
typedef struct {              /* control an ouput buffer */
    char    *base;
    char    *in;
    int           len;
    int           size;
    int           *socp;
} OUT_BC;
typedef struct work {
    int           proxy_in_soc;
    DCC_SOCKU   proxy_in_su;
    int           proxy_out_soc;
    char    buf1[DCC_HDR_CK_MAX*8]; /* >=DCC_HDR_CK_MAX*2 & MAX_RCPTS */
    char    buf2[DCC_HDR_CK_MAX*8];
    char    buf3[1024];
    char    buf4[1024];
    CMN_WORK    cw;
    enum {
      SMTP_ST_START,                /* expecting HELO */
      SMTP_ST_HELO,                 /* seen HELO, expecting Mail_From */
      SMTP_ST_TRANS,                /* seen Mail_From */
      SMTP_ST_RCPT,                 /* seen Rcpt_To */
      SMTP_ST_ERROR,                /* no transaction until RSET or HELO */
    } smtp_state;
    /* from here down is zeroed when the structure is allocated */
#define WORK_ZERO fwd
    struct work *fwd;
    IN_BC       msg_rd;             /* incoming mail message */
    OUT_BC      msg_wt;             /* outoing mail message  */
    IN_BC       reply_in;           /* incoming SMTP replies */
    OUT_BC      reply_out;          /* outgoing SMTP replies */
    int           total_hdrs, cr_hdrs;
    int           parse_rcvd;       /* which received: header to parse */
    u_int       flags;
#    define      FG_WORK_LOCK       0x0001  /* hold the lock */
#    define      FG_MISSING_BODY    0x0002  /* missing message body */
#    define      FG_MTA_BODY      0x0004  /* MTA wants the body */
#    define      FG_MTA_HEADER      0x0008  /* MTA wants the X-DCC header */
#    define      FG_MTA_GREY_QUERY  0x0010  /* MTA wants query DCC greylist */
#    define      FG_SEEN_HDR      0x0020  /* have at least 1 header */
#    define      FG_PARSE_RCVD          0x0040  /* parse Received header */
#    define  FG_XFORWARD_NAME   0x0080 /* client name via XFORWARD */
#    define  FG_XFORWARD_ADDR   0x0100 /* client addresses via XFORWARD */
#    define  FG_XCLIENT_HELO    0x0200  /* HELO value via XCLIENT */
#    define  FG_RECYCLE (FG_WORK_LOCK | FG_MTA_BODY               \
                       | FG_XFORWARD_NAME | FG_XFORWARD_ADDR      \
                       | FG_XCLIENT_HELO)
} WORK;

/* use a free list to avoid malloc() overhead */
static WORK *work_free;

/* Every job involves
 *      a socket connected to the MTA or upstream proxy
 *      a socket connected to the downstream proxy if using SMTP
 *      a log file,
 *      and a socket to talk to the DCC server.
 * While processing per-user whitelists there are
 *      another log file
 *      and an extra file descriptor for the main log file.
 * The file descriptors for the whitelists are accounted for in EXTRA_FILES */
#define FILES_PER_JOB   6


#define MAX_SELECT_WORK ((FD_SETSIZE-EXTRA_FILES)/FILES_PER_JOB)
#define DEF_MAX_WORK    200
#define MIN_MAX_WORK    2
static int max_max_work = MAX_SELECT_WORK;
static int max_work = DEF_MAX_WORK;

typedef struct user_domain {
    struct user_domain *fwd;
    const char    *nm;
    int           len;
} USER_DOMAIN;
static USER_DOMAIN *user_domains;

/* max seconds to wait for MTA
 *      Longer than the longest timeout in RFC 2821 */
#define MAX_MTA_DELAY   (10*60+5)


static void sigterm(int);
static u_char set_soc(DCC_EMSG, int, int, const char *);
static void bind_listen(void);
static void close_listen_soc(void);
static void unlink_listen_sun(void);
static void NRATTRIB *job_start(void *);
static void job_close(WORK *);
static void NRATTRIB job_exit(WORK *);


static void
usage(const char* barg, const char *bvar)
{
      const char str[] = {
          "usage: [-VdbxANQ] [-G on | off | noIP | IPmask/xx] [-h homedir]"
          " [-I user]\n"
          "    [-p /sock | host,port,rhost/bits]"
          " [-o /sock | host,port]\n"
          "    [-D local-domain] [-r rejection-msg] [-m map] [-w whiteclnt]\n"
          "    [-U userdirs] [-a IGNORE | REJECT | DISCARD]\n"
          "    [-t type,[log-thold,][rej-thold]] [-g [not-]type]"
          " [-S header]\n"
          "    [-l logdir] [-R rundir] [-T tmpdir] [-j maxjobs]\n"
          "    [-B dnsbl-option] [-X xfltr-option]"
          " [-L ltype,facility.level]\n"
      };
      static u_char complained;

      if (!complained) {
            if (barg)
                  dcc_error_msg("unrecognized \"%s%s\"\n%s\n..."
                              " continuing",
                              barg, bvar, str);
            else
                  dcc_error_msg("%s\n... continuing", str);
            complained = 1;
      }
}


int NRATTRIB
main(int argc, char **argv)
{
      DCC_EMSG emsg;
#ifdef RLIMIT_NOFILE
      struct rlimit nofile;
      int old_rlim_cur;
#endif
      long l;
      u_char log_tgts_set = 0;
      const char *logdir = 0;
      WORK *wp;
      pthread_t tid;
      DCC_SOCKLEN_T namelen;
      const char *cp;
      USER_DOMAIN *udom, *udom2, **udomp;
      char *p;
      int error, i;

      emsg[0] = '\0';
      if (*argv[0] == '/')
            progpath = argv[0];
      dcc_syslog_init(1, argv[0], 0);
      dcc_clear_tholds();

#ifdef RLIMIT_NOFILE
      if (0 > getrlimit(RLIMIT_NOFILE, &nofile)) {
            dcc_error_msg("getrlimit(RLIMIT_NOFILE): %s", ERROR_STR());
            old_rlim_cur = 1000*1000;
      } else {
            old_rlim_cur = nofile.rlim_cur;
            if (nofile.rlim_max < 1000*1000) {
                  i = nofile.rlim_max;
#ifndef USE_POLL
                  if (i > FD_SETSIZE)
                        i = FD_SETSIZE;
#endif
                  max_max_work = (i - EXTRA_FILES)/FILES_PER_JOB;
                  if (max_max_work <= 0) {
                        dcc_error_msg("only %d open files allowed",
                                    (int)nofile.rlim_max);
                        max_max_work = 1;
                  }
            }
      }
#else /* RLIMIT_NOFILE */
      if (max_work <= 0) {
            dcc_error_msg(EX_OSERR, "too few open files allowed");
            max_max_work = 1;
      }
#endif /* RLIMIT_NOFILE */
      max_work = min(DEF_MAX_WORK, max_max_work);

#define SLARGS "64VdbxANQW"         /* fix start-dccifd if these change */
      while (EOF != (i = getopt(argc, argv, SLARGS"G:h:I:p:o:D:r:m:w:U:"
                          "a:t:g:S:l:R:T:j:B:X:L:"))) {
            switch (i) {
#ifndef NO_IPV6
            case '6':
                  use_ipv6 = 1;
                  break;
#endif
            case '4':
                  use_ipv6 = 0;
                  break;

            case 'V':
                  fprintf(stderr, DCC_VERSION"\n");
                  exit(EX_OK);
                  break;

            case 'd':
                  ++dcc_clnt_debug;
                  break;

            case 'b':
                  background = 0;
                  break;

            case 'x':
                  try_extra_hard = DCC_CLNT_FG_NO_FAIL;
                  break;

            case 'A':
                  chghdr = ADDHDR;
                  break;

            case 'N':
                  chghdr = NOHDR;
                  break;

            case 'Q':
                  dcc_query_only = 1;
                  break;

            case 'G':
                  if (!dcc_parse_client_grey(optarg))
                        usage("-G", optarg);
                  break;

            case 'W':
                  to_white_only = 1;
                  break;

            case 'h':
                  homedir = optarg;
                  break;

            case 'I':
                  dcc_daemon_su(optarg);
                  break;

            case 'p':
                  listen_addr = optarg;
                  break;

            case 'o':
                  proxy = 1;
                  discard_nok_def = 1;
                  p = strchr(optarg, ',');
                  if (!p) {
                        /* recognize single-ended (not-really-)-proxy */
                        if (!strcmp(optarg, _PATH_DEVNULL)) {
                              proxy_out_family = AF_UNSPEC;
                              break;
                        }
                        if (strlen(optarg)>=ISZ(proxy_out_sun.sun_path))
                              dcc_logbad(EX_USAGE, "invalid UNIX"
                                       " domain socket: -o %s",
                                       optarg);
                        strcpy(proxy_out_sun.sun_path, optarg);
#ifdef HAVE_SA_LEN
                        proxy_out_sun.sun_len = SUN_LEN(&proxy_out_sun);
#endif
                        proxy_out_sun.sun_family = AF_UNIX;
                        proxy_out_family = AF_UNIX;
                  } else {
                        cp = dcc_parse_nm_port(emsg, optarg,
                                           DCC_GET_PORT_INVALID,
                                           proxy_out_host,
                                           sizeof(proxy_out_host),
                                           &proxy_out_port, 0, 0,
                                           0, 0);
                        if (!cp)
                              dcc_logbad(dcc_ex_code, "%s", emsg);
                        if (*cp != '\0')
                              dcc_logbad(EX_USAGE,
                                       "invalid IP address: \"%s\"",
                                       optarg);
                        proxy_out_family = AF_INET; /* includes IPv6 */
                  }
                  break;

            case 'D':
                  /* save user domain names sorted  by length */
                  if (*optarg == '\0'
                      || !strcmp(optarg, "@")
                      || strchr(optarg+1, '@')) {
                        dcc_logbad(EX_USAGE,
                                 "invalid local-domain \"%s\"",
                                 optarg);
                        break;
                  }
                  udom2 = dcc_malloc(sizeof(*udom2));
                  udom2->nm = optarg;
                  i = strlen(optarg);
                  udom2->len = i;
                  udomp = &user_domains;
                  for (;;) {
                        udom = *udomp;
                        if (!udom || i > udom->len) {
                              udom2->fwd = udom;
                              *udomp = udom2;
                              break;
                        }
                        udomp = &udom->fwd;
                  }
                  break;

            case 'r':
                  parse_reply_arg(optarg);
                  break;

            case 'm':
                  mapfile_nm = optarg;
                  break;

            case 'w':
                  main_white_nm = optarg;
                  break;

            case 'U':
                  parse_userdirs(optarg);
                  break;

            case 'a':
                  if (!strcasecmp(optarg, "IGNORE")) {
                        action = CMN_IGNORE;
                  } else if (!strcasecmp(optarg, "REJECT")) {
                        action = CMN_REJECT;
                  } else if (!strcasecmp(optarg, "DISCARD")) {
                        action = CMN_DISCARD;
                  } else {
                        dcc_error_msg("unrecognized -a action: %s",
                                    optarg);
                  }
                  break;

            case 't':
                  if (dcc_parse_tholds('t', optarg))
                        log_tgts_set = 1;
                  break;

            case 'g':         /* honor not-spam "counts" */
                  dcc_parse_honor(optarg);
                  break;

            case 'S':
                  dcc_add_sub_hdr(0, optarg);
                  break;

            case 'l':         /* log rejected mail here */
                  logdir = optarg;
                  break;

            case 'R':
                  rundir = optarg;
                  break;

            case 'T':
                  if (optarg[0] == '\0')
                        dcc_error_msg("illegal temporary directory");
                  else
                        tmpdir = optarg;
                  break;

            case 'j':         /* maximum simultaneous jobs */
                  l = strtoul(optarg, &p, 0);
                  if (*p != '\0' || l < MIN_MAX_WORK) {
                        dcc_error_msg("invalid queue length %s",
                                    optarg);
                  } else if (l > max_max_work) {
                        dcc_error_msg("queue length %s"
                                    " larger than limit %d",
                                    optarg, max_max_work);
                        max_work = max_max_work;
                  } else {
                        max_work = l;
                  }
                  break;

            case 'B':
                  if (!dcc_parse_dnsbl(emsg, optarg))
                        dcc_error_msg("%s", emsg);
                  break;

            case 'X':
                  if (!dcc_parse_xfltr(emsg, optarg))
                        dcc_error_msg("%s", emsg);
                  break;

            case 'L':
                  dcc_parse_log_opt(optarg);
                  break;

            default:
                  usage(argv[optind-1], "");
            }
      }
      argc -= optind;
      argv += optind;
      if (argc != 0)
            usage(argv[0], "");

      if (!dcc_cdhome(emsg, homedir))
            dcc_error_msg("%s", emsg);

      if (proxy_out_family == AF_INET) {
            if (proxy_out_host[0] == '\0'
                || !strcmp(proxy_out_host, "@")) {
                  /* null or "@" means incoming host */
                  ;
            } else {
                  dcc_host_lock();
                  if (!dcc_get_host(proxy_out_host, use_ipv6, &error))
                        dcc_logbad(EX_NOHOST, "%s: %s",
                                 proxy_out_host,
                                 DCC_HSTRERROR(error));
                  proxy_out_su = dcc_hostaddrs[0];
                  *DCC_SU_PORT(&proxy_out_su) = proxy_out_port;
                  dcc_host_unlock();
            }
      }

      /* Open the incoming socket before our backgrounding fork() to
       * minimize races and allow better error reporting. */
      bind_listen();

      if (logdir) {
            if (!dcc_log_init(emsg, logdir))
                  dcc_error_msg("%s", emsg);
      } else if (log_tgts_set) {
            dcc_error_msg("log thresholds set with -t but no -l directory");
      } else if (userdirs != '\0') {
            dcc_error_msg("no -l directory prevents per-user"
                        " logging with -U");
      }

#ifdef RLIMIT_NOFILE
      if (old_rlim_cur < (i = max_work*FILES_PER_JOB+EXTRA_FILES)) {
            nofile.rlim_cur = i;
            if (0 > setrlimit(RLIMIT_NOFILE, &nofile)) {
                  dcc_error_msg("setrlimit(RLIMIT_NOFILE,%d): %s",
                              i, ERROR_STR());
                  max_work = old_rlim_cur/FILES_PER_JOB - EXTRA_FILES;
                  if (max_work <= 0) {
                        dcc_error_msg("only %d open files allowed",
                                    old_rlim_cur);
                        max_work = MIN_MAX_WORK;
                  }
            }
      }
#endif /* RLIMIT_NOFILE */

      helpers_init(max_work, progpath);

      if (background) {
            if (daemon(1, 0) < 0)
                  dcc_logbad(EX_OSERR, "daemon(): %s", ERROR_STR());

            dcc_daemon_restart(rundir, listen_soc, bind_listen);
            dcc_pidfile(pidpath, rundir);
      }

      signal(SIGPIPE, SIG_IGN);
      signal(SIGHUP, sigterm);
      signal(SIGTERM, sigterm);
      signal(SIGINT, sigterm);

      /* Be careful to start all threads only after the fork() in daemon(),
       * because some POSIX threads packages (e.g. FreeBSD) get confused
       * about threads in the parent.  */

      finish_replies();

      /* Create the contexts. */
      i = max_work;
      wp = dcc_malloc(sizeof(*wp)*i);
      work_free = wp;
      memset(wp, 0, sizeof(*wp)*i);
      while (--i > 0) {
            wp->proxy_in_soc = -1;
            wp->proxy_out_soc = -1;
            cmn_create(&wp->cw);
            wp->fwd = wp+1;
            ++wp;
      }
      wp->proxy_in_soc = -1;
      wp->proxy_out_soc = -1;
      cmn_create(&wp->cw);

      create_rcpt_sts(max_work*2);

      cmn_init(emsg);

      if (listen_family != AF_UNIX) {
            dcc_trace_msg(DCC_VERSION" listening to %s from %s",
                        listen_addr, raddr_rmask);
      } else {
            dcc_trace_msg(DCC_VERSION" listening to %s",
                        listen_addr);
      }
      if (dcc_clnt_debug)
            dcc_trace_msg("max_work=%d max_max_work=%d",
                        max_work, max_max_work);

      while (!stopping) {
            /* delay for 1 second instead of forever to notice
             * when SIGTERM has said to stop */
            i = dcc_select_poll(emsg, listen_soc, 1, DCC_USECS);
            if (i < 0)
                  dcc_logbad(EX_OSERR, "%s", emsg);
            if (i == 0)
                  continue;

            /* A new connection is ready.  Allocate a context
             * block and create a thread */
            lock_work();
            wp = work_free;
            if (!wp) {
                  /* pretend we weren't listening if we
                   * are out of context blocks */
                  unlock_work();
                  sleep(1);
                  continue;
            }
            work_free = wp->fwd;
            unlock_work();

            /* clear most of context block for the new connection */
            cmn_clear(&wp->cw, wp, 1);
            wp->cw.helo[0] = '\0';
            memset(&wp->WORK_ZERO, 0,
                   sizeof(*wp) - ((char*)&wp->WORK_ZERO - (char*)wp));

            namelen = sizeof(wp->proxy_in_su);
            wp->proxy_in_soc = accept(listen_soc,
                                &wp->proxy_in_su.sa, &namelen);
            if (wp->proxy_in_soc < 0) {
                  dcc_error_msg("accept(): %s", ERROR_STR());
                  job_close(wp);
                  continue;
            }
            if (listen_family != AF_UNIX) {
                  struct in6_addr addr6, *ap;

                  if (wp->proxy_in_su.sa.sa_family == AF_INET6) {
                        ap = &wp->proxy_in_su.ipv6.sin6_addr;
                  } else {
                        ap = &addr6;
                        dcc_ipv4toipv6(ap,
                                     wp->proxy_in_su.ipv4.sin_addr);
                  }
                  if (!DCC_IN_BLOCK(*ap, raddr, rmask)) {
                        char str[DCC_SU2STR_SIZE];
                        dcc_error_msg("unauthorized client address %s",
                                    dcc_su2str(str, sizeof(str),
                                          &wp->proxy_in_su));
                        job_close(wp);
                        continue;
                  }
            }
            if (!set_soc(emsg, wp->proxy_in_soc, listen_family, "MTA")) {
                  dcc_error_msg("%s", emsg);
                  job_close(wp);
                  continue;
            }
            i = pthread_create(&tid, 0, job_start, wp);
            if (i) {
                  dcc_error_msg("pthread_create(): %s", ERROR_STR1(i));
                  job_close(wp);
                  continue;
            }
            i = pthread_detach(tid);
            if (i != 0) {
               if (i != ESRCH)
                     dcc_error_msg("pthread_detach(): %s", ERROR_STR1(i));
               else if (dcc_clnt_debug)
                     dcc_trace_msg("pthread_detach(): %s", ERROR_STR1(i));
            }
      }

      close_listen_soc();

      totals_stop();

      if (pidpath[0] != '\0')
            unlink(pidpath);

      exit(stopping);
}



static void
unlink_listen_sun(void)
{
      if (listen_family != AF_UNIX)
            return;

      /* there is an unavoidable race here */
      if (0 > unlink(listen_sun.sun_path)
          && errno != ENOENT)
            dcc_error_msg("unlink(%s): %s",
                        listen_sun.sun_path, ERROR_STR());
}



static void
bind_unix_listen(void)
{
      DCC_EMSG emsg;
      struct stat sb;

#ifdef HP_UX_BAD_AF_UNIX
      dcc_logbad(EX_CONFIG, "HP-UX AF_UNIX does not support shutdown()");
#endif

      emsg[0] = '\0';
      listen_family = AF_UNIX;

      if (!listen_addr) {
            /* use default UNIX domain socket */
            snprintf(listen_sun.sun_path, sizeof(listen_sun.sun_path),
                   "%s/"DCC_DCCIF_UDS, dcc_homedir);
            listen_addr = listen_sun.sun_path;
      } else {
            if (strlen(listen_addr) >= ISZ(listen_sun.sun_path))
                  dcc_logbad(EX_USAGE, "invalid UNIX domain socket: %s",
                           listen_addr);
            strcpy(listen_sun.sun_path, listen_addr);
      }
#ifdef HAVE_SA_LEN
      listen_sun.sun_len = SUN_LEN(&listen_sun);
#endif
      listen_sun.sun_family = AF_UNIX;

      if (0 <= stat(listen_sun.sun_path, &sb)
          && !(S_ISSOCK(sb.st_mode) || S_ISFIFO(sb.st_mode)))
            dcc_logbad(EX_UNAVAILABLE, "non-socket present at %s",
                     listen_sun.sun_path);

      /* look for a daemon already using our socket */
      listen_soc = socket(AF_UNIX, SOCK_STREAM, 0);
      if (listen_soc < 0)
            dcc_logbad(EX_OSERR, "socket(AF_UNIX): %s", ERROR_STR());
      /* unlink it only if it looks like a dead socket */
      if (0 > connect(listen_soc, (struct sockaddr *)&listen_sun,
                  sizeof(listen_sun))) {
            if (errno == ECONNREFUSED || errno == ECONNRESET
                || errno == EACCES) {
                  unlink_listen_sun();
            } else if (dcc_clnt_debug > 2
                     && errno != ENOENT) {
                  dcc_trace_msg("connect(old server %s): %s",
                              listen_sun.sun_path, ERROR_STR());
            }
      } else {
            /* connect() worked so the socket is alive */
            dcc_logbad(EX_UNAVAILABLE,
                     "something already or still running with socket at"
                     " %s",
                     listen_sun.sun_path);
      }
      close(listen_soc);

      listen_soc = socket(AF_UNIX, SOCK_STREAM, 0);
      if (listen_soc < 0)
            dcc_logbad(EX_OSERR, "socket(AF_UNIX): %s", ERROR_STR());
      if (0 > bind(listen_soc, (struct sockaddr *)&listen_sun,
                 sizeof(listen_sun)))
            dcc_logbad(EX_IOERR, "bind(%s) %s",
                     listen_sun.sun_path, ERROR_STR());
      if (0 > chmod(listen_sun.sun_path, 0666))
            dcc_error_msg("chmod(%s, 0666): %s",
                        listen_sun.sun_path, ERROR_STR());
}



static void
bind_tcp_listen(void)
{
      DCC_EMSG emsg;
      char host[MAXHOSTNAMELEN];
      u_int16_t port;
      DCC_SOCKU su;
      const char *cp;
      char *rhost;
      int on, error;

      emsg[0] = '\0';

      rhost = strchr(listen_addr, ',');
      if (!rhost)
            dcc_logbad(EX_USAGE, "missing port number in \"%s\"",
                     listen_addr);
      rhost = strchr(rhost+1, ',');
      if (!rhost) {
            dcc_logbad(EX_USAGE, "missing rhost in \"%s\"",
                     listen_addr);
      }
      *rhost++ = '\0';
      error = dcc_str2cidr(emsg, &raddr, &rmask, rhost, 0, 0);
      if (error <= 0) {
            dcc_logbad(EX_USAGE, "invalid rhost and mask \"%s\"",
                     rhost);
      }
      raddr_rmask = rhost;

      cp = dcc_parse_nm_port(emsg, listen_addr, DCC_GET_PORT_INVALID,
                         host, sizeof(host), &port, 0, 0, 0, 0);
      if (!cp)
            dcc_logbad(dcc_ex_code, "%s", emsg);
      if (*cp != '\0')
            dcc_logbad(EX_USAGE, "invalid IP address: \"%s\"",
                     listen_addr);

      if (host[0] == '\0' || !strcmp(host, "@")) {
            /* null or "@" means INADDR_ANY */
            dcc_mk_su(&su, use_ipv6 ? AF_INET6: AF_INET, 0, port);
      } else {
            dcc_host_lock();
            if (!dcc_get_host(host, use_ipv6, &error))
                  dcc_logbad(EX_NOHOST, "%s: %s",
                           host, DCC_HSTRERROR(error));
            su = dcc_hostaddrs[0];
            *DCC_SU_PORT(&su) = port;
            dcc_host_unlock();
      }

      listen_soc = socket(su.sa.sa_family, SOCK_STREAM, 0);
      if (listen_soc < 0)
            dcc_logbad(EX_OSERR, "socket(): %s", ERROR_STR());

      on = 1;
      if (0 > setsockopt(listen_soc, SOL_SOCKET, SO_REUSEADDR,
                     &on, sizeof(on)))
            dcc_error_msg("setsockopt(listen %s, SO_REUSADDR): %s",
                        dcc_su2str_err(&su), ERROR_STR());

      if (0 > bind(listen_soc, &su.sa, DCC_SU_LEN(&su)))
            dcc_logbad(EX_UNAVAILABLE, "bind(%s) %s",
                     listen_addr, ERROR_STR());

      listen_family = su.sa.sa_family;
}



static u_char
set_soc(DCC_EMSG emsg, int s, int family, const char *sname)
{
      int on;

      if (0 > fcntl(s, F_SETFD, FD_CLOEXEC)) {
            dcc_pemsg(EX_IOERR, emsg,
                    "fcntl(%s, F_SETFD, FD_CLOEXEC): %s",
                    sname, ERROR_STR());
            return 0;
      }

      if (family != AF_UNIX) {
            on = 1;
            if (0 > setsockopt(s, SOL_SOCKET, SO_KEEPALIVE,
                           &on, sizeof(on))) {
                  dcc_pemsg(EX_IOERR, emsg,
                          "setsockopt(%s, SO_KEEPALIVE): %s",
                          sname, ERROR_STR());
                  return 0;
            }
      }

      /* use non-blocking sockets so that we can read entire lines
       * without knowing how big they are or expecting the operating
       * system to allow peeking at its buffers */
      if (-1 == fcntl(s, F_SETFL,
                  fcntl(s, F_GETFL, 0) | O_NONBLOCK)) {
            dcc_pemsg(EX_OSERR, emsg, "fcntl(%s, O_NONBLOCK): %s",
                    sname, ERROR_STR());
            return 0;
      }

      return 1;
}



static void
bind_listen(void)
{
      DCC_EMSG emsg;
      char *p;

      /* It is a TCP address if it has a port number and mask.
       * Otherwise it is a UNIX domain socket.
       */
      if (listen_addr != 0
          && (p = strchr(listen_addr, ',')) != 0
          && strchr(p, ',')) {
            bind_tcp_listen();
      } else {
            bind_unix_listen();
      }

      if (!set_soc(emsg, listen_soc, listen_family, "main socket"))
            dcc_logbad(dcc_ex_code, "%s", emsg);
      if (0 > listen(listen_soc, 10))
            dcc_logbad(EX_IOERR, "listen(): %s", ERROR_STR());
}



static u_char                       /* 0=EOF, 1=read something */
soc_read(WORK *wp, IN_BC *bc)
{
      int fspace, len, total, i;

      if (bc->out >= bc->in)
            bc->out = bc->in = bc->base;

      fspace = &bc->base[bc->size] - bc->in;
      if (fspace < bc->size/8) {
            if (bc->out == bc->base) {
                  thr_error_msg(&wp->cw, "buffer overrun; in=%d",
                              (int)(bc->in - bc->base));
                  job_exit(wp);
            }
            len = bc->in - bc->out;
            memmove(bc->base, bc->out, len);
            bc->out = bc->base;
            bc->in = bc->out+len;
            fspace = bc->size - len;
      }

      if (wp->flags & FG_WORK_LOCK)
            unlock_work();

      for (;;) {
            i = dcc_select_poll(wp->cw.emsg, *bc->socp, 1,
                            MAX_MTA_DELAY*DCC_USECS);
            if (i > 0)
                  break;
            if (i < 0) {
                  thr_error_msg(&wp->cw, "%s", wp->cw.emsg);
            } else {
                  thr_error_msg(&wp->cw, "MTA read timeout");
            }
            job_exit(wp);
      }
      total = read(*bc->socp, bc->in, fspace);
      if (total < 0) {
            if (!proxy || dcc_clnt_debug)
                  thr_error_msg(&wp->cw, "read(sock): %s", ERROR_STR());
            job_exit(wp);
      }
      bc->in += total;

      if (wp->flags & FG_WORK_LOCK)
            lock_work();
      return total != 0;
}



/* ensure there is another line in the buffer */
static u_char                       /* 0=eof or buffer overflow */
msg_read_line(WORK *wp, IN_BC *bc)
{
      int i;
      char *p;

      for (;;) {
            p = bc->out;
            i = bc->in - p;
            if (i != 0) {
                  /* look for <LF> for ASCII protocol
                   * or <CR><LF> for SMTP */
                  p = memchr(p, '\n', i);
                  if (p
                      && (!proxy
                        || (p > bc->out && *(p-1) == '\r'))) {
                        bc->next_line = p+1;
                        return 1;
                  }
            }

            if (!soc_read(wp, bc))
                  return 0;
      }
}



static void
buf_write_flush(WORK *wp, OUT_BC *bc)
{
      const char *buf;
      int len, i;

      len = bc->len;
      if (!len)
            return;
      bc->len = 0;

      if (*bc->socp < 0)
            dcc_logbad(EX_SOFTWARE, "attempt to write closed socket");

      buf = bc->base;
      do {
            i = dcc_select_poll(wp->cw.emsg, *bc->socp, 0,
                            MAX_MTA_DELAY*DCC_USECS);
            if (i < 0) {
                  thr_error_msg(&wp->cw, "%s", wp->cw.emsg);
                  job_exit(wp);
            }
            if (i == 0) {
                  thr_error_msg(&wp->cw, "MTA write timeout");
                  job_exit(wp);
            }
            i = write(*bc->socp, buf, len);
            if (i < 0) {
                  if (DCC_BLOCK_ERROR())
                        continue;
                  thr_error_msg(&wp->cw, "write(MTA socket,%d): %s",
                              len, ERROR_STR());
                  job_exit(wp);
            }
            if (i == 0) {
                  thr_error_msg(&wp->cw, "write(MTA socket,%d)=%d",
                              len, i);
                  job_exit(wp);
            }
            buf += i;
            len -= i;
      } while (len > 0);
}



static void
buf_write(WORK *wp, OUT_BC *bc, const void *buf, u_int len)
{
      u_int n;

      for (;;) {
            n = bc->size - bc->len;
            if (n > len)
                  n = len;
            memcpy(&bc->base[bc->len], buf, n);
            if ((bc->len += n) >= bc->size)
                  buf_write_flush(wp, bc);
            if ((len -= n) == 0)
                  return;
            buf = (void *)((char *)buf + n);
      }
}



static void
tmp_write(WORK *wp, const void *buf, int len)
{
      if (!cmn_write_tmp(&wp->cw, buf, len)) {
            thr_error_msg(&wp->cw, "%s", wp->cw.emsg);
            job_exit(wp);
      }
}



static int
tmp_read(WORK *wp, void *buf, int len)
{
      int i;

      if (wp->cw.tmp_fd < 0)
            return 0;

      i = read(wp->cw.tmp_fd, buf, len);
      if (i < 0) {
            thr_error_msg(&wp->cw, "read(%s,%d): %s",
                        wp->cw.tmp_nm, len, ERROR_STR());
            job_exit(wp);
      }
      return i;
}



/* fill MTA read buffer from temporary file */
static int
tmp_read_msg_in(WORK *wp)
{
      int i;

      /* preserving what is already in it */
      i = wp->msg_rd.in - wp->msg_rd.out;
      if (i > 0)
            memmove(wp->msg_rd.base, wp->msg_rd.out, i);
      wp->msg_rd.out = wp->msg_rd.base;
      wp->msg_rd.in = wp->msg_rd.out+i;

      wp->msg_rd.in += tmp_read(wp, wp->msg_rd.in, wp->msg_rd.size - i);
      return wp->msg_rd.in > wp->msg_rd.out;
}



static void
job_close(WORK *wp)
{
      if (wp->flags & FG_WORK_LOCK) {
            wp->flags &= ~FG_WORK_LOCK;
            unlock_work();
      }

      wp->msg_rd.socp = 0;
      if (wp->msg_wt.socp) {
            buf_write_flush(wp, &wp->msg_wt);
            wp->msg_wt.socp = 0;
      }
      wp->reply_in.socp = 0;
      if (wp->reply_out.socp) {
            buf_write_flush(wp, &wp->reply_out);
            wp->reply_out.socp = 0;
      }

      cmn_close_tmp(&wp->cw);

      if (wp->proxy_in_soc >= 0) {
            if (0 > close(wp->proxy_in_soc))
                  thr_error_msg(&wp->cw, "close(socket): %s",
                              ERROR_STR());
            wp->proxy_in_soc = -1;
      }
      if (wp->proxy_out_soc >= 0) {
            if (0 > close(wp->proxy_out_soc))
                  thr_error_msg(&wp->cw, "close(socket): %s",
                              ERROR_STR());
            wp->proxy_out_soc = -1;
      }
      log_stop(&wp->cw);

      lock_work();
      free_rcpt_sts(&wp->cw, 0);
      wp->fwd = work_free;
      work_free = wp;
      unlock_work();
}



static void NRATTRIB
job_exit(WORK *wp)
{
      job_close(wp);
      pthread_exit(0);
      /* mostly to suppress warning */
      dcc_logbad(EX_OSERR, "pthread_exit() returned");
}



/* write headers or body data from the MTA read buffer to the MTA */
static void
mta_write(WORK *wp, char *end)
{
      int len;

      len = end - wp->msg_rd.out;
      if (len <= 0)
            return;
      buf_write(wp, &wp->msg_wt, wp->msg_rd.out, len);
      wp->msg_rd.out = end;
}



/* copy headers from the temporary file to the MTA */
static void
hdrs_copy(WORK *wp)
{
      enum {START_HDR, SKIP_HDR, COPY_HDR} cmode;
      const char *nl;
      char *bol, *eol;
      int i;

      if (-1 == lseek(wp->cw.tmp_fd, 0, SEEK_SET)) {
            thr_error_msg(&wp->cw, "rewind %s: %s",
                        wp->cw.tmp_nm, ERROR_STR());
            job_exit(wp);
      }

      cmode = START_HDR;
      wp->msg_rd.in = wp->msg_rd.out = wp->msg_rd.base;
      for (;;) {
            /* fill wp->msg_rd while keeping anything already present */
            if (!tmp_read_msg_in(wp))
                  return;

            bol = wp->msg_rd.out;
            while ((i = wp->msg_rd.in - bol) > 0) {
                  /* Find the end of the next line. */
                  eol = memchr(bol, '\n', i);
                  if (!eol) {
                        /* Fill the buffer if we can't find '\n''
                         * and the buffer has room */
                        if (i < wp->msg_rd.size
                            && wp->msg_rd.out != wp->msg_rd.base)
                              break;
                        /* pretend the header ended with the buffer
                         * if there is no room */
                        eol = wp->msg_rd.in-1;
                        nl = 0;
                  } else if (eol+1 >= wp->msg_rd.in) {
                        /* get the character after '\n' */
                        if (i < wp->msg_rd.size
                            && wp->msg_rd.out != wp->msg_rd.base)
                              break;
                        /* pretend we could not find it if there
                         * is no room in the buffer
                         * or if we are at end of file.
                         * This will also end the headers */
                        nl = 0;
                  } else {
                        nl = eol+1;
                  }

                  if (cmode == START_HDR) {
                        /* We are at start of a header or the body.
                         * Quit before line of "\n" or "\r\n" */
                        if (eol == bol
                            || (eol == bol+1 && *bol == '\r')) {
                              /* write any preceding lines */
                              mta_write(wp, bol);
                              return;
                        }

                        /* Look for our header
                         * Assume the buffer is larger than the
                         * largest possible X-DCC header. */
                        if (bol[wp->cw.xhdr_len] == ':'
                            && !strncasecmp(bol, wp->cw.xhdr,
                                        wp->cw.xhdr_len)
                            && chghdr != ADDHDR) {
                              /* Found it and we want to skip it.
                               * Copy any preceding headers. */
                              mta_write(wp, bol);
                              cmode = SKIP_HDR;
                        } else {
                              cmode = COPY_HDR;
                        }
                  }
                  if (cmode == SKIP_HDR)
                        wp->msg_rd.out = eol+1;

                  /* Check the character after '\n' for
                   * whitespace indicating continuation */
                  if (nl && *nl  != ' ' && *nl != '\t') {
                        cmode = START_HDR;
                        /* deal with SMTP transparency */
                        if (proxy && *nl == '.')
                              *eol-- = '.';
                  }

                  bol = eol+1;
            }

            mta_write(wp, bol);
      }
}



static void
add_hdr(void *wp0, const char *buf, u_int buf_len)
{
      WORK *wp = wp0;

      buf_write(wp, &wp->msg_wt, buf, buf_len);
}



static void
body_copy(WORK *wp)
{
      u_char seen_crlf;
      char *p;

      hdrs_copy(wp);

      if (chghdr != NOHDR) {
            /* write headers
             *    end them with "\r\n" if at least
             *    half of the header lines ended that way */
#ifdef USE_XFLTR
            /* write external filter's header */
            if (wp->cw.xfltr_header_len)
                  dcc_write_header(add_hdr, wp, wp->cw.xfltr_header,
                               wp->cw.xfltr_header_len,
                               wp->cr_hdrs > wp->total_hdrs/2);
#endif
            if (wp->cw.header.buf[0] != '\0')
                  dcc_write_header(add_hdr, wp, wp->cw.header.buf,
                               wp->cw.header.used,
                               wp->cr_hdrs > wp->total_hdrs/2);
      }

      /* copy body */
      seen_crlf = 2;
      do {
            p = wp->msg_rd.out;
            while (p < wp->msg_rd.in) {
                  if (seen_crlf == 1) {
                        seen_crlf = (*p++ == '\n') ? 2 : 0;
                        continue;
                  }
                  if (seen_crlf == 2) {
                        seen_crlf = 0;
                        if (*p == '.' && proxy) {
                              mta_write(wp, p);
                              buf_write(wp, &wp->msg_wt, ".", 1);
                        }
                  }

                  if (!proxy)
                        break;
                  p = memchr(p, '\r', wp->msg_rd.in-p);
                  if (!p)
                        break;
                  seen_crlf = 1;
                  ++p;
            }
            mta_write(wp, wp->msg_rd.in);
      } while (tmp_read_msg_in(wp));
}



static void
close_listen_soc(void)
{
      if (listen_soc < 0)
            return;

      unlink_listen_sun();
      if (0 > close(listen_soc))
            dcc_error_msg("close(main socket): %s",
                        ERROR_STR());
      listen_soc = -1;
}



/* watch for fatal signals */
static void
sigterm(int sig)
{
      stopping = 100 + sig;
      unlink_listen_sun();
      dcc_clnt_stop_resolve();
      signal(sig, SIG_DFL);         /* quit on repeated signals */
}



void
user_reject_discard(UATTRIB CMN_WORK *cwp, UATTRIB RCPT_ST *rcpt_st)
{
      /* dccifd has no way to tell the MTA to remove a recipient
       * after the Rcpt_To command */
      dcc_error_msg("trying discard for an individual target");
}



static void
get_helo(WORK *wp, const char *vp, int i)
{
      if (i < DCC_HELO_MAX-1) {
            memcpy(wp->cw.helo, vp, i);
            wp->cw.helo[i] = '\0';
      } else {
            /* if the HELO value is too long, capture what we can
             * and indicate that we got only part of it */
            i = DCC_HELO_MAX-1;
            memcpy(wp->cw.helo, vp, i);
            strcpy(&wp->msg_rd.out[DCC_HELO_MAX-ISZ(DCC_HELO_CONT)],
                   DCC_HELO_CONT);
      }
}



/* Get the next header field
 *      The line is copied to the temporary file and then null terminated
 *      in the buffer */
static u_char                       /* 1=have one, 0=end of headers */
get_hdr(WORK *wp)
{
      int tf_len;             /* header bytes written to temp file */
      int hdr_len;                  /* total header length */
      int nlen;               /* length of next chunk of header */
      char *np;               /* next byte after field */
      char *p;
      u_char at_bol;                /* 1=at start of a line of header */

      tf_len = 0;
      hdr_len = 0;
      at_bol = 1;
      for (;;) {
            /* get another line of the header */
            np = wp->msg_rd.out + hdr_len;
            nlen = wp->msg_rd.in - np;
            if (nlen <= 1) {
                  /* we do not have enough data */
read_more:;
                  if (hdr_len > DCC_HDR_CK_MAX) {
                        /* we already have more than DCC_HDR_CK_MAX
                         * bytes of the header.
                         * Keep only DCC_HDR_CK_MAX bytes of it
                         * in the buffer.  We will eventually
                         * return only the first DCC_HDR_CK_MAX
                         * bytes and an arbitrary part of the tail
                         * to our caller. */
                        if (tf_len < hdr_len)
                              tmp_write(wp, wp->msg_rd.out+tf_len,
                                      hdr_len - tf_len);
                        /* discard all but the first DCC_HDR_CK_MAX
                         * bytes of the header */
                        hdr_len = DCC_HDR_CK_MAX;
                        tf_len = DCC_HDR_CK_MAX;
                        if (nlen != 0)
                              wp->msg_rd.out[DCC_HDR_CK_MAX] = *np;
                        wp->msg_rd.in = (wp->msg_rd.out + DCC_HDR_CK_MAX
                                     + nlen);
                  }
                  /* Get more data, possibly sliding what we already
                   * have down in the buffer.  That would move
                   * wp->msg_rd.in to wp->msg_rd.base. */
                  if (!soc_read(wp, &wp->msg_rd)) {
                        /* EOF implies the message body including the
                         * separating blank line is missing.
                         * That is impossible for the ASCII dccifd
                         * protocol because there should be at least
                         * a Received header.
                         * When running as a proxy, we should get
                         * "\r\n.\r\n"
                         * Fake newlines until we get out of here
                         * so that we will log whatever we got */
                        wp->flags |= FG_MISSING_BODY;
                        if (wp->cr_hdrs >= wp->total_hdrs/2)
                              *wp->msg_rd.in++ = '\r';
                        *wp->msg_rd.in++ = '\n';
                  }
                  continue;
            }

            if (at_bol) {
                  /* deal with SMTP transparency */
                  if (*np == '.' && proxy) {
                        if (nlen < 3)
                              goto read_more;
                        if (np[1] != '\r' || np[2] != '\n') {
                              memmove(np, np+1, --nlen);
                        } else if (hdr_len == 0) {
                              /* ".\r\n" at the start of a line
                               * ends the message. In this case
                               * the message body including the
                               * separating blank line is missing.
                               * Stop short before the ".\r\n" */
                              return 0;
                        }
                  }

                  /* stop before the next line if it is
                   * not a continuation of the current header */
                  if (hdr_len != 0
                      && *np != ' '
                      && *np != '\t') {
                        if (tf_len < hdr_len)
                              tmp_write(wp, wp->msg_rd.out+tf_len,
                                      hdr_len - tf_len);
                        wp->msg_rd.out[hdr_len-1] = '\0';
                        if (hdr_len > 1
                            && wp->msg_rd.out[hdr_len-2] == '\r')
                              ++wp->cr_hdrs;
                        wp->msg_rd.next_line = &wp->msg_rd.out[hdr_len];
                        return 1;
                  }
            }

            /* find the end of the next line */
            p = memchr(np, '\n', nlen);
            if (p) {
                  hdr_len += ++p - np;
                  /* quit at the end of headers */
                  if (hdr_len == 1
                      || (hdr_len == 2 && *wp->msg_rd.out == '\r'))
                        return 0;
                  at_bol = 1;
            } else {
                  hdr_len += nlen;
                  at_bol = 0;
            }
      }
}



static void
get_hdrs(WORK *wp)
{
      const char *p, *p2, *rh;

      for (;;) {
            /* stop at the separator between the body and headers */
            if (!get_hdr(wp))
                  break;
            ++wp->total_hdrs;

#define GET_HDR_CK(h,t) {                                   \
                        if (!CSTRCMP(wp->msg_rd.out, h)) {        \
                        dcc_get_cks(&wp->cw.cks, DCC_CK_##t,    \
                                  &wp->msg_rd.out[STRZ(h)], 1); \
                        wp->flags |= FG_SEEN_HDR;           \
                        wp->msg_rd.out = wp->msg_rd.next_line;  \
                        continue;}}
            GET_HDR_CK(DCC_XHDR_TYPE_FROM":", FROM);
            GET_HDR_CK(DCC_XHDR_TYPE_MESSAGE_ID":", MESSAGE_ID);
#undef GET_HDR_CK

            if (!CSTRCMP(wp->msg_rd.out, "Return-Path:")) {
                  parse_return_path(&wp->msg_rd.out[STRZ("Return-Path:")],
                                &wp->cw.cks,
                                wp->cw.env_from);
                  wp->flags |= FG_SEEN_HDR;
                  wp->msg_rd.out = wp->msg_rd.next_line;
                  continue;
            }

            /* notice UNIX From_ line */
            if (!(wp->flags & FG_SEEN_HDR)
                && wp->cw.env_from[0] == '\0'
                && !strncmp(wp->msg_rd.out, "From ", STRZ("From "))) {
                  p = &wp->msg_rd.out[STRZ("From ")];
                  p += strspn(p, " ");
                  p2 = strchr(p, ' ');
                  if (p2 != 0) {
                        if (wp->cw.cks.sums[DCC_CK_ENV_FROM].type
                            == DCC_CK_INVALID) {
                              if (p2 > p+sizeof(wp->cw.env_from))
                                  p2 = p+sizeof(wp->cw.env_from);
                              memcpy(wp->cw.env_from, p, p2-p);
                              wp->cw.env_from[p2-p] = '\0';
                        }
                        wp->flags |= FG_SEEN_HDR;
                        wp->msg_rd.out = wp->msg_rd.next_line;
                        continue;
                  }
            }

            if (!CSTRCMP(wp->msg_rd.out, DCC_XHDR_TYPE_RECEIVED":")) {
                  p = &wp->msg_rd.out[STRZ(DCC_XHDR_TYPE_RECEIVED":")];

                  /* compute checksum of the last Received: header */
                  dcc_get_cks(&wp->cw.cks, DCC_CK_RECEIVED, p, 1);

                  wp->flags |= FG_SEEN_HDR;
                  wp->msg_rd.out = wp->msg_rd.next_line;

                  /* pick IP address out of first Received: header */
                  if (!(wp->flags & FG_PARSE_RCVD)
                      || --wp->parse_rcvd >= 0)
                        continue;
                  rh = parse_received(p, &wp->cw.cks,
                                  (wp->cw.helo[0] == '\0')
                                  ? wp->cw.helo : 0,
                                  sizeof(wp->cw.helo),
                                  wp->cw.sender_str,
                                  sizeof(wp->cw.sender_str),
                                  wp->cw.sender_name,
                                  sizeof(wp->cw.sender_name));
                  if (rh == 0) {
                        /* to avoid being fooled by forged
                         * headers, stop at strange one */
                        wp->flags &= ~FG_PARSE_RCVD;

                  } else if (*rh != '\0') {
                        thr_log_print(&wp->cw, 1,
                                    "skip %s Received: header\n", rh);

                  } else if (!check_mx_listing(&wp->cw)) {
                        /* we know the client */
                        wp->flags &= ~FG_PARSE_RCVD;
                  }
                  continue;
            }

            /* Notice MIME multipart boundary definitions */
            dcc_ck_mime_hdr(&wp->cw.cks, wp->msg_rd.out, 0);

            if (dcc_ck_get_sub(&wp->cw.cks, wp->msg_rd.out, 0))
                  wp->flags |= FG_SEEN_HDR;

            /* notice any sort of header */
            if (!(wp->flags & FG_SEEN_HDR)) {
                  for (p = wp->msg_rd.out; ; ++p) {
                        if (*p == ':') {
                              wp->flags |= FG_SEEN_HDR;
                              break;
                        }
                        if (*p <= ' ' || *p >= 0x7f)
                              break;
                  }
            }

            wp->msg_rd.out = wp->msg_rd.next_line;
      }

      /* Create a checksum for a null Message-ID header if there
       * was no Message-ID header.  */
      if (wp->cw.cks.sums[DCC_CK_MESSAGE_ID].type != DCC_CK_MESSAGE_ID)
            dcc_get_cks(&wp->cw.cks, DCC_CK_MESSAGE_ID, "", 0);
}



/* assume for now that the sender is the SMTP client */
static void
get_sender(WORK *wp)
{
      wp->cw.sender_addr = wp->cw.clnt_addr;
      strcpy(wp->cw.sender_name, wp->cw.clnt_name);
      strcpy(wp->cw.sender_str, wp->cw.clnt_str);
      if (wp->cw.sender_str[0] == '\0'
          || check_mx_listing(&wp->cw)) {
            /* try Received: header if client is unknown or MX server*/
            wp->flags |= FG_PARSE_RCVD;
      }
}



static u_char                       /* 0=temporary failure, 1=ok */
get_body(WORK *wp)
{
      char *p;
      char buf[1024];
      u_char bol;
      int buflen, i;

      /* We must make a copy of the entire message in a temporary file
       * if the MTA wants a copy with the X-DCC header added
       * Copy the headers to a temporary file because the official
       * log file needs the SMTP client IP address and envelope information
       * before the header lines.  The log file needs all of the header
       * lines including stray X-DCC lines, but those lines must be removed
       * from the output file. */
      if (!cmn_open_tmp(&wp->cw, xfltr_parm == 0)) {
            dcc_error_msg("%s", wp->cw.emsg);
            job_close(wp);
      }

      /* open the connection to the nearest DCC server
       * and figure out our X-DCC header */
      if (!ck_dcc_ctxt(&wp->cw)) {
            /* failed to create context */
            make_reply(&wp->cw.reply,  &dcc_fail_reply, &wp->cw);
            return 0;
      }

      /* Get ready for the body checksums before the headers so that
       * we can notice the MIME separator */
      dcc_ck_body_init(&wp->cw.cks);
      dcc_dnsbl_init(&wp->cw.cks,  &wp->cw.dnsbl_work,
                   wp->cw.dcc_ctxt, &wp->cw, wp->cw.id);
      wp->cw.cmn_fgs |= CMN_FG_LOG_EARLY;

      get_hdrs(wp);

      if (wp->cw.env_from[0] == '\0'
          || (p = strchr(wp->cw.env_from, '@')) == 0) {
            wp->cw.mail_host[0] = '\0';
      } else  {
            BUFCPY(wp->cw.mail_host, p+1);
            p = strchr(wp->cw.mail_host, '>');
            if (p)
                  *p = '\0';
            if (strpbrk(wp->cw.mail_host, ";@,"))
                  wp->cw.mail_host[0] = '\0';
      }

      /* log IP address and so forth that we may have collected from
       * the headers or Postfix XFORWARD ESTMP extension */
      thr_log_envelope(&wp->cw, 0);

      /* check DNS blacklists for STMP client and envelope sender */
      if (wp->cw.cks.dnsbl) {
            if (wp->cw.cks.sums[DCC_CK_IP].type == DCC_CK_IP)
                  dcc_sender_dnsbl(wp->cw.cks.dnsbl, &wp->cw.cks.ip_addr);
            if (wp->cw.mail_host[0] != '\0')
                  dcc_mail_host_dnsbl(wp->cw.cks.dnsbl, wp->cw.mail_host);
      }

      /* copy headers from tmp file to log file */
      if (wp->cw.log_fd >= 0) {
            if (0 > lseek(wp->cw.tmp_fd, 0, SEEK_SET)) {
                  thr_error_msg(&wp->cw, "rewind %s: %s",
                              wp->cw.tmp_nm, ERROR_STR());
                  job_exit(wp);
            }
            for (;;) {
                  buflen = tmp_read(wp, buf, sizeof(buf));
                  if (buflen <= 0)
                        break;
                  log_body_write(&wp->cw, buf, buflen);
            }
      }
#ifdef USE_XFLTR
      if (!(wp->flags & FG_MTA_BODY)
          && !xfltr_parm)
            cmn_close_tmp(&wp->cw);
#endif

      if (wp->flags & FG_MISSING_BODY)
            thr_error_msg(&wp->cw, "missing message body");

      /* collect the body */
      bol = 1;
      for (;;) {
            buflen = wp->msg_rd.in - wp->msg_rd.out;
            if (buflen <= 0) {
                  if (!soc_read(wp, &wp->msg_rd))
                        break;
                  buflen = wp->msg_rd.in - wp->msg_rd.out;
            }

            /* deal with SMTP transparency and detect end of message */
            if (proxy) {
                  if (bol) {
                        /* We are at the beginning of a line.
                         * There will always be at least 3 more bytes
                         * in the message, ".\r\n" */
                        if (buflen < 3) {
                              if (!soc_read(wp, &wp->msg_rd)) {
                                  thr_error_msg(&wp->cw,
                                          "truncated message");
                                  job_exit(wp);
                              }
                              buflen = wp->msg_rd.in - wp->msg_rd.out;
                        }
                        /* Whether a '.' is the end of the data
                         * or an escaped '.', we will discard it. */
                        if (*wp->msg_rd.out == '.'
                            && buflen >= 1) {
                              ++wp->msg_rd.out;
                              --buflen;
                              /* if it is followed by "\r\n", then
                               * we have the end of the message */
                              if (buflen >= 2
                                  && wp->msg_rd.out[0] == '\r'
                                  && wp->msg_rd.out[1] == '\n') {
                                  wp->msg_rd.out += 2;
                                  break;
                              }
                        }
                        /* we are still at the beginning of a line */
                  }

                  /* check all of the lines in the buffer */
                  p = wp->msg_rd.out;
                  for (;;) {
                        i = p - wp->msg_rd.out;
                        p = memchr(p, '\r', buflen-i);
                        if (!p) {
                              /* We have checked all of the buffer.
                               * It does not end near an end of line.
                               * So log the block of body lines
                               * and leave the part of a line
                               * ending the buffer for later. */
                              bol = 0;
                              break;
                        }
                        /* We have found '\r'
                         * Maybe it will be followed by '\n' */
                        i = ++p - wp->msg_rd.out;
                        if (i+2 >= buflen) {
                              /* '\r' ends or almost ends buffer. */
                              bol = 0;
                              buflen = i-1;
                              if (buflen > 0) {
                                  /* The buffer ends with "\rX"
                                   * and contains text before '\r'
                                   * Process up to the '\r' */
                                  break;
                              }
                              /* buffer starts with "\r" */
                              if (!soc_read(wp, &wp->msg_rd)) {
                                  thr_error_msg(&wp->cw,
                                          "truncated message");
                                  job_exit(wp);
                              }
                              p = wp->msg_rd.out;
                              buflen = wp->msg_rd.in - p;

                        } else if (*p == '\n') {
                              /* We have "\r\n"
                               * If "\r\n" is followed by '.',
                               * then process up to the '.' */
                              if (*++p == '.') {
                                  buflen = i+1;
                                  bol = 1;
                                  break;
                              }
                        }
                  }
            }

            /* Log the body block */
            log_body_write(&wp->cw, wp->msg_rd.out, buflen);

            if ((wp->flags & FG_MTA_BODY)
                || xfltr_parm)
                  tmp_write(wp, wp->msg_rd.out, buflen);

            dcc_ck_body(&wp->cw.cks, wp->msg_rd.out, buflen);
            wp->msg_rd.out += buflen;
      }
      dcc_ck_body_fin(&wp->cw.cks);

      LOG_CAPTION(wp, DCC_LOG_MSG_SEP);
      thr_log_late(&wp->cw);

      /* check the grey and white lists */
      cmn_ask_white(&wp->cw,
                  (wp->flags & FG_MTA_GREY_QUERY) != 0);

      wp->cw.header.buf[0] = '\0';
      wp->cw.header.used = 0;
      if (wp->cw.tgts != wp->cw.white_tgts) {
            /* Report to the DCC and add our header if allowed.
             * After serious errors, act as if DCC server said not-spam
             * but remove our X-DCC header */
            i = cmn_ask_dcc(&wp->cw);
            if (i <= 0) {
                  if (!i && try_extra_hard) {
                        make_reply(&wp->cw.reply,
                                 &dcc_fail_reply, &wp->cw);
                        return 0;   /* temporary failure */
                  }
                  /* after unrecoverable errors, act as if DCC server
                   * said not-spam but without a header */
                  chghdr = NOHDR;
            }

      } else {
            /* if we cannot ask the DCC server because the message is
             * whitelisted, then use the local target count to decide
             * whether to log the mail message */
            dcc_honor_log_cnts(&wp->cw.ask_st, &wp->cw.cks, wp->cw.tgts);
      }

      totals.tgts += wp->cw.tgts;

      return 1;
}



/* ensure there is another line, trim its terminal "\r\n",
 *      and add a terminal '\0'
 * This cannot be used with the proxy code because it often needs to
 *    send the original buffer downstream. */
static int                    /* # of bytes available */
ascii_read_line(WORK *wp)
{
      char *p;

      if (!msg_read_line(wp, &wp->msg_rd)) {
            thr_error_msg(&wp->cw, "truncated request");
            job_exit(wp);
      }

      p = wp->msg_rd.next_line-1;
      for (;;) {
            *p-- = '\0';
            if (p < wp->msg_rd.out)
                  return 0;
            if (*p != '\r')
                  return p+1 - wp->msg_rd.out;
      }
}



/* Look for a string from the MTA
 *      while ignoring case and skipping whitespace
 *      The next line must already be in the buffer */
static u_char                       /* 1=matched it */
ascii_opt_str(WORK *wp, const char *st, int stlen)
{
      /* skip initial white space */
      while (wp->msg_rd.out < wp->msg_rd.next_line
             && (*wp->msg_rd.out == '\t' || *wp->msg_rd.out == ' '))
            ++wp->msg_rd.out;

      if (stlen <= wp->msg_rd.next_line - wp->msg_rd.out
          && !strncasecmp(wp->msg_rd.out, st, stlen)) {
            if (wp->msg_rd.out[stlen] == '\0') {
                  wp->msg_rd.out += stlen;
                  return 1;
            }

            /* skip trailing whitespace */
            if (wp->msg_rd.out[stlen] == '\t'
                || wp->msg_rd.out[stlen] == ' ') {
                  do {
                        ++stlen;
                  } while (wp->msg_rd.out[stlen] == '\t'
                         || wp->msg_rd.out[stlen] == ' ');
                  wp->msg_rd.out += stlen;
                  return 1;
            }
      }

      return 0;
}



static u_char                       /* 0=bad recipient */
check_addr(WORK *wp,
         const char **addrp, int *addr_lenp,
         const char **userp, int *user_lenp)
{
      USER_DOMAIN *udom;
      const char *addr, *cp;
      int addr_len, i;

      addr = *addrp;
      addr_len = *addr_lenp;
      if (*addr == '<' && addr_len >= 2 && addr[addr_len-1] == '>') {
            ++addr;
            addr_len -= 2;
            if (addr_len == 0) {
                  if (wp->flags & FG_WORK_LOCK)
                        unlock_work();
                  thr_error_msg(&wp->cw, "null recipient address");
                  if (wp->flags & FG_WORK_LOCK)
                        lock_work();
                  return 0;
            }
      }

      /* strip source route from recipient */
      while (addr_len != 0
             && (cp = memchr(addr, ',', addr_len)) != 0) {
            ++cp;
            addr_len -= cp-addr;
            addr = cp;
            *addr_lenp = addr_len;
            *addrp = addr;
      }

      if (addr_len >= RCTP_MAXNAME) {
            if (wp->flags & FG_WORK_LOCK)
                  unlock_work();
            thr_error_msg(&wp->cw, "recipient \"%s\" is too long", addr);
            if (wp->flags & FG_WORK_LOCK)
                  lock_work();
            return 0;
      }
      if (addr_len <= 0) {
            if (wp->flags & FG_WORK_LOCK)
                  unlock_work();
            thr_error_msg(&wp->cw, "null recipient");
            if (wp->flags & FG_WORK_LOCK)
                  lock_work();
            return 0;
      }

      /* use the list of local domain names to guess a user name
       * from the recipient address */
      if (!*user_lenp) {
            /* take the whole recipient name if it does not
             * include a domain name */
            if (!memchr(addr, '@', addr_len)) {
                  *userp = addr;
                  *user_lenp = addr_len;
                  return 1;
            }

            for (udom = user_domains; udom; udom = udom->fwd) {
                  i = addr_len - udom->len;
                  if (i > 0
                      && !strncasecmp(addr+i, udom->nm,
                                  udom->len)) {
                        if (*udom->nm == '@'
                            || *udom->nm == '.') {
                              ;   /* got it */
                        } else {
                              /* match must start at '.' or '@'
                               * if target did not start with
                               * '.' or '@' then the name must
                               * end with '.' or '@' */
                              if (i-- < 2
                                  || (addr[i] != '.'
                                    && addr[i] != '@'))
                                  continue;
                        }
                        /* we have something like user
                         * or user@sub
                         * from user@sub.domain.com */
                        *userp = addr;
                        *user_lenp = i;
                        return 1;
                  }
            }
      }

      return 1;
}



/* The mutex must be locked */
static RCPT_ST *
set_rcpt(WORK *wp,
       const char *rcpt, int rcpt_len,
       const char *user, int user_len)
{
      RCPT_ST *rcpt_st;

      rcpt_st = alloc_rcpt_st(&wp->cw, 0);
      if (!rcpt_st)
            return 0;

      ++wp->cw.tgts;
      memcpy(rcpt_st->env_to, rcpt, rcpt_len);
      rcpt_st->env_to[rcpt_len] = '\0';
      if (user_len)
            memcpy(rcpt_st->user, user, user_len);
      rcpt_st->user[user_len] = '\0';

      rcpt_st->dir[0] = '\0';
      if (user_len != 0
          && !get_user_dir(rcpt_st, user, user_len, 0, 0))
            thr_trace_msg(&wp->cw, "%s", wp->cw.emsg);

      return rcpt_st;
}



/* read lines of pairs if (env_To, username) values from the MTA */
static void
get_ascii_rcpts(WORK *wp)
{
      const char *addr, *user, *user_end;
      char c;
      int addr_len, user_len;
      RCPT_ST *rcpt_st;

      lock_work();
      wp->flags |= FG_WORK_LOCK;
      for (;;) {
            ascii_read_line(wp);

            /* stop after the empty line */
            addr = wp->msg_rd.out;
            if (*addr == '\0') {
                  wp->msg_rd.out = wp->msg_rd.next_line;
                  break;
            }

            user_end = wp->msg_rd.next_line;
            while (user_end > addr
                   && ((c = *(user_end-1)) == ' ' || c == '\t'))
                  --user_end;

            /* bytes up to '\r' are the env_To value
             * and bytes after are the local recipient */
            user = strchr(addr, '\r');
            if (!user) {
                  addr_len = user_end - wp->msg_rd.out;
                  user = user_end;
            } else {
                  addr_len  = user - addr;
                  ++user;
            }

            user_len = user_end - user;
            if (!check_addr(wp, &addr, &addr_len, &user, &user_len))
                  job_exit(wp);
            rcpt_st = set_rcpt(wp, addr, addr_len, user, user_len);
            if (!rcpt_st)
                  job_exit(wp);

            /* don't worry if this recipient is incompatible with preceding
             * recipients */
            cmn_compat_whitelist(&wp->cw, rcpt_st);

            wp->msg_rd.out = wp->msg_rd.next_line;
      }
      unlock_work();
      wp->flags &= ~FG_WORK_LOCK;
}



/* We are finished with one SMTP message with the ASCII protocol
 *      Send the result to the MTA and end this thread */
static void NRATTRIB
ascii_done(WORK *wp, DCCIF_RESULT_CHAR result_char)
{
      const char *result_str;
      RCPT_ST *rcpt_st;
      char *p;

      result_str = wp->cw.reply.log_result;
      if (!result_str)
            thr_error_msg(&wp->cw, "rejection reason undecided");
      else
            thr_log_print(&wp->cw, 0, DCC_XHDR_RESULT"%s\n", result_str);

      /* tell MTA the overall result */
      p = wp->msg_rd.base;
      *p++ = result_char;
      *p++ = '\n';

      /* output list of recipients */
      for (rcpt_st = wp->cw.rcpt_st_first; rcpt_st; rcpt_st = rcpt_st->fwd) {
            if (p >= &wp->msg_rd.base[wp->msg_rd.size-1]) {
                  buf_write(wp, &wp->msg_wt,
                          wp->msg_rd.base, wp->msg_rd.size-1);
                  p = wp->msg_rd.base;
            }
            if (result_char == DCCIF_RESULT_GREY) {
                  *p++ = DCCIF_RCPT_GREY;
            } else if (rcpt_st->fgs & RCPT_FG_ISSPAM) {
                  *p++ = DCCIF_RCPT_REJECT;
            } else {
                  *p++ = DCCIF_RCPT_ACCEPT;
            }
      }
      *p++ = '\n';
      buf_write(wp, &wp->msg_wt, wp->msg_rd.base, p-wp->msg_rd.base);

      /* send the body from the temporary file to the MTA */
      if (wp->flags & FG_MTA_BODY) {
            body_copy(wp);

      } else if (wp->flags & FG_MTA_HEADER) {
            /* MTA wants only the header, if we have it */
            if (wp->cw.header.used != 0)
                  buf_write(wp, &wp->msg_wt, wp->cw.header.buf,
                               wp->cw.header.used);
            buf_write(wp, &wp->msg_wt, "\n", 1);
      }

      job_exit(wp);
}



/* use the dccifd ASCII protocol to talk to an MTA */
static void NRATTRIB
ascii_job(WORK *wp)
{
      char *p;
      int i;

      wp->msg_rd.base = wp->buf1;
      wp->msg_rd.size = sizeof(wp->buf1);
      wp->msg_rd.socp = &wp->proxy_in_soc;

      wp->msg_wt.base = wp->buf2;
      wp->msg_wt.size = sizeof(wp->buf2);
      wp->msg_wt.socp = &wp->proxy_in_soc;

      /* get any options */
      ascii_read_line(wp);
      while (*wp->msg_rd.out != '\0') {
            if (ascii_opt_str(wp, DCCIF_OPT_SPAM,
                          STRZ(DCCIF_OPT_SPAM))) {
                  wp->cw.ask_st |= ASK_ST_MTA_ISSPAM;
                  continue;
            }
            if (ascii_opt_str(wp, DCCIF_OPT_BODY,
                          STRZ(DCCIF_OPT_BODY))) {
                  wp->flags |= FG_MTA_BODY;
                  continue;
            }
            if (ascii_opt_str(wp, DCCIF_OPT_HEADER,
                          STRZ(DCCIF_OPT_HEADER))) {
                  wp->flags |= FG_MTA_HEADER;
                  continue;
            }
            if (ascii_opt_str(wp, DCCIF_OPT_QUERY,
                          STRZ(DCCIF_OPT_QUERY))) {
                  wp->cw.cmn_fgs |= CMN_FG_MTA_QUERY;
                  continue;
            }
            if (ascii_opt_str(wp, DCCIF_OPT_GREY_QUERY,
                          STRZ(DCCIF_OPT_GREY_QUERY))) {
                  wp->flags |= FG_MTA_GREY_QUERY;
                  continue;
            }
            if (ascii_opt_str(wp, DCCIF_OPT_NO_REJECT,
                          STRZ(DCCIF_OPT_NO_REJECT))) {
                  wp->cw.action = CMN_IGNORE;
                  continue;
            }
            if (ascii_opt_str(wp, DCCIF_OPT_RCVD_NXT,
                          STRZ(DCCIF_OPT_RCVD_NXT))
                || ascii_opt_str(wp, DCCIF_OPT_RCVD_NEXT,
                             STRZ(DCCIF_OPT_RCVD_NEXT))) {
                  ++wp->parse_rcvd;
                  continue;
            }

            thr_error_msg(&wp->cw, "unrecognized option value: \"%s\"",
                        wp->msg_rd.out);
            job_exit(wp);
      }
      if ((wp->cw.ask_st & ASK_ST_MTA_ISSPAM)
          && (wp->cw.cmn_fgs & CMN_FG_MTA_QUERY)) {
            thr_error_msg(&wp->cw, DCCIF_OPT_SPAM" and "DCCIF_OPT_QUERY
                        " are incompatible");
            wp->cw.ask_st &= ~ASK_ST_MTA_ISSPAM;
      }
      if ((wp->cw.ask_st & ASK_ST_MTA_ISSPAM)
          && (wp->flags & FG_MTA_GREY_QUERY)) {
            thr_error_msg(&wp->cw, DCCIF_OPT_SPAM" and "DCCIF_OPT_GREY_QUERY
                        " are incompatible");
            wp->cw.ask_st &= ~ASK_ST_MTA_ISSPAM;
      }
      wp->msg_rd.out = wp->msg_rd.next_line;

      /* get the SMTP client IP address and host name */
      i = ascii_read_line(wp);
      if (i == 0
          || !strcmp("0.0.0.0", wp->msg_rd.out)
          || !strcmp("0.0.0.0\r0.0.0.0", wp->msg_rd.out)) {
            /* it is absent or the SpamAssassin junk */
            wp->flags |= FG_PARSE_RCVD;    /* try a Received header */
      } else {
            /* the host name follows the IP address */
            p = strchr(wp->msg_rd.out, '\r');
            if (p) {
                  *p++ = '\0';
                  BUFCPY(wp->cw.clnt_name, p);
            }
            /* convert ASCCI representation of IP address to a
             * canonical form and to a checksum */
            if (!dcc_get_str_ip_ck(&wp->cw.cks, wp->msg_rd.out)) {
                  thr_error_msg(&wp->cw, "unrecognized IP address \"%s\"",
                              wp->msg_rd.out);
            } else {
                  /* convert IPv6 address to a canonical string */
                  wp->cw.clnt_addr = wp->cw.cks.ip_addr;
                  dcc_ipv6tostr(wp->cw.clnt_str, sizeof(wp->cw.clnt_str),
                              &wp->cw.clnt_addr);
            }
      }
      wp->msg_rd.out = wp->msg_rd.next_line;

      /* get the HELO value */
      i = ascii_read_line(wp);
      get_helo(wp, wp->msg_rd.out, i);
      wp->msg_rd.out = wp->msg_rd.next_line;

      /* get the envelope Mail_From value */
      i = ascii_read_line(wp);
      if (i > ISZ(wp->cw.env_from)-1) {
            i = ISZ(wp->cw.env_from)-1;
            wp->msg_rd.out[ISZ(wp->cw.env_from)] = '\0';
      }
      memcpy(wp->cw.env_from, wp->msg_rd.out, i+1);
      wp->msg_rd.out = wp->msg_rd.next_line;

      get_sender(wp);

      /* get the list of recipients from the MTA */
      get_ascii_rcpts(wp);

      if (!get_body(wp)) {
            /* something wrong while collecting the message body
             * such as contacting the DCC server */
            ascii_done(wp, DCCIF_RESULT_TEMP);
      }

      /* get consensus of targets' wishes */
      users_process(&wp->cw);
      totals.tgts_rejected += wp->cw.reject_tgts;
      /* log the consensus & generate SMTP rejection message if needed */
      users_log_result(&wp->cw, 0);

      if (wp->cw.ask_st & ASK_ST_GREY_EMBARGO) {
            totals.tgts_embargoed += wp->cw.tgts;
            ascii_done(wp, DCCIF_RESULT_GREY);
      }

      if (wp->cw.reject_tgts != 0
          || (wp->cw.tgts == 0 && (wp->cw.ask_st & ASK_ST_CLNT_ISSPAM))) {
            if (wp->cw.action != CMN_IGNORE) {
                  if (!wp->cw.reply.log_result)
                        wp->cw.reply.log_result=DCC_XHDR_RESULT_REJECT;
                  ascii_done(wp, DCCIF_RESULT_REJECT);
            } else {
                  wp->cw.reply.log_result = DCC_XHDR_RESULT_I_A;
                  ascii_done(wp, DCCIF_RESULT_OK);
            }
      }

      wp->cw.reply.log_result = DCC_XHDR_RESULT_ACCEPT;
      ascii_done(wp, DCCIF_RESULT_OK);
}



#define SMTP_REPLY_221        "221 dccifd closing connection"
#define SMTP_REPLY_220        "220 dccifd proxy ready"
#define SMTP_REPLY_250_DATA   "250 dccifd mail ok"
#define SMTP_REPLY_250_RSET   "250 dccifd RSET ok"
#define SMTP_REPLY_250_HELO   "250 dccifd HELO ok"
#define SMTP_REPLY_250_POSTFIX      "250 dccifd Postfix extension ok"
#define SMTP_REPLY_250_RCPT   "250 dccifd Recipient ok"
#define SMTP_REPLY_250_MAIL   "250 dccifd Sender ok"
#define SMTP_REPLY_350        "354 Enter mail to dccifd"
#define SMTP_REPLY_452_WLIST  "452 4.5.3 "DCC_XHDR_INCOMPAT_WLIST
#define SMTP_REPLY_452_2MANY  "452 4.5.3 "DCC_XHDR_TOO_MANY_RCPTS
#define SMTP_REPLY_500        "500 5.0.0 dccifd command unrecognized"
#define SMTP_REPLY_501_NO_ARG "501 5.5.4 dccifd command arg required"
#define SMTP_REPLY_501_BAD_ARG      "501 5.5.1 dccifd command unrecognized"
#define SMTP_REPLY_501_RCPT   "501 5.5.2 dccifd RCPT command syntax error"
#define SMTP_REPLY_501_POSTFIX      "501 5.5.2 dccifd Postfix extension syntax error"
#define SMTP_REPLY_503_DATA   "503 5.0.0 dccifd need RCPT"
#define SMTP_REPLY_503        "503 5.0.0 bad dccifd command sequence"

#define SMTP_DEBUG_TRACE 3


static void
smtp_trace(WORK *wp, const char *type, const char *buf, int len)
{
      if (dcc_clnt_debug < SMTP_DEBUG_TRACE)
            return;

      log_start(&wp->cw);
      if (len > 0 && buf[len-1] == '\n')
            --len;
      if (len > 0 && buf[len-1] == '\r')
            --len;
      thr_trace_msg(&wp->cw, "%s %s: %.*s",
                  wp->cw.id, type, len, buf);
}



/* send an SMTP reply upstream to the SMTP client of our proxy */
static void
smtp_send_reply(WORK *wp, const char *reply, int len)
{
      if (dcc_clnt_debug >= SMTP_DEBUG_TRACE)
            smtp_trace(wp, "SMTP response", reply, len);
      buf_write(wp, &wp->reply_out, reply, len);
      buf_write(wp, &wp->reply_out, "\r\n", 2);
}

#define SMTP_REPLY(r) smtp_send_reply(wp,SMTP_REPLY_##r,STRZ(SMTP_REPLY_##r))

static void
smtp_reply_error(WORK *wp, const char *reply, int len)
{
      smtp_send_reply(wp, reply, len);
      wp->msg_rd.out = wp->msg_rd.next_line;
      wp->smtp_state = SMTP_ST_ERROR;
      if (dcc_clnt_debug)
            wp->cw.ask_st |= ASK_ST_LOGIT;
      thr_log_print(&wp->cw, 1, "%s", reply);
}

#define SMTP_ERROR(r) smtp_reply_error(wp,SMTP_REPLY_##r,STRZ(SMTP_REPLY_##r))



/* look for an SMTP verb */
typedef enum {
    SMTP_VERB_ERR,                  /* bad command */
    SMTP_VERB_UNREC,                /* unrecognized command */
    SMTP_VERB_RSET,
    SMTP_VERB_HELO,
    SMTP_VERB_XCLIENT,              /* Postfix extension */
    SMTP_VERB_XFORWARD,             /* Postfix extension */
    SMTP_VERB_MAIL,                 /* Mail From */
    SMTP_VERB_RCPT,                 /* Rcpt To */
    SMTP_VERB_DATA,
    SMTP_VERB_QUIT,
} VERB_SMTP;
typedef struct {
    const char    *str;
    int           str_len;
    const char    *parm;
    int           parm_len;
    VERB_SMTP     verb;
    u_char  arg_required;
} PT;
PT pt[] = {
#define PT_M(s,p,v,r) {s,STRZ(s),p,STRZ(p),SMTP_VERB_##v,r}
    PT_M("HELO","", HELO, 1),
    PT_M("EHLO","", HELO, 1),
    PT_M("Mail","From:", MAIL, 1),
    PT_M("Rcpt","To:", RCPT, 1),
    PT_M("DATA","", DATA, 0),
    PT_M("XFORWARD","", XFORWARD, 1),
    PT_M("XCLIENT","", XCLIENT, 1),
    PT_M("RSET","", RSET, 0),
    PT_M("QUIT","", QUIT, 0),
#undef PT_M
};

#define RESP_DIGIT(c) ((c) >= '0' && (c) <= '9')

static VERB_SMTP
get_smtp_verb(WORK *wp,
            const char **ppp,       /* parameter after command */
            const char **pep)       /* end of parameter */
{
      PT *ptp;
      const char *lp, *pp;
      int i, len;

      /* skip leading whitespace */
      wp->msg_rd.out += strspn(wp->msg_rd.out, " \t");
      lp = wp->msg_rd.out;

      if (dcc_clnt_debug >= SMTP_DEBUG_TRACE)
            smtp_trace(wp, "SMTP command", lp, wp->msg_rd.next_line - lp);

      for (ptp = pt; ptp < &pt[DIM(pt)]; ++ptp) {
            len = ptp->str_len;
            pp = lp+len;
            if (pp+2 > wp->msg_rd.next_line
                || strncasecmp(lp, ptp->str, len))
                  continue;

            if (pp+2 == wp->msg_rd.next_line) {
                  /* SMTP command by itself on the line */
                  *ppp = 0;
                  *pep = 0;
                  if (ptp->arg_required) {
                        SMTP_REPLY(501_NO_ARG);
                        return SMTP_VERB_ERR;
                  }
                  /* finished, having matched the command */
                  return ptp->verb;
            }

            /* all other commands require following text separated
             * from the command with whitespace, as in "Mail From:" */
            i = strspn(pp, " \t");
            if (i == 0)
                  continue;
            pp += i;

            /* skip the initial part of the parameter, such as "From:" */
            if (ptp->parm_len) {
                  if (*pp == '\r') {
                        SMTP_REPLY(501_NO_ARG);
                        return SMTP_VERB_ERR;
                  }
                  if (strncasecmp(pp, ptp->parm, ptp->parm_len)) {
                        SMTP_REPLY(501_BAD_ARG);
                        return SMTP_VERB_ERR;
                  }
                  pp += ptp->parm_len;
                  pp += strspn(pp, " \t");
            }

            *ppp = pp;
            *pep = strpbrk(pp, " \t\r");
            return ptp->verb;
      }

      *ppp = 0;
      return SMTP_VERB_UNREC;
}



static void
proxy_rset(WORK *wp, const char *log_msg)
{
      if (log_msg) {
            if (dcc_clnt_debug >= SMTP_DEBUG_TRACE)
                  smtp_trace(wp, "reset", log_msg, strlen(log_msg));

            if (wp->smtp_state != SMTP_ST_START
                && wp->smtp_state != SMTP_ST_HELO
                && wp->smtp_state != SMTP_ST_ERROR) {
                  if (dcc_clnt_debug)
                        wp->cw.ask_st |= ASK_ST_LOGIT;
                  thr_log_print(&wp->cw, 1, "%s", log_msg);
            }
      }

      if (wp->smtp_state == SMTP_ST_START)
            return;

      cmn_clear(&wp->cw, wp, 0);
      wp->flags &= FG_RECYCLE;
      wp->smtp_state = SMTP_ST_START;

      /* Values from the Postfix XCLIENT ESMTP extension endure for the
       * entire session.  Values from the XFORWARD extension or guesses from
       * a HELO command are cleared at the end of the SMTP transaction. */
      if (wp->flags & FG_XFORWARD_NAME)
            wp->cw.clnt_name[0] = '\0';
      if (wp->flags & FG_XFORWARD_ADDR)
            wp->cw.clnt_str[0] = '\0';
      if (!(wp->flags & FG_XCLIENT_HELO))
            wp->cw.helo[0] = '\0';
}



/* pass a line from the downstream proxy to our upstream */
static void
smtp_pass_line(WORK *wp)
{
      int len;

      len = wp->reply_in.next_line - wp->reply_in.out;
      if (dcc_clnt_debug >= SMTP_DEBUG_TRACE)
            smtp_trace(wp, "pass SMTP response", wp->reply_in.out, len);

      buf_write(wp, &wp->reply_out, wp->reply_in.out,
              wp->reply_in.next_line - wp->reply_in.out);

      wp->reply_in.out = wp->reply_in.next_line;
}



static int                    /* -1 or 1st digit of response */
smtp_get_resp(WORK *wp,
            u_char pass,            /* 1=pass it upstream */
            char *logbuf,           /* log response here */
            int logbuflen)
{
      int len;
      char dig, cont;

      for (;;) {
            if (!msg_read_line(wp, &wp->reply_in)) {
                  if (pass)
                        job_exit(wp);
                  return -1;
            }
            len = wp->reply_in.next_line - wp->reply_in.out;
            if (len < 5) {
                  thr_error_msg(&wp->cw, "short SMTP response"
                              " \"%s\" from proxy server",
                              wp->reply_in.out);
                  return -1;
            }
            dig = wp->reply_in.out[0];
            cont = wp->reply_in.out[3];
            if ((dig < '1' || dig > '5')
                || !RESP_DIGIT(wp->reply_in.out[1])
                || !RESP_DIGIT(wp->reply_in.out[2])
                || (cont != ' ' && cont != '-')) {
                  *wp->reply_in.next_line = '\0';
                  thr_error_msg(&wp->cw, "unrecognized SMTP response"
                              " \"%s\" from proxy",
                              wp->reply_in.out);
                  return -1;
            }

            if (logbuflen != 0) {
                  if (logbuflen > len)
                        logbuflen = len;
                  memcpy(logbuf, wp->reply_in.out, logbuflen);
                  while (logbuflen > 0
                         && (logbuf[logbuflen-1] == '\r'
                           || logbuf[logbuflen-1] == '\n'))
                        --logbuflen;
                  logbuf[logbuflen] = '\0';

                  /* log only the first line */
                  logbuflen = 0;
            }

            if (pass) {
                  /* pass the next line upstream */
                  smtp_pass_line(wp);
                  if (cont == ' ')
                        return dig;
            } else {
                  /* we don't like multi-line responses */
                  if (cont == ' ')
                        return dig;

                  *wp->reply_in.next_line = '\0';
                  thr_error_msg(&wp->cw, "unacceptable multi-line"
                              " SMTP response"
                              " \"%s\" from proxy",
                              wp->reply_in.out);
                  return -1;
            }
      }
}



/* pass an SMTP command from upstream of the SMTP client of our   proxy
 *    downstream to the SMTP server of our proxy.
 *    Then send a response upstream to the SMTP client */
static u_char                       /* 0=server was unhappy */
smtp_pass_cmd(WORK *wp,
            const char *reply,      /* send this response upstream if */
            int reply_len,          /*    if downstream is happy */
            char *logbuf,           /* logbuf response here */
            int logbuflen)
{
      int len, result;

      /* fake it if the SMTP server is /dev/null */
      if (proxy_out_family == AF_UNSPEC) {
            smtp_send_reply(wp, reply, reply_len);
            return 1;
      }

      len = wp->msg_rd.next_line - wp->msg_rd.out;
      if (len != 0) {
            buf_write(wp, &wp->msg_wt, wp->msg_rd.out, len);
            buf_write_flush(wp, &wp->msg_wt);
      }

      /* wait for & pass on response */
      result = smtp_get_resp(wp, 1, logbuf, logbuflen);
      if (result < 0)
            job_exit(wp);
      return (result == reply[0]);
}

#define SMTP_PASS_CMD(r) smtp_pass_cmd(wp,SMTP_REPLY_##r,STRZ(SMTP_REPLY_##r), \
                               0, 0)



/* parse Postfix XFORWARD and XCLIENT commands
 * see http://www.postfix.org/XCLIENT_README.html
 * and http://www.postfix.org/XFORWARD_README.html
 *    Depend on the Postfix downstream to answer EHLO with XFORWRD */
static void
smtp_xpostfix(WORK *wp,
            VERB_SMTP verb,         /* SMTP_VERB_XFORWARD or _XCLIENT */
            const char *parm)
{
      const char *val, *next_parm;
      char addr[INET6_ADDRSTRLEN+1];
      int i;

      wp->smtp_state = SMTP_ST_HELO;

      for (; ; parm = next_parm) {
            if (*parm == '\r' || *parm == '\n') {
                  SMTP_PASS_CMD(250_POSTFIX);
                  break;
            }

            /* find the value */
            val = strpbrk(parm, "= \t\r\n");
            if (*val++ != '=') {
                  SMTP_REPLY(501_POSTFIX);
                  break;;
            }

            next_parm = strpbrk(val, " \t\r\n");
            next_parm += strspn(next_parm, " \t");

            if (!CSTRCMP(parm, "NAME=")) {
                  if (!CSTRCMP(val, "[UNAVAILABLE]")
                      || !CSTRCMP(val, "[TEMPUNAVAIL]")) {
                        wp->cw.clnt_name[0] = '\0';
                        continue;
                  }
                  i = min(ISZ(wp->cw.clnt_name)-1, next_parm-val);
                  memcpy(wp->cw.clnt_name, val, i);
                  wp->cw.clnt_name[i] = '\0';
                  if (verb == SMTP_VERB_XFORWARD)
                        wp->flags |= FG_XFORWARD_NAME;
                  else
                        wp->flags &= ~FG_XFORWARD_NAME;
                  continue;
            }

            if (!CSTRCMP(parm, "ADDR=")) {
                  if (!CSTRCMP(val, "[UNAVAILABLE]")
                      || !CSTRCMP(val, "[TEMPUNAVAIL]")) {
                        wp->cw.clnt_str[0] = '\0';
                        continue;
                  }
                  if (!CSTRCMP(val, "IPV6"))
                        val += STRZ("IPV6");
                  i = min(ISZ(addr)-1, next_parm-val);
                  memcpy(addr, val, i);
                  addr[i] = '\0';
                  /* try to convert ASCCI representation of IP address to
                   * a canonical form and to a checksum */
                  if (!dcc_get_str_ip_ck(&wp->cw.cks, addr)) {
                        thr_error_msg(&wp->cw,
                                    "unrecognized IP address \"%s\"",
                                    addr);
                        wp->cw.clnt_str[0] = '\0';
                        continue;
                  }
                  wp->cw.clnt_addr = wp->cw.cks.ip_addr;
                  dcc_ipv6tostr(wp->cw.clnt_str, sizeof(wp->cw.clnt_str),
                              &wp->cw.clnt_addr);
                  if (verb == SMTP_VERB_XFORWARD) {
                        wp->cw.cmn_fgs |= FG_XFORWARD_ADDR;
                  } else {
                        wp->cw.cmn_fgs &= ~FG_XFORWARD_ADDR;
                  }
                  continue;
            }

            if (!CSTRCMP(parm, "HELO=")) {
                  if (!CSTRCMP(val, "[UNAVAILABLE]")
                      || !CSTRCMP(val, "[TEMPUNAVAIL]")) {
                        wp->cw.helo[0] = '\0';
                  } else {
                        get_helo(wp, val, next_parm-val);
                  }
                  if (verb == SMTP_VERB_XCLIENT)
                        wp->flags |= FG_XCLIENT_HELO;
                  else
                        wp->flags &= ~FG_XCLIENT_HELO;
                  continue;
            }
      }

      wp->msg_rd.out = wp->msg_rd.next_line;
}



/* parse SMTP Rcpt_To command */
static u_char                       /* 1=now seen >=1 good Rcpt command*/
get_smtp_rcpt(WORK *wp,
            const char *path,
            const char *epath)
{
      const char *user;
      int path_len, user_len;
      RCPT_ST *rcpt_st;

      path_len = epath - path;
      user = "";
      user_len = 0;
      if (!check_addr(wp, &path, &path_len, &user, &user_len)) {
            SMTP_REPLY(501_RCPT);
            wp->msg_rd.out = wp->msg_rd.next_line;
            return 0;
      }

      lock_work();
      wp->flags |= FG_WORK_LOCK;
      rcpt_st = set_rcpt(wp, path, path_len, user, user_len);
      unlock_work();
      wp->flags &= ~FG_WORK_LOCK;
      if (!rcpt_st) {
            SMTP_REPLY(452_2MANY);
            wp->msg_rd.out = wp->msg_rd.next_line;
            return 0;
      }

      /* if this recipient is incompatible with preceding recipients
       * then reject it and remember to put something into the log */
      if (!cmn_compat_whitelist(&wp->cw, rcpt_st)) {
            --wp->cw.tgts;
            SMTP_REPLY(452_WLIST);
            wp->msg_rd.out = wp->msg_rd.next_line;
            BUFCPY(rcpt_st->temp_rej_msg, SMTP_REPLY_452_WLIST);
            rcpt_st->temp_rej_result = DCC_XHDR_INCOMPAT_WLIST;
            return 0;
      }

      /* Try to pass the command in the buffer downstream.
       * Forget this recipient if it is not good enough downstream.
       * Let the downstream proxy do its own logging.
       *    That way we need save only one bit instead of an SMTP
       *    rejection message for our eventual per-user log file. */
      if (!smtp_pass_cmd(wp, SMTP_REPLY_250_RCPT, STRZ(SMTP_REPLY_250_RCPT),
                     rcpt_st->temp_rej_msg,
                     sizeof(rcpt_st->temp_rej_msg))) {
            --wp->cw.tgts;
            wp->msg_rd.out = wp->msg_rd.next_line;
            rcpt_st->temp_rej_result = rcpt_st->temp_rej_msg;
            rcpt_st->temp_rej_result += strspn(rcpt_st->temp_rej_result,
                                       "0123456789. ");
            return 0;
      } else {
            /* if the downstream was happy, there was no problem */
            rcpt_st->temp_rej_msg[0] = '\0';
      }

      wp->msg_rd.out = wp->msg_rd.next_line;
      return 1;
}



static void
smtp_data_abort(WORK *wp)
{
      /* After a rejection Postfix wants a before-queue proxy to
       * "abort the connnection" which might mean a TCP shutdown.
       * For testing with sendmail, send a RSET command. */
      if (proxy_out_family != AF_UNSPEC) {
            buf_write(wp, &wp->msg_wt,
                    "RSET\r\n", STRZ("RSET\r\n"));
            buf_write_flush(wp, &wp->msg_wt);
            /* sendmail will respond, but what about Postfix? */
            if (smtp_get_resp(wp, 0, 0, 0) >= 0
                && dcc_clnt_debug >= SMTP_DEBUG_TRACE) {
                  smtp_trace(wp, "ignore response to RSET",
                           wp->reply_in.out,
                           wp->reply_in.next_line
                           - wp->reply_in.out);
                  wp->reply_in.out = wp->reply_in.next_line;
            }
      }
}



/* We are rejecting the message, so do not pass the
 * DATA command or the mail message to the SMTP server.
 * Instead send our rejection message to the SMTP client */
static void
smtp_data_reject(WORK *wp)
{
      if (dcc_clnt_debug >= SMTP_DEBUG_TRACE)
            thr_trace_msg(&wp->cw, "%s SMTP response: %s %s %s",
                        wp->cw.id,
                        wp->cw.reply.rcode, wp->cw.reply.xcode,
                        wp->cw.reply.buf);
      buf_write(wp, &wp->reply_out,
              wp->cw.reply.rcode, strlen(wp->cw.reply.rcode));
      buf_write(wp, &wp->reply_out, " ", 1);
      buf_write(wp, &wp->reply_out,
              wp->cw.reply.xcode, strlen(wp->cw.reply.xcode));
      buf_write(wp, &wp->reply_out, " ", 1);
      buf_write(wp, &wp->reply_out,
              wp->cw.reply.buf, strlen(wp->cw.reply.buf));
      buf_write(wp, &wp->reply_out, "\r\n", 2);
      log_smtp_reply(&wp->cw);

      smtp_data_abort(wp);
}



/* Send the mail message to the SMTP server for our proxy. */
static const char *
smtp_data_send(WORK *wp)
{
      int class;
      int len;
      const char *result;

      /* First send the DATA command. */
      buf_write(wp, &wp->msg_wt, "DATA\r\n", STRZ("DATA\r\n"));
      buf_write_flush(wp, &wp->msg_wt);

      /* the server should respond with a 350 result */
      class = smtp_get_resp(wp, 0, 0, 0);
      if (class < 0)
            job_exit(wp);

      if (class == '3') {
            wp->reply_in.out = wp->reply_in.next_line;

            /* send the mail message to the SMTP server */
            body_copy(wp);
            buf_write(wp, &wp->msg_wt, ".\r\n", 3);
            buf_write_flush(wp, &wp->msg_wt);

            /* see what it has to say */
            class = smtp_get_resp(wp, 0, 0, 0);

      } else if (class != '4' && class != '5') {
            /* or quit if response to DATA was not 4yz or 5yz */
            thr_error_msg(&wp->cw, "unrecognized SMTP response"
                        " \"%s\" from proxy to DATA command",
                        wp->reply_in.out);
            job_exit(wp);
      }

      if (class == '2') {
            result = 0;
      } else {
            len = wp->reply_in.next_line - wp->reply_in.out;
            while (len > 0 && (wp->reply_in.out[len-1] == '\r'
                           || wp->reply_in.out[len-1] == '\n'))
                  --len;
            if (len > ISZ(wp->cw.reply.buf)-2)
                  len = ISZ(wp->cw.reply.buf)-2;
            memcpy(wp->cw.reply.buf, wp->reply_in.out, len);
            wp->cw.reply.buf[len++] = '\n';
            wp->cw.reply.buf[len] = '\0';
            thr_log_print(&wp->cw, 0,
                        "SMTP server "DCC_XHDR_REJ_DATA_MSG"%.*s",
                        len, wp->cw.reply.buf);
            wp->cw.ask_st |= ASK_ST_LOGIT;
            result = "rejected by SMTP server";
      }

      /* pass SMTP server's response to the SMTP client */
      smtp_pass_line(wp);

      return result;
}



static void
smtp_data_done(WORK *wp)
{
      const char *global_result, *user_result;

      if (wp->cw.ask_st & ASK_ST_GREY_EMBARGO) {
            totals.tgts_embargoed += wp->cw.tgts;
            smtp_data_reject(wp);
            global_result = wp->cw.reply.log_result;
            user_result = global_result;

      } else if (wp->cw.reject_tgts != 0) {
            switch (wp->cw.action) {
            case CMN_IGNORE:
                  totals.tgts_ignored += wp->cw.reject_tgts;
                  ++totals.msgs_spam;
                  if (proxy_out_family == AF_UNSPEC) {
                        SMTP_REPLY(250_DATA);
                        global_result = DCC_XHDR_RESULT_I_A;
                  } else {
                        global_result = smtp_data_send(wp);
                  }
                  user_result = global_result;
                  break;

            case CMN_DISCARD:
                  totals.tgts_discarded += wp->cw.reject_tgts;
                  ++totals.msgs_spam;
                  /* discard the message by aborting the downstream
                   * SMTP session and telling the upstream 250-OK */
                  if (proxy_out_family != AF_UNSPEC)
                        smtp_data_abort(wp);
                  SMTP_REPLY(250_DATA);
                  global_result = DCC_XHDR_RESULT_DISCARD;
                  user_result = global_result;
                  break;

            case CMN_REJECT:
            default:
                  totals.tgts_rejected += wp->cw.reject_tgts;
                  ++totals.msgs_spam;
                  smtp_data_reject(wp);
                  global_result = wp->cw.reply.log_result;
                  user_result = 0;
                  break;
            }

      } else if (proxy_out_family == AF_UNSPEC) {
            SMTP_REPLY(250_DATA);
            global_result = DCC_XHDR_RESULT_ACCEPT;
            user_result = 0;

      } else {
            global_result = smtp_data_send(wp);
            user_result = 0;
      }

      /* log the consensus & generate SMTP rejection message if needed */
      users_log_result(&wp->cw, user_result);
      thr_log_print(&wp->cw, 0, DCC_XHDR_RESULT"%s\n", global_result);

      proxy_rset(wp, 0);
}



static void
smtp_data(WORK *wp)
{
      if (!wp->cw.num_rcpts) {
            /* never got SMTP DATA command */
            SMTP_ERROR(503_DATA);
            return;
      }

      /* tell STMP client to send the mail message */
      wp->msg_rd.out = wp->msg_rd.next_line;
      SMTP_REPLY(350);
      buf_write_flush(wp, &wp->reply_out);

      if (!get_body(wp)) {
            /* something went wrong while collecting the message body
             * such as contacting the DCC server */
            make_reply(&wp->cw.reply, &dcc_fail_reply, &wp->cw);
            smtp_data_done(wp);
            return;
      }

      /* get consensus of targets' wishes */
      users_process(&wp->cw);

      smtp_data_done(wp);
}



static void NRATTRIB
proxy_run(WORK *wp)
{
      VERB_SMTP verb;
      const char *parmp, *eparmp;
      int i;

      /* pass the banner upstream */
      wp->smtp_state = SMTP_ST_START;
      SMTP_PASS_CMD(220);

      /* assume for now that the IP address is the SMTP client to our proxy */
      if (wp->proxy_in_su.sa.sa_family != AF_UNIX) {
            if (wp->proxy_in_su.sa.sa_family == AF_INET) {
                  dcc_ipv4toipv6(&wp->cw.clnt_addr,
                               wp->proxy_in_su.ipv4.sin_addr);
            } else {
                  wp->cw.clnt_addr = (wp->proxy_in_su.ipv6.sin6_addr);
            }
            dcc_ipv6tostr(wp->cw.clnt_str, sizeof(wp->cw.clnt_str),
                        &wp->cw.clnt_addr);
      }

      for (;;) {
            buf_write_flush(wp, &wp->reply_out);
            if (!msg_read_line(wp, &wp->msg_rd)) {
                  proxy_rset(wp, "disconnected\n");
                  job_exit(wp);
            }
            verb = get_smtp_verb(wp, &parmp, &eparmp);

            /* deal with common SMTP commands */
            switch (verb) {
            case SMTP_VERB_ERR:
                  wp->msg_rd.out = wp->msg_rd.next_line;
                  continue;
            case SMTP_VERB_UNREC:
                  /* pass anything we don't recognize to the SMTP server
                   * for our proxy.
                   * If we don't have a server, reject it */
                  SMTP_PASS_CMD(500);
                  wp->msg_rd.out = wp->msg_rd.next_line;
                  continue;
            case SMTP_VERB_QUIT:
                  SMTP_PASS_CMD(221);
                  proxy_rset(wp, "SMTP QUIT\n");
                  job_exit(wp);
            case SMTP_VERB_RSET:
                  SMTP_PASS_CMD(250_RSET);
                  proxy_rset(wp, "SMTP RSET\n");
                  wp->msg_rd.out = wp->msg_rd.next_line;
                  continue;
            case SMTP_VERB_HELO:
                  proxy_rset(wp, 0);
                  wp->smtp_state = SMTP_ST_HELO;
                  /* save helo value in case there is no XPOSTFIX */
                  if (SMTP_PASS_CMD(250_HELO)
                      && wp->cw.helo[0] == '\0'
                      && !(wp->flags & FG_XCLIENT_HELO))
                        get_helo(wp, parmp, eparmp - parmp);
                  wp->msg_rd.out = wp->msg_rd.next_line;
                  continue;
            case SMTP_VERB_XCLIENT:
            case SMTP_VERB_XFORWARD:
            case SMTP_VERB_MAIL:
            case SMTP_VERB_RCPT:
            case SMTP_VERB_DATA:
                  break;
            }

            switch (wp->smtp_state) {
            case SMTP_ST_START:     /* expecting HELO or perhaps MAIL */
            case SMTP_ST_HELO:      /* seen HELO */
                  if (verb == SMTP_VERB_XCLIENT
                      || verb == SMTP_VERB_XFORWARD) {
                        smtp_xpostfix(wp, verb, parmp);
                        continue;
                  }
                  if (verb == SMTP_VERB_MAIL) {
                        /* get sender */
                        if (SMTP_PASS_CMD(250_MAIL)) {
                              i = eparmp - parmp;
                              if (i > ISZ(wp->cw.env_from)-1)
                                  i = ISZ(wp->cw.env_from)-1;
                              memcpy(wp->cw.env_from, parmp, i);
                              wp->cw.env_from[i] = '\0';
                              wp->smtp_state = SMTP_ST_TRANS;
                              log_start(&wp->cw);
                        }
                        wp->msg_rd.out = wp->msg_rd.next_line;
                        get_sender(wp);
                        continue;
                  }
                  break;

            case SMTP_ST_TRANS:     /* seen Mail_From */
                  if (verb ==  SMTP_VERB_RCPT) {
                        if (get_smtp_rcpt(wp, parmp, eparmp))
                              wp->smtp_state = SMTP_ST_RCPT;
                        continue;
                  }
                  break;

            case SMTP_ST_RCPT:      /* seen Rctp_To */
                  if (verb == SMTP_VERB_RCPT) {
                        get_smtp_rcpt(wp, parmp, eparmp);
                        continue;
                  }
                  if (verb == SMTP_VERB_DATA) {
                        smtp_data(wp);
                        continue;
                  }
                  break;

            case SMTP_ST_ERROR:     /* no transaction until RSET or HELO */
                  break;
            }

            /* reject anything out of sequence */
            SMTP_ERROR(503);
      }
}



/* use a subset of SMTP to talk to an MTA */
static void NRATTRIB
proxy_job(WORK *wp)
{
      char str[DCC_SU2STR_SIZE];
      DCC_SOCKU su;

      if (proxy_out_family == AF_UNSPEC) {
            /* single-ended */
            ;

      } else if (proxy_out_family == AF_UNIX) {
            wp->proxy_out_soc = socket(AF_UNIX, SOCK_STREAM, 0);
            if (wp->proxy_out_soc < 0) {
                  thr_error_msg(&wp->cw, "proxy_out socket(AF_UNIX): %s",
                              ERROR_STR());
                  job_exit(wp);
            }
            if (0 > connect(wp->proxy_out_soc,
                        (struct sockaddr *)&proxy_out_sun,
                        sizeof(listen_sun))) {
                  thr_error_msg(&wp->cw,
                              "proxy_out connect(AF_UNIX,%s): %s",
                              proxy_out_sun.sun_path,
                              ERROR_STR());
                  job_exit(wp);
            }
            if (!set_soc(wp->cw.emsg, wp->proxy_out_soc, AF_UNIX,
                       "proxy output")) {
                  thr_error_msg(&wp->cw, "%s", wp->cw.emsg);
                  job_exit(wp);
            }
            wp->flags |= FG_MTA_BODY;

      } else {
            if (proxy_out_su.sa.sa_family == AF_UNSPEC) {
                  /* use SMTP client's IP address */
                  if (wp->proxy_in_su.sa.sa_family == AF_INET)
                        dcc_mk_su(&su, AF_INET,
                                &wp->proxy_in_su.ipv4.sin_addr,
                                proxy_out_port);
                  else
                        dcc_mk_su(&su, AF_INET6,
                                &wp->proxy_in_su.ipv6.sin6_addr,
                                proxy_out_port);
            } else {
                  su = proxy_out_su;
            }
            wp->proxy_out_soc = socket(su.sa.sa_family, SOCK_STREAM, 0);
            if (wp->proxy_out_soc < 0) {
                  thr_error_msg(&wp->cw, "proxy_out socket(%s): %s",
                              dcc_su2str(str, sizeof(str), &su),
                              ERROR_STR());
                  job_exit(wp);
            }
            if (0 > connect(wp->proxy_out_soc, &su.sa, DCC_SU_LEN(&su))) {
                  thr_error_msg(&wp->cw,
                              "proxy_out connect(%s): %s",
                              dcc_su2str(str, sizeof(str), &su),
                              ERROR_STR());
                  job_exit(wp);
            }
            if (!set_soc(wp->cw.emsg, wp->proxy_out_soc, su.sa.sa_family,
                       "proxy output")) {
                  thr_error_msg(&wp->cw, "%s", wp->cw.emsg);
                  job_exit(wp);
            }
            wp->flags |= FG_MTA_BODY;
      }

      wp->msg_rd.base = wp->buf1;
      wp->msg_rd.size = sizeof(wp->buf1);
      wp->msg_rd.socp = &wp->proxy_in_soc;

      wp->msg_wt.base = wp->buf2;
      wp->msg_wt.size = sizeof(wp->buf2);
      wp->msg_wt.socp = &wp->proxy_out_soc;

      wp->reply_in.base = wp->buf3;
      wp->reply_in.size = sizeof(wp->buf3);
      wp->reply_in.socp = &wp->proxy_out_soc;

      wp->reply_out.base = wp->buf4;
      wp->reply_out.size = sizeof(wp->buf4);
      wp->reply_out.socp = &wp->proxy_in_soc;

      proxy_run(wp);
}



/* start a new connection to an MTA */
static void * NRATTRIB
job_start(void *wp0)
{
      WORK *wp = wp0;
      sigset_t sigsoff;
      int i;

      /* working threads do not deal with signals */
      sigemptyset(&sigsoff);
      sigaddset(&sigsoff, SIGHUP);
      sigaddset(&sigsoff, SIGINT);
      sigaddset(&sigsoff, SIGTERM);
      i = pthread_sigmask(SIG_BLOCK, &sigsoff, 0);
      if (i)
            dcc_logbad(EX_OSERR, "pthread_sigmask(): %s", ERROR_STR1(i));

      log_start(&wp->cw);
      if (proxy)
            proxy_job(wp);
      else
            ascii_job(wp);
}

Generated by  Doxygen 1.6.0   Back to index