Logo Search packages:      
Sourcecode: dcc version File versions

cmn.c

/* Distributed Checksum Clearinghouse
 *
 * threaded version of client library
 *
 * 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.98 $Revision$
 */


#include "cmn_defs.h"
#include "dcc_paths.h"

const char *tmpdir;

CMN_ACTION action = CMN_REJECT;

CHGHDR chghdr = SETHDR;

const char *userdirs;
static int userdirs_len;

u_char dcc_query_only = 0;
u_char try_extra_hard = 0;          /* 1=don't quit if DCC server dead */
u_char to_white_only = 0;
const char *mapfile_nm = DCC_MAP_NM_DEF;

u_int dcc_ctxt_sn = 1;              /* change X-DCC header server name */

RCPT_ST *rcpt_st_free;

/* cwf_mutex protects all CWF structures as well as the cmn_wf structure */
typedef struct cwf {                /* private whitelist state */
    struct cwf *older, *newer;
    DCC_WF  wf;
} CWF;
static CWF *cur_cwf, cwfs[NUM_CWFS];


void
cmn_init(DCC_EMSG emsg)
{
      /* start the client library threads and locks */
      dcc_clnt_thread_init();
      for (cur_cwf = cwfs; cur_cwf <= LAST(cwfs); ++cur_cwf) {
            cur_cwf->newer = cur_cwf-1;
            cur_cwf->older = cur_cwf+1;
            dcc_wf_init(&cur_cwf->wf, DCC_WF_PER_USER);
      }
      cur_cwf = cwfs;
      LAST(cwfs)->older = cur_cwf;
      cur_cwf->newer = LAST(cwfs);

      dcc_ctxts_lock();
      if (!dcc_map_info(emsg, mapfile_nm, -1))
            dcc_logbad(EX_USAGE, "%s", emsg);
      dcc_ctxts_unlock();

      dcc_wf_init(&cmn_wf, 0);
      if (main_white_nm
          && !dcc_new_white_nm(emsg, &cmn_wf, main_white_nm)) {
            dcc_error_msg("%s", emsg);
            main_white_nm = 0;
      }
      if (main_white_nm) {
            lock_wf();
            switch (dcc_rdy_white(emsg, &cmn_wf, &cmn_tmp_wf)) {
            case DCC_WHITE_OK:
            case DCC_WHITE_NOFILE:
            case DCC_WHITE_SILENT:
                  break;
            case DCC_WHITE_CONTINUE:
            case DCC_WHITE_COMPLAIN:
                  cmn_wf.changed = 1;
                  dcc_error_msg("%s", emsg);
                  break;
            }
            unlock_wf();
      }

      totals_init();
}



void
cmn_create(CMN_WORK *cwp)
{
      cwp->tmp_fd = -1;
      cwp->log_fd = -1;
      cwp->log_fd2 = -1;
}



u_char
cmn_open_tmp(CMN_WORK *cwp, u_char anonymous)
{
      if (!tmpdir)
            tmpdir = dcc_logdir[0] != '\0' ? dcc_logdir : _PATH_TMP;

      if (!anonymous)
            cwp->cmn_fgs |= CMN_FG_TMP_UNLINK;

      cwp->tmp_fd = dcc_mkstemp(cwp->emsg,
                          cwp->tmp_nm, sizeof(cwp->tmp_nm),
                          cwp->id, sizeof(cwp->id),
                          tmpdir, DCC_TMP_LOG_PAT,
                          anonymous, 0, 0);

      if (cwp->tmp_fd < 0 && tmpdir == dcc_logdir) {
            cwp->tmp_fd = dcc_mkstemp(cwp->emsg,
                                cwp->tmp_nm, sizeof(cwp->tmp_nm),
                                cwp->id, sizeof(cwp->id),
                                _PATH_TMP, DCC_TMP_LOG_PAT,
                                anonymous, 0, 0);
      }

      return cwp->tmp_fd >= 0;
}



void
cmn_close_tmp(CMN_WORK *cwp)
{
      if (cwp->tmp_fd >= 0) {
            if (0 > close(cwp->tmp_fd))
                  thr_error_msg(cwp, "close(%s): %s",
                              cwp->tmp_nm, ERROR_STR());
            cwp->tmp_fd = -1;
            if (cwp->cmn_fgs & CMN_FG_TMP_UNLINK) {
                  if (0 < unlink(cwp->tmp_nm))
                        dcc_error_msg("unlink(%s): %s",
                                    cwp->tmp_nm, ERROR_STR());
                  cwp->cmn_fgs &= ~CMN_FG_TMP_UNLINK;
            }
      }
      cwp->tmp_nm[0] = '\0';
}



u_char
cmn_write_tmp(CMN_WORK *cwp, const void *buf, int len)
{
      int i;

      if (cwp->tmp_fd < 0)
            return 1;

      i = write(cwp->tmp_fd, buf, len);
      if (i == len)
            return 1;

      if (i < 0)
            thr_error_msg(cwp, "write(%s,%d): %s",
                        cwp->tmp_nm, len, ERROR_STR());
      else
            thr_error_msg(cwp, "write(%s,%d)=%d",
                        cwp->tmp_nm, len, i);
      cmn_close_tmp(cwp);
      return 0;
}



u_char                              /* 1=listed MX server */
check_mx_listing(CMN_WORK *cwp)
{
      DCC_TGTS tgts;
      u_char result;

      lock_wf();
      result = dcc_white_mx(cwp->emsg, &tgts, &cwp->cks);
      unlock_wf();
      if (!result)
            thr_error_msg(cwp, "%s", cwp->emsg);

      if (tgts == DCC_TGTS_OK) {
            cwp->cmn_fgs |= CMN_FG_IP_WHITE;
            return 0;
      } else if (tgts == DCC_TGTS_OK_MXDCC) {
            thr_log_print(cwp, 1,
                        "%s is a whitelisted MX server with DCC client\n",
                        dcc_trim_ffff(cwp->sender_str));
            cwp->cmn_fgs |= CMN_FG_MTA_QUERY;
      } else if (tgts == DCC_TGTS_OK_MX) {
            thr_log_print(cwp, 1, "%s is a whitelisted MX server\n",
                        dcc_trim_ffff(cwp->sender_str));
      } else {
            return 0;
      }

      /* we cannot greylist or reject through our MX servers */
      cwp->cmn_fgs |= CMN_FG_MX_GREY_OFF;
      if (cwp->action == CMN_REJECT)
            cwp->action = CMN_DISCARD;

      cwp->sender_name[0] = '\0';
      cwp->sender_str[0] = '\0';
      dcc_unget_ipv6_ck(&cwp->cks);

      return 1;
}



/* clear a common work area for a message, possibly not the first
 * in the session */
void
cmn_clear(CMN_WORK *cwp, struct work *wp, u_char new_conn)
{
      log_stop(cwp);

      cmn_close_tmp(cwp);

      if (cwp->num_rcpts)
            free_rcpt_sts(cwp, 1);

      cwp->cmn_fgs  = 0;
      cwp->mail_host[0] = '\0';
      cwp->env_from[0] = '\0';
      cwp->early_log.len = 0;
      cwp->emsg[0] = '\0';
      cwp->id[0] = '\0';
      memset(&cwp->CMN_WORK_ZERO, 0,
             sizeof(*cwp) - ((char*)&cwp->CMN_WORK_ZERO - (char*)cwp));
      cwp->action = action;

      if (new_conn) {
            cwp->wp = wp;
            cwp->transaction = 1;
            cwp->helo[0] = '\0';
            cwp->clnt_name[0] = '\0';
            cwp->clnt_str[0] = '\0';
      } else {
            ++cwp->transaction;

            /* assume for now that the sender is the current SMTP client */
            strcpy(cwp->sender_name, cwp->clnt_name);
            strcpy(cwp->sender_str, cwp->clnt_str);
            cwp->sender_addr = cwp->clnt_addr;
      }
}



void
create_rcpt_sts(int i)
{
      RCPT_ST *rcpt_st;

      rcpt_st = dcc_malloc(sizeof(*rcpt_st)*i);
      rcpt_st_free = rcpt_st;
      memset(rcpt_st, 0, sizeof(*rcpt_st)*i);
      while (--i > 0) {
            rcpt_st->fwd = rcpt_st+1;
            rcpt_st->user_log_fd = -1;
            ++rcpt_st;
      }
}



/* free all of the per-recipient state for a message */
void
free_rcpt_sts(CMN_WORK *cwp, u_char need_lock)
{
      RCPT_ST *rcpt_st, *next_rcpt_st;

      rcpt_st = cwp->rcpt_st_first;
      if (!rcpt_st)
            return;

      if (need_lock)
            lock_work();
      cwp->rcpt_st_first = 0;
      do {
            next_rcpt_st = rcpt_st->fwd;
            if (rcpt_st->user_log_fd >= 0) {
                  dcc_log_close(0, rcpt_st->user_log_nm,
                              rcpt_st->user_log_fd, &cwp->ldate);
                  rcpt_st->user_log_fd = -1;
            }
            rcpt_st->fwd = rcpt_st_free;
            rcpt_st_free = rcpt_st;
      } while ((rcpt_st = next_rcpt_st) != 0);
      cwp->num_rcpts = 0;

      if (need_lock)
            unlock_work();
}



RCPT_ST *
alloc_rcpt_st(CMN_WORK *cwp,
         u_char unlocked)           /* 1=unlocked on entry & exit */
{
      RCPT_ST *rcpt_st;

      if (cwp->num_rcpts >= MAX_RCPTS) {
            thr_error_msg(cwp, "too many recipients");
            return 0;
      }

      if (unlocked)
            lock_work();
      rcpt_st = rcpt_st_free;
      if (rcpt_st) {
            rcpt_st_free = rcpt_st->fwd;
            if (unlocked)
                  unlock_work();
      } else {
            if (unlocked)
                  unlock_work();
            rcpt_st = dcc_malloc(sizeof(*rcpt_st));
            memset(rcpt_st, 0, sizeof(*rcpt_st));
            rcpt_st->user_log_fd = -1;
      }
      rcpt_st->fwd = 0;
      rcpt_st->log_pos_white = 0;
      memset(rcpt_st->wtgts, 0, sizeof(rcpt_st->wtgts));
      rcpt_st->env_to_tgts = 0;
      rcpt_st->user_tgts = 0;
      rcpt_st->embargo_num = 0;
      rcpt_st->fgs = 0;
      rcpt_st->sws = 0;
      rcpt_st->user[0] = '\0';
      rcpt_st->temp_rej_msg[0] = '\0';
      rcpt_st->dir[0] = '\0';
      rcpt_st->user_log_nm[0] = '\0';

      rcpt_st->cwp = cwp;
      if (!cwp->rcpt_st_first) {
            cwp->rcpt_st_first = rcpt_st;
      } else {
            cwp->rcpt_st_last->fwd = rcpt_st;
      }
      cwp->rcpt_st_last = rcpt_st;
      ++cwp->num_rcpts;

      return rcpt_st;
}



void
parse_userdirs(const char *arg)
{
      static DCC_PATH userdirs_path;

      /* add '/' to end of the path without converting it to "/" */
      if (*arg == '\0') {
            userdirs_path[0] = '\0';
            userdirs_len = 0;
      } else {
            strncpy(userdirs_path, arg,
                  sizeof(userdirs_path));
            userdirs_len = strlen(userdirs_path)-1;
            while (userdirs_len > 1 && userdirs_path[userdirs_len] == '/')
                  --userdirs_len;
            userdirs_path[++userdirs_len] = '/';
            userdirs_path[++userdirs_len] = '\0';

      }
      userdirs = userdirs_path;
}



/* sanitize recipient mailbox and per-user log and whitelist directory */
u_char                              /* 0=complain about something */
get_user_dir(RCPT_ST *rcpt_st,
           const char *str1, int str1_len, const char *str2, int str2_len)
{
      char *p;
      char c;
      u_char seen_slash;
      int dots;
      int i;

      if (!userdirs) {
            rcpt_st->dir[0] = '\0';
            return 1;
      }

      memcpy(rcpt_st->dir, userdirs, userdirs_len);
      i = userdirs_len;
      if (i+str1_len < ISZ(rcpt_st->dir))
            memcpy(&rcpt_st->dir[i], str1, str1_len);
      i += str1_len;
      if (str2) {
            if (i+str2_len+1 < ISZ(rcpt_st->dir)) {
                  rcpt_st->dir[i++] = '/';
                  memcpy(&rcpt_st->dir[i], str2, str2_len);
            }
            i += str2_len;
      }
      if (i >= ISZ(rcpt_st->dir)) {
            dcc_pemsg(EX_DATAERR, rcpt_st->cwp->emsg,
                    "recipient \"%s\" is too long", rcpt_st->dir);
            rcpt_st->dir[0] = '\0';
            return 0;
      }
      rcpt_st->dir[i] = '\0';

      /* To get a consistent directory name,
       * convert ASCII upper to lower case.
       * Be simplistic about international character sets and
       * avoid locale and portability complications.
       * Refuse insecure paths. */
      seen_slash = 1;               /* userdirs ends with '/' */
      dots = 0;
      p = &rcpt_st->dir[userdirs_len];
      for (;;) {
            c = *p;
            if (c == '/' || c == '\\' || c == '\0') {
                  if (dots == 2) {
                        dcc_pemsg(EX_DATAERR, rcpt_st->cwp->emsg,
                                "path \"%s\" is insecure",
                                rcpt_st->dir);
                        rcpt_st->dir[0] = '\0';
                        return 0;
                  }
                  if (c == '\0')
                        break;
                  seen_slash = 1;
                  dots = 0;
            } else if (c == '.' && seen_slash && dots <= 1) {
                  ++dots;
            } else {
                  *p = DCC_TO_LOWER(c);
                  seen_slash = 0;
                  dots = 0;
            }
            ++p;
      }

      return 1;
}



/* start main log file */
void
log_start(CMN_WORK *cwp)
{
      struct tm tm;
      char date_buf[40];

      /* don't even whine if there is no log directory */
      if (dcc_logdir[0] == '\0')
            return;

      /* nothing to do if we already have a log file */
      if (cwp->log_fd >= 0)
            return;

#if MAX_LOG_SIZE > 0
      cwp->log_size = 0;
#endif
      cwp->log_fd = dcc_log_open(cwp->emsg, cwp->log_nm, sizeof(cwp->log_nm),
                           cwp->id, sizeof(cwp->id));
      if (cwp->log_fd < 0) {
            static time_t whined;
            time_t now;

            /* complain about not being able to open log files
             * only occassionally */
            now = time(0);
            if (now < whined || now > whined+5*60 || dcc_clnt_debug)
                  dcc_error_msg("%s", cwp->emsg);
            whined = now;
            cwp->emsg[0] = '\0';
            return;
      }

      cwp->cmn_fgs |= CMN_FG_LOG_EARLY;

      gettimeofday(&cwp->ldate, 0);
      strftime(date_buf, sizeof(date_buf), DCC_LOG_DATE_FMT,
             dcc_localtime(cwp->ldate.tv_sec, &tm));
      thr_log_print(cwp, 0, DCC_LOG_DATE_PAT"\n", date_buf);
}



/* get an independent FD for the main log file that can be
 * repositioned without affecting additional output to the main log. */
u_char
log2_start(CMN_WORK *cwp)
{
      DCC_PATH abs_nm;

      if (cwp->log_fd2 >= 0)
            return 1;

      /* give up if things are already broken */
      if (cwp->log_fd2 != -1
          || cwp->log_fd < 0)
            return 0;

      /* Some systems don't synchronize the meta data among FDs for
       * a file, causing the second FD to appear to be truncated. */
      if (fsync(cwp->log_fd) < 0)
            thr_error_msg(cwp, "log_fd fsync(%s): %s",
                        fnm2path_err(abs_nm, cwp->log_nm),
                        ERROR_STR());

      cwp->log_fd2 = open(cwp->log_nm, O_RDWR, 0);
      if (cwp->log_fd2 < 0) {
            thr_error_msg(cwp, "log_fd2 open(%s): %s",
                        fnm2path_err(abs_nm, cwp->log_nm),
                        ERROR_STR());
            cwp->log_fd2 = -2;
            return 0;
      }

      return 1;
}



static void
log_fd2_close(CMN_WORK *cwp, int flag)
{
      if (cwp->log_fd2 >= 0
          && 0 > close(cwp->log_fd2))
            dcc_error_msg("close(FD2 %s): %s",
                        cwp->log_nm, ERROR_STR());
      cwp->log_fd2 = flag;
}



void
log_stop(CMN_WORK *cwp)
{
      thr_log_late(cwp);
      log_fd2_close(cwp, -1);

      if (cwp->log_fd < 0)
            return;

      /* Close before renaming to accomodate WIN32 foolishness.
       * Assuming dcc_mkstemp() works properly, there is no race */
      dcc_log_close(0, cwp->log_nm, cwp->log_fd, &cwp->ldate);
      if (!(cwp->ask_st & (ASK_ST_LOGIT
                       | ASK_ST_LOG_ONE
                       | ASK_ST_GREY_LOGIT))) {
            /* Delete the log file if it is not interesting */
            unlink(cwp->log_nm);
      } else {
            /* give it a permanent name if it is interesting */
            dcc_log_keep(0, cwp->log_nm, sizeof(cwp->log_nm));
      }
      cwp->log_nm[0] = '\0';
      cwp->log_fd = -1;
}



void
log_write(CMN_WORK *cwp, const void *buf, u_int buflen)
{
      int result;

      if (cwp->log_fd < 0)
            return;

      if (!buflen)
            buflen = strlen(buf);
#if MAX_LOG_SIZE > 0
      cwp->log_size += buflen;
#endif

      result = write(cwp->log_fd, buf, buflen);
      if (buflen == (u_int)result)
            return;

      if (result < 0)
            dcc_error_msg("write(%s): %s", cwp->log_nm, ERROR_STR());
      else
            dcc_error_msg("write(%s,%d)=%d", cwp->log_nm, buflen, result);
      dcc_log_close(0, cwp->log_nm, cwp->log_fd, &cwp->ldate);
      cwp->log_fd = -1;
}



void
log_body_write(CMN_WORK *cwp, const char *buf, u_int buflen)
{
#if MAX_LOG_SIZE > 0
      int trimlen;
      const char *p, *lim;

      if (cwp->log_fd < 0)
            return;

      /* just write if there is room */
      trimlen = MAX_LOG_SIZE - cwp->log_size;
      if (trimlen >= (int)buflen) {
            log_write(cwp, buf, buflen);
            return;
      }

      /* do nothing if too much already written */
      if (trimlen < 0)
            return;

      /* look for and end-of-line near the end of the buffer
       * so that we can make the truncation pretty */
      lim = buf;
      p = lim+trimlen;
      if (trimlen > 90)
            lim += trimlen-90;
      while (--p > lim) {
            if (*p == '\n') {
                  trimlen = p-buf+1;
                  break;
            }
      }
      log_write(cwp, buf, trimlen);
      if (buf[trimlen-1] != '\n')
            LOG_CMN_EOL(cwp);
      LOG_CMN_CAPTION(cwp, DCC_LOG_TRN_MSG_CR);
      cwp->log_size = MAX_LOG_SIZE+1;
#else
      log_write(&wp->cw, buf, buflen);
#endif
}



off_t
log_lseek_get(CMN_WORK *cwp)
{
      off_t result;

      if (cwp->log_fd < 0)
            return 0;
      result = lseek(cwp->log_fd, 0, SEEK_END);
      if (result == -1) {
            thr_error_msg(cwp, "lseek(%s, 0, SEEK_END): %s",
                        cwp->log_nm, ERROR_STR());
            dcc_log_close(0, cwp->log_nm, cwp->log_fd, &cwp->ldate);
            cwp->log_fd = -1;
            return 0;
      }
      return result;
}



u_char
log_lseek_set(CMN_WORK *cwp, off_t pos)
{
      if (cwp->log_fd2 < 0)
            return 0;

      if (-1 == lseek(cwp->log_fd2, pos, SEEK_SET)) {
            thr_error_msg(cwp, "lseek(%s,%d,SEEK_SET): %s",
                        cwp->log_nm, (int)pos, ERROR_STR());
            log_fd2_close(cwp, -2);
            return 0;
      }

      return 1;
}



/* put something into a log file */
static int                    /* bytes written */
vthr_log_print(CMN_WORK *cwp,
             u_char error,          /* 1=important enough to buffer */
             const char *p, va_list args)
{
      char logbuf[MAXHOSTNAMELEN*3];
      int i;

      if (cwp->log_fd < 0
          || (error && (cwp->cmn_fgs & CMN_FG_LOG_EARLY))) {
            return dcc_vearly_log(&cwp->early_log, p, args);
      }

      i = vsnprintf(logbuf, sizeof(logbuf), p, args);
      if (i >= ISZ(logbuf))
            i = sizeof(logbuf)-1;
      log_write(cwp, logbuf, i);
      return i;
}



int PATTRIB(3,4)              /* bytes written */
thr_log_print(void *cwp, u_char error, const char *p, ...)
{
      va_list args;
      int i;

      va_start(args, p);
      i = vthr_log_print(cwp, error, p, args);
      va_end(args);
      return i;
}



void
thr_error_msg(void *cwp0, const char *p, ...)
{
      CMN_WORK *cwp = cwp0;
      va_list args;

      va_start(args, p);
      dcc_verror_msg(p, args);
      va_end(args);

      va_start(args, p);
      vthr_log_print(cwp, 1, p, args);
      va_end(args);
      thr_log_print(cwp, 1, "\n");

      cwp->ask_st |= ASK_ST_LOGIT;
}



void
thr_trace_msg(void *cwp0, const char *p, ...)
{
      va_list args;

      va_start(args, p);
      dcc_vtrace_msg(p, args);
      va_end(args);

      if (cwp0) {
            CMN_WORK *cwp = cwp0;
            va_start(args, p);
            vthr_log_print(cwp, 1, p, args);
            va_end(args);
            thr_log_print(cwp, 1, "\n");

            cwp->ask_st |= ASK_ST_LOGIT;
      }
}



void
thr_log_late(CMN_WORK *cwp)
{
      cwp->cmn_fgs &= ~CMN_FG_LOG_EARLY;
      if (cwp->early_log.len) {
            log_write(cwp, cwp->early_log.buf, cwp->early_log.len);
            cwp->early_log.len = 0;
      }
}



void
thr_log_envelope(CMN_WORK *cwp, u_char ip_placekeeper)
{
      RCPT_ST *rcpt_st;
      off_t cur_pos;
      int i;

      cwp->cmn_fgs |= CMN_FG_ENV_LOGGED;

      cwp->log_ip_pos = (log_lseek_get(cwp) + STRZ(DCC_XHDR_TYPE_IP": "));
      if (cwp->sender_str[0] != '\0') {
            if (cwp->cks.sums[DCC_CK_IP].type == DCC_CK_INVALID)
                  dcc_get_ipv6_ck(&cwp->cks, &cwp->sender_addr);
            i = thr_log_print(cwp, 0,
                          DCC_XHDR_TYPE_IP": %s %s\n",
                          cwp->sender_name, cwp->sender_str);

      } else if (ip_placekeeper) {
            /* log a blank, place keeping string for the IP address
             * so that it can be inserted later */
            i = thr_log_print(cwp, 0,
                          DCC_XHDR_TYPE_IP": %*s\n",
                          1+1+1+INET6_ADDRSTRLEN, "");
      } else {
            i = 0;
      }
      i -= STRZ(DCC_XHDR_TYPE_IP":  \n");
      cwp->log_ip_len = i > 0 ? i : 0;

      /* log HELO value if we have it
       * make checksum of it or of null string if we don't */
      if (cwp->helo[0] != '\0')
            thr_log_print(cwp, 0, "HELO: %s\n", cwp->helo);
      dcc_ck_get_sub(&cwp->cks, "helo", cwp->helo);

      if (cwp->env_from[0] != '\0') {
            LOG_CMN_CAPTION(cwp, DCC_XHDR_TYPE_ENV_FROM": ");
            log_write(cwp, cwp->env_from, 0);
            dcc_get_cks(&cwp->cks, DCC_CK_ENV_FROM, cwp->env_from, 1);

            LOG_CMN_CAPTION(cwp, "  mail_host=");
            log_write(cwp, cwp->mail_host, 0);
            if (cwp->mail_host[0] != '\0')
                  dcc_ck_get_sub(&cwp->cks, "mail_host", cwp->mail_host);
            LOG_CMN_EOL(cwp);
      }

      cwp->log_pos_to_first = cur_pos = log_lseek_get(cwp);
      for (rcpt_st = cwp->rcpt_st_first; rcpt_st; rcpt_st = rcpt_st->fwd) {
            rcpt_st->log_pos_to = cur_pos;
            LOG_CMN_CAPTION(cwp, DCC_XHDR_TYPE_ENV_TO": ");
            log_write(cwp, rcpt_st->env_to, 0);
            LOG_CMN_CAPTION(cwp, "  addr=");
            log_write(cwp, rcpt_st->user, 0);
            LOG_CMN_CAPTION(cwp, "  dir=");
            log_write(cwp, rcpt_st->dir, 0);
            LOG_CMN_EOL(cwp);
            cur_pos = log_lseek_get(cwp);
      }
      cwp->log_pos_to_end = cur_pos;

      /* log the blank line between the log file header and mail message */
      LOG_CMN_EOL(cwp);

      if (cwp->transaction > 1 && dcc_clnt_debug)
            thr_log_print(cwp, 1, "transaction #%d", cwp->transaction);
}



/* open the connection to the nearest DCC server */
u_char
ck_dcc_ctxt(CMN_WORK *cwp)
{
      if (cwp->dcc_ctxt_sn != dcc_ctxt_sn) {
            cwp->dcc_ctxt_sn = dcc_ctxt_sn;
            cwp->dcc_ctxt = dcc_clnt_init(cwp->emsg, cwp->dcc_ctxt, 0,
                                    DCC_CLNT_FG_NO_SRVR_OK
                                    | DCC_CLNT_FG_NO_FAIL);
            if (!cwp->dcc_ctxt) {
                  /* failed to create context */
                  thr_error_msg(cwp, "%s", cwp->emsg);
                  cwp->dcc_ctxt_sn = 0;
                  return 0;
            }
            cwp->xhdr_len = dcc_xhdr_start(cwp->xhdr, sizeof(cwp->xhdr),
                                     dcc_clnt_info);
      }
      return 1;
}



/* find and lock a per-user DCC_WF
 *    it is locked by grabbing the mutex for the main whiteclnt file */
static CWF *
find_cwf(RCPT_ST *rcpt_st)
{
      CWF *cwf;
      DCC_PATH white_nm_buf;
      const char *white_nm_ptr;

      if (rcpt_st->dir[0] == '\0') {
            rcpt_st->fgs |= RCPT_FG_NULL_WHITECLNT;
            return 0;
      }

      /* canonicalize the key */
      if (!fnm2path(white_nm_buf, rcpt_st->dir, "/whiteclnt")) {
            thr_error_msg(rcpt_st->cwp,
                        "long user whiteclnt name \"%s/whiteclnt\"",
                        rcpt_st->dir);
            rcpt_st->fgs |= RCPT_FG_NULL_WHITECLNT;
            return 0;
      }
      white_nm_ptr = path2fnm(white_nm_buf);

      lock_wf();
      cwf = cur_cwf;
      for (;;) {
            if (!strcmp(white_nm_ptr, cwf->wf.ascii_nm)) {
                  /* we found an old DCC_WF for the target file */
                  white_nm_ptr = 0;
                  break;
            }

            if (cwf->older == cur_cwf) {
                  /* We do not know this file.
                   * Recycle the oldest DCC_WF */
                  if (!dcc_new_white_nm(rcpt_st->cwp->emsg, &cwf->wf,
                                    white_nm_ptr)) {
                        thr_error_msg(rcpt_st->cwp, "%s",
                                    rcpt_st->cwp->emsg);
                        unlock_wf();
                        rcpt_st->fgs |= RCPT_FG_NULL_WHITECLNT;
                        return 0;
                  }
                  break;
            }

            cwf = cwf->older;
      }

      /* move to front */
      if (cwf != cur_cwf) {
            cwf->older->newer = cwf->newer;
            cwf->newer->older = cwf->older;
            cwf->older = cur_cwf;
            cwf->newer = cur_cwf->newer;
            cwf->newer->older = cwf;
            cwf->older->newer = cwf;
            cur_cwf = cwf;
      }

      switch (dcc_rdy_white(rcpt_st->cwp->emsg, &cwf->wf, &cmn_tmp_wf)) {
      case DCC_WHITE_OK:
            /* notice if the file contains no checksums or CIDR blocks */
            if (cwf->wf.info->hdr.entries == 0
                && cwf->wf.info->hdr.cidr.len == 0) {
                  rcpt_st->fgs |= RCPT_FG_NULL_WHITECLNT;
            } else {
                  rcpt_st->fgs &= ~RCPT_FG_NULL_WHITECLNT;
                  memcpy(rcpt_st->wf_sum, cwf->wf.info->hdr.ck_sum,
                         sizeof(rcpt_st->wf_sum));
            }
            return cwf;
      case DCC_WHITE_NOFILE:
            break;
      case DCC_WHITE_SILENT:
            if (dcc_clnt_debug)
                  thr_error_msg(rcpt_st->cwp, "%s", rcpt_st->cwp->emsg);
            break;
      case DCC_WHITE_CONTINUE:
      case DCC_WHITE_COMPLAIN:
            thr_error_msg(rcpt_st->cwp, "%s", rcpt_st->cwp->emsg);
            break;
      }

      unlock_wf();
      rcpt_st->fgs |= RCPT_FG_NULL_WHITECLNT;
      return 0;
}



/* digest the results of a whitelist */
static void
white_results(RCPT_ST *rcpt_st,
            CMN_WORK *cwp,
            RCPT_FGS *fgsp,
            DCC_WHITE_RESULT result,
            const DCC_WHITE_LISTING *listingp)
{

      DCC_WHITE_LISTING listing;

      /* call-by-reference parameter to resolve order of evaluation
       * in callers */
      listing = *listingp;

      /* override if the result of the whitelist lookup was bad */
      switch (result) {
      case DCC_WHITE_OK:
            break;
      case DCC_WHITE_SILENT:
      case DCC_WHITE_NOFILE:
            listing = DCC_WHITE_UNLISTED;
            break;
      case DCC_WHITE_COMPLAIN:
      case DCC_WHITE_CONTINUE:
            thr_error_msg(cwp, "%s", cwp->emsg);
            return;
      }

      switch (listing) {
      case DCC_WHITE_USE_DCC:
            /* "OK2" for the env_to checksum in the local whitelist
             * does not mean the address is half whitelisted,
             * but that it is ok to reject or discard spam for it based
             * on DCC results.
             * We get this value only from dcc_white_sum() */
            if (rcpt_st)
                  rcpt_st->sws &= ~FLTR_SW_DCC_OFF;
            break;

      case DCC_WHITE_LISTED:
            *fgsp |= RCPT_FG_WHITE;
            *fgsp &= ~RCPT_FG_BLACK;
            /* remember this hit for the log */
            *fgsp |= RCPT_FG_WLIST_NOTSPAM;
            cwp->rcpt_fgs |= RCPT_FG_WLIST_NOTSPAM;
            break;

      case DCC_WHITE_UNLISTED:
            break;

      case DCC_WHITE_BLACK:
            if (!(*fgsp & RCPT_FG_WHITE))
                  *fgsp |= RCPT_FG_BLACK;
            /* remember this hit for the log */
            *fgsp |= RCPT_FG_WLIST_ISSPAM;
            cwp->rcpt_fgs |= RCPT_FG_WLIST_ISSPAM;
            break;
      }
}



static void
rcpt_fgs2ask_st(CMN_WORK *cwp, FLTR_SWS sws, RCPT_FGS fgs)
{
      /* it is spam if it is blacklisted by any target */
      if (fgs & RCPT_FG_BLACK)
            cwp->ask_st |= (ASK_ST_CLNT_ISSPAM | ASK_ST_LOGIT);

      /* If we had a DNS blacklist hit on a URL
       * and this recipient believes the DNS blacklists,
       * then it is spam to report to DCC server */
      if ((sws & FLTR_SW_DNSBL_ON)
          && (cwp->ask_st & ASK_ST_DNSBL_ISSPAM))
            cwp->ask_st |= (ASK_ST_CLNT_ISSPAM | ASK_ST_LOGIT);

      /* If the external flter said this is spam
       * and this recipient cares,
       * then it is spam to report to the DCC server */
      if ((sws & FLTR_SW_XFLTR_ON)
          && (cwp->ask_st & ASK_ST_XFLTR_ISSPAM))
            cwp->ask_st |= (ASK_ST_CLNT_ISSPAM | ASK_ST_LOGIT);
}



static FLTR_SWS
wf2rcpt_st_sws(FLTR_SWS sws, const DCC_WF *wf)
{
      static u_char once;
      DCC_PATH abs_nm;

      if (!once
          && (wf->info_flags & (DCC_WHITE_FG_GREY_ON
                          | DCC_WHITE_FG_GREY_LOG_ON))) {
            once = 1;
            if (!grey_on)
                  dcc_error_msg("%s wants greylisting"
                              " but it is turned off",
                              fnm2path_err(abs_nm, wf->ascii_nm));
      }

      /* compute switch values from whiteclnt bits */
      if (wf->info_flags & DCC_WHITE_FG_DISCARD_NOK)
            sws |= FLTR_SW_DISCARD_NOK;
      else if (wf->info_flags & DCC_WHITE_FG_DISCARD_OK)
            sws &= ~FLTR_SW_DISCARD_NOK;

      if ((wf->info_flags & DCC_WHITE_FG_DCC_OFF))
            sws |= FLTR_SW_DCC_OFF;
      else if (wf->info_flags & DCC_WHITE_FG_DCC_ON)
            sws &= ~FLTR_SW_DCC_OFF;

      if (wf->info_flags & DCC_WHITE_FG_GREY_ON) {
            sws &= ~FLTR_SW_GREY_OFF;
      }
      if (!grey_on || (wf->info_flags & DCC_WHITE_FG_GREY_OFF))
            sws |= FLTR_SW_GREY_OFF;

      if (wf->info_flags & DCC_WHITE_FG_GREY_LOG_ON) {
            sws &= ~FLTR_SW_GREY_LOG_OFF;
      } else if (wf->info_flags & DCC_WHITE_FG_GREY_LOG_OFF) {
            sws |= FLTR_SW_GREY_LOG_OFF;
      }

      if (wf->info_flags & DCC_WHITE_FG_LOG_ALL) {
            sws |= FLTR_SW_LOG_ALL;
      } else if (wf->info_flags & DCC_WHITE_FG_LOG_NORMAL) {
            sws &= ~FLTR_SW_LOG_ALL;
      }

      if (wf->info_flags & DCC_WHITE_FG_MTA_FIRST) {
            sws |= FLTR_SW_MTA_FIRST;
      } else if (wf->info_flags & DCC_WHITE_FG_MTA_LAST) {
            sws &= ~FLTR_SW_MTA_FIRST;
      }

      if ((wf->info_flags & DCC_WHITE_FG_DNSBL_ON)
          && dnsbls) {
            sws |= FLTR_SW_DNSBL_ON;
      } else if (wf->info_flags & DCC_WHITE_FG_DNSBL_OFF) {
            sws &= ~FLTR_SW_DNSBL_ON;
      }

#ifdef USE_XFLTR
      if ((wf->info_flags & DCC_WHITE_FG_XFLTR_ON)
          && xfltr_parm) {
            sws |= FLTR_SW_XFLTR_ON;
      } else if (wf->info_flags & DCC_WHITE_FG_XFLTR_OFF) {
            sws &= ~FLTR_SW_XFLTR_ON;
      }
#endif

      return sws | FLTR_SW_SET;
}



/* set the defaults for the options */
static void
rcpt_sws_def(CMN_WORK *cwp, u_char locked)
{
      if (cwp->init_sws & FLTR_SW_SET)
            return;

      if (!locked)
            lock_wf();

      if (to_white_only)
            cwp->init_sws |= FLTR_SW_DCC_OFF;
      if (discard_nok_def)
            cwp->init_sws |= FLTR_SW_DISCARD_NOK;
      cwp->init_sws = wf2rcpt_st_sws(cwp->init_sws, &cmn_wf);
      cwp->rcpts_sws = cwp->init_sws;

      if (!locked)
            unlock_wf();
}



/* set per-user switches and compute env_to whitelisting
 *    If on entry we have a per-user whitelist, then we hold the common
 *    whitelist lock to protect the per-user whitelist.  The lock
 *    is still held on exit if we return a pointer */
static CWF *
rcpt_sws_env_to(CMN_WORK *cwp, RCPT_ST *rcpt_st)
{
      CWF *cwf;
      DCC_WHITE_LISTING listing;

      /* finished after finding the whitelist
       * if we have already checked its settings */
      if (rcpt_st->fgs & RCPT_FG_NULL_WHITECLNT)
            return 0;
      cwf = find_cwf(rcpt_st);
      if (rcpt_st->sws & FLTR_SW_SET)
            return cwf;

      /* The first time, we must get the global switch settings.
       * if we have a per-user whitelist, then we have locked cmn_wf
       * to protect the per-user whitelist data structures */
      rcpt_sws_def(cwp, cwf != 0);

      /* get flags and filter settings for recipient */
      if (cwf)
            rcpt_st->sws = wf2rcpt_st_sws(cwp->init_sws, &cwf->wf);
      else
            rcpt_st->sws = cwp->init_sws;

      /* remove any reset _ON bits from the consensus */
      cwp->rcpts_sws &= (rcpt_st->sws | ~FLTR_SWS_SETTINGS_ON);
      /* copy any set _OFF bits to the consensus */
      cwp->rcpts_sws |= (rcpt_st->sws & FLTR_SWS_SETTINGS_OFF);

      /* if we have a per-user whitelist, then we have locked cmn_wf
       * to protect the per-user whitelist data structures */
      if (!cwf)
            lock_wf();

      /* check the env_to address */
      dcc_str2ck(rcpt_st->env_to_sum, 0, 0, rcpt_st->env_to);
      rcpt_st->global_env_to_fgs = 0;
      white_results(rcpt_st, cwp, &rcpt_st->global_env_to_fgs,
                  dcc_white_sum(cwp->emsg, &cmn_wf,
                            DCC_CK_ENV_TO, rcpt_st->env_to_sum,
                            &rcpt_st->env_to_tgts, &listing),
                  &listing);
      if (listing != DCC_WHITE_UNLISTED)
            cwp->cmn_fgs |= CMN_FG_LOG_ENV_TO;

      /* check the mailbox name (after aliases etc.) */
      if (rcpt_st->user[0] != '\0'
          && strcmp(rcpt_st->env_to,rcpt_st->user)) {
            dcc_str2ck(rcpt_st->user_sum, 0, 0, rcpt_st->user);
            white_results(rcpt_st, cwp, &rcpt_st->global_env_to_fgs,
                        dcc_white_sum(cwp->emsg, &cmn_wf,
                                  DCC_CK_ENV_TO, rcpt_st->user_sum,
                                  &rcpt_st->user_tgts, &listing),
                        &listing);
            if (listing != DCC_WHITE_UNLISTED)
                  cwp->cmn_fgs |= CMN_FG_LOG_ENV_TO;
      }
      if (!cwf) {
            unlock_wf();

      } else {
            /* save envelope Rcpt_To value and the mailbox name
             * (after aliases etc.) white- or blacklisting and
             * the DCC_WHITE_USE_DCC setting */
            rcpt_st->env_to_fgs = 0;
            white_results(rcpt_st, cwp, &rcpt_st->env_to_fgs,
                        dcc_white_sum(cwp->emsg, &cwf->wf,
                                  DCC_CK_ENV_TO, rcpt_st->env_to_sum,
                                  &rcpt_st->env_to_tgts, &listing),
                        &listing);
            if (rcpt_st->user[0] != '\0'
                && strcmp(rcpt_st->env_to,rcpt_st->user)) {
                  white_results(rcpt_st, cwp, &rcpt_st->env_to_fgs,
                              dcc_white_sum(cwp->emsg, &cwf->wf,
                                        DCC_CK_ENV_TO,
                                        rcpt_st->user_sum,
                                        &rcpt_st->user_tgts,
                                        &listing),
                              &listing);
            }
      }

      return cwf;
}



/* see if a recipient's whitelist decision is certain to be the same
 * as all preceding recipients */
u_char                              /* 0=must reject this recipient */
cmn_compat_whitelist(CMN_WORK *cwp,
                 RCPT_ST *rcpt_st_new)
{
      RCPT_ST *rcpt_st2;
      FLTR_SWS save_rcpts_sws;

      /* everything is compatible if we won't reject */
      if (cwp->action != CMN_REJECT)
            return 1;

      /* postpone poking at whitelists on the first recipient */
      rcpt_st2 = cwp->rcpt_st_first;
      if (rcpt_st_new == rcpt_st2)
            return 1;

      /* get the settings that we postponed when we saw the first recipient */
      if (!(rcpt_st2->sws & FLTR_SW_SET)
          && rcpt_sws_env_to(cwp, rcpt_st2))
            unlock_wf();

      /* See if this message might need to be accept for this recipient
       * but rejected for some other recipient that does not want
       * forced discarding or if this recipient does not want forced
       * discarding and has weaker filtering than other recipients. */
      save_rcpts_sws = cwp->rcpts_sws;
      if (rcpt_sws_env_to(cwp, rcpt_st_new))
            unlock_wf();

      /* everything is compatible if discarding is ok */
      if (!(cwp->rcpts_sws & FLTR_SW_DISCARD_NOK))
            return 1;

      /* If the SMTP client is whitelisted, then everything is compatible,
       * but we must reject the message for all or no recipients.
       * This is because the SMTP client might be a mail submission
       * client that is too dumb to do the right thing with 4yz
       * rejections of individual Rcpt_To commands */
      if (cwp->cmn_fgs & CMN_FG_IP_WHITE) {
            cwp->cmn_fgs |= CMN_FG_ALL_REJECT;
            return 1;
      }

      do {
            if (rcpt_st2->temp_rej_msg[0] != '\0')
                  continue;   /* ignore alread rejected recipients */

            /* Differing whitelists make it possible that the message
             * could need to be rejected for one recipient but accepted
             * for the other */
            if (((rcpt_st2->sws & FLTR_SW_DISCARD_NOK)
                 || (rcpt_st_new->sws & FLTR_SW_DISCARD_NOK))
                && (((rcpt_st2->fgs ^ rcpt_st_new->fgs)
                   & RCPT_FG_NULL_WHITECLNT) != 0
                  || (!(rcpt_st2->fgs & RCPT_FG_NULL_WHITECLNT)
                      && memcmp(rcpt_st2->wf_sum, rcpt_st_new->wf_sum,
                              sizeof(rcpt_st2->wf_sum))))) {
                  cwp->rcpts_sws = save_rcpts_sws;
                  return 0;
            }

            /* Stronger filter choices for an existing recipient imply
             * potential reasons to reject the message not shared by the
             * new recipient and that could force the message to be
             * discarded for the existing recipient */
            if ((rcpt_st2->sws & FLTR_SW_DISCARD_NOK)
                && ((FLTR_SWS_ON(rcpt_st2->sws)
                   & ~FLTR_SWS_ON(rcpt_st_new->sws)) != 0)) {
                  cwp->rcpts_sws = save_rcpts_sws;
                  return 0;
            }

            /* weaker filter choices for an existing recipient imply
             * potential reasons to reject the message not shared by the
             * preceding recipient and that could force the message to
             * be discarded for the new recipient */
            if ((rcpt_st_new->sws & FLTR_SW_DISCARD_NOK)
                && ((~FLTR_SWS_ON(rcpt_st2->sws)
                   & FLTR_SWS_ON(rcpt_st_new->sws)) != 0)) {
                  cwp->rcpts_sws = save_rcpts_sws;
                  return 0;
            }
      } while ((rcpt_st2 = rcpt_st2->fwd) != rcpt_st_new);

      return 1;
}



/* check the whitelists for a single user or target */
static void
ask_white_rcpt(CMN_WORK *cwp,
             RCPT_ST *rcpt_st,
             RCPT_FGS global_fgs)
{
      DCC_WHITE_LISTING listing;
      CWF *cwf;
      DCC_PATH abs_nm;

      rcpt_st->log_pos_white = log_lseek_get(cwp);

      /* quit after capturing the log position if the recipient is bad */
      if (rcpt_st->temp_rej_msg[0] != '\0')
            return;

      /* Get the switch settings and the env_to whitelist results. */
      cwf = rcpt_sws_env_to(cwp, rcpt_st);

      /* Compute white- or blacklisting.
       *    The per-user whiteclnt file overrides the global file.
       *    The whiteclnt files override the MTA with "option MTA-first".
       *        Without, the MTA controls.
       *    Within each category, whitelisting overrides blacklisting.
       *
       *    The global whitelist's answer for message's checksums other
       *        than the env_to checksums have already been computed in
       *        global_fgs. */

      if (global_fgs != 0) {
            rcpt_st->fgs &= ~(RCPT_FG_WHITE | RCPT_FG_BLACK);
            rcpt_st->fgs |= global_fgs;
      }

      if (rcpt_st->sws & FLTR_SW_MTA_FIRST) {
            if (cwp->ask_st & ASK_ST_MTA_NOTSPAM) {
                  rcpt_st->fgs |= RCPT_FG_WHITE;
                  rcpt_st->fgs &= ~RCPT_FG_BLACK;
            } else if (cwp->ask_st & ASK_ST_MTA_ISSPAM) {
                  rcpt_st->fgs |= RCPT_FG_BLACK;
                  rcpt_st->fgs &= ~RCPT_FG_WHITE;
            }
      }

      /* apply the previously computed env_to global whitelist results
       * as well as the other global whitelist results */
      if ((rcpt_st->global_env_to_fgs | global_fgs) & RCPT_FG_WHITE) {
            rcpt_st->fgs &= ~RCPT_FG_BLACK;
            rcpt_st->fgs |= RCPT_FG_WHITE;
      } else if ((rcpt_st->global_env_to_fgs | global_fgs) & RCPT_FG_BLACK) {
            rcpt_st->fgs &= ~RCPT_FG_WHITE;
            rcpt_st->fgs |= RCPT_FG_BLACK;
      }

      if (!cwf) {
            /* Without a per-user whitelist or without any entries in
             * the per-user whitelist, we will be using the
             * global whitelist for the other messages checksums.
             * Arrange to include those results in the per-user log file */
            memcpy(rcpt_st->wtgts, cwp->wtgts,
                   sizeof(rcpt_st->wtgts));

      } else {
            /* Check other message checksums in per-user whitelist */
            white_results(rcpt_st, cwp, &rcpt_st->env_to_fgs,
                        dcc_white_cks(cwp->emsg, &cwf->wf,
                                  &cwp->cks,
                                  rcpt_st->wtgts, &listing),
                        &listing);

            if (rcpt_st->env_to_fgs == 0) {
                  /* Without an answer from the per-user whitelist,
                   * we will be using the global whitelist.
                   * So arrange to include those results in the per-user
                   * log file */
                  memcpy(rcpt_st->wtgts, cwp->wtgts,
                         sizeof(rcpt_st->wtgts));

            } else {
                  thr_log_print(cwp, 0, "%s%s\n",
                              fnm2path_err(abs_nm, cwf->wf.ascii_nm),
                              (rcpt_st->env_to_fgs & RCPT_FG_WHITE)
                              ? DCC_XHDR_ISOK
                              : DCC_XHDR_ISSPAM);
                  rcpt_st->fgs &= ~(RCPT_FG_WHITE | RCPT_FG_BLACK);
                  rcpt_st->fgs |= rcpt_st->env_to_fgs;
            }

            /* release common lock that protected the per-user whitelist
             * because we are finished with the per-user whitelist */
            unlock_wf();
      }

      if (rcpt_st->env_to_tgts != 0
          || rcpt_st->user_tgts != 0)
            cwp->cmn_fgs |= CMN_FG_LOG_ENV_TO;

      if (!(rcpt_st->sws & FLTR_SW_MTA_FIRST)) {
            if (cwp->ask_st & ASK_ST_MTA_NOTSPAM) {
                  rcpt_st->fgs |= RCPT_FG_WHITE;
                  rcpt_st->fgs &= ~RCPT_FG_BLACK;
            } else if (cwp->ask_st & ASK_ST_MTA_ISSPAM) {
                  rcpt_st->fgs |= RCPT_FG_BLACK;
                  rcpt_st->fgs &= ~RCPT_FG_WHITE;
            }
      }
}



/* check the whitelists for all targets */
void
cmn_ask_white(CMN_WORK *cwp, u_char mta_grey_query)
{
      RCPT_ST *rcpt_st;
      RCPT_FGS global_fgs;
      DCC_OPS grey_op;
      DCC_WHITE_LISTING listing;

      /* decide to log sendmail access_db spam now after early checks which
       * would cause log files for aborted transactions */
      if (cwp->ask_st & ASK_ST_MTA_ISSPAM)
            cwp->ask_st |= ASK_ST_LOGIT;

      dcc_dnsbl_result(&cwp->ask_st, &cwp->dnsbl_reply, cwp->cks.dnsbl);

#ifdef USE_XFLTR
      if (cwp->tmp_fd >= 0
          && xfltr_parm) {
            cwp->xfltr_header_len = sizeof(cwp->xfltr_header);
            if (ask_xfltr(cwp->dcc_ctxt, cwp,
                        cwp->xfltr_header, &cwp->xfltr_header_len,
                        cwp->id,
                        cwp->sender_name, &cwp->sender_addr,
                        cwp->helo, cwp->env_from, cwp->num_rcpts,
                        cwp->tmp_nm)) {
                  cwp->ask_st |= (ASK_ST_XFLTR_ISSPAM | ASK_ST_LOGIT);
            }
      }
#endif

      cwp->log_pos_white_first = log_lseek_get(cwp);

      /* Use the main whitelist only for recipients whose individual
       * whitelists don't give a black or white answer.
       * Check the main whitelist first (and so even if not necessary)
       * so that problems with it are in all of the logs and to simplify
       * merging the global and per-user whitelist results. */
      lock_wf();
      global_fgs = 0;
      white_results(0, cwp, &global_fgs,
                  dcc_white_cks(cwp->emsg, &cmn_wf, &cwp->cks,
                            cwp->wtgts, &listing),
                  &listing);

      /* get the defaults for the options */
      rcpt_sws_def(cwp, 1);
      unlock_wf();

      /* special kludge like ask_white_rcpt() for no recipients for dccifd
       *    leave cwp->white_tgts == 1 and != cwp->tgts
       *    if the message is not whitelisted so that dccifd will
       *    ask the DCC server
       */
      if ((rcpt_st = cwp->rcpt_st_first) == 0) {
            if (cwp->init_sws & FLTR_SW_MTA_FIRST) {
                  if (cwp->ask_st & ASK_ST_MTA_NOTSPAM) {
                        cwp->rcpt_fgs |= RCPT_FG_WHITE;
                        cwp->rcpt_fgs &= ~RCPT_FG_BLACK;
                  } else if (cwp->ask_st & ASK_ST_MTA_ISSPAM) {
                        cwp->rcpt_fgs |= RCPT_FG_BLACK;
                        cwp->rcpt_fgs &= ~RCPT_FG_WHITE;
                  }
            }
            if (global_fgs != 0) {
                  cwp->rcpt_fgs &= ~(RCPT_FG_WHITE | RCPT_FG_BLACK);
                  cwp->rcpt_fgs |= global_fgs;
            }
            if (!(cwp->init_sws & FLTR_SW_MTA_FIRST)) {
                  if (cwp->ask_st & ASK_ST_MTA_NOTSPAM) {
                        cwp->rcpt_fgs |= RCPT_FG_WHITE;
                        cwp->rcpt_fgs &= ~RCPT_FG_BLACK;
                  } else if (cwp->ask_st & ASK_ST_MTA_ISSPAM) {
                        cwp->rcpt_fgs |= RCPT_FG_BLACK;
                        cwp->rcpt_fgs &= ~RCPT_FG_WHITE;
                  }
            }
            if (!(cwp->rcpt_fgs & RCPT_FG_WHITE))
                  ++cwp->white_tgts;
            rcpt_fgs2ask_st(cwp, cwp->init_sws, cwp->rcpt_fgs);
      }

      for (; rcpt_st; rcpt_st = rcpt_st->fwd) {
            /* maybe this recipient is whitelisted or a spam trap
             * or has per-user option settings */
            ask_white_rcpt(cwp, rcpt_st, global_fgs);

            /* quit after capturing the log position if recipient bad */
            if (rcpt_st->temp_rej_msg[0] != '\0') {
                  rcpt_st->grey_result = ASK_GREY_OFF;
                  continue;
            }

            /* We need to know if all targets are whitelisted for the DCC
             * before we ask the DCC server.  Mail sent only to whitelisted
             * targets should not be reported to the DCC server.
             * For that we need a count of white-listed targets */
            if (rcpt_st->fgs & RCPT_FG_WHITE) {
                  ++cwp->white_tgts;
            } else {
                  rcpt_fgs2ask_st(cwp, rcpt_st->sws, rcpt_st->fgs);
            }

            if ((rcpt_st->sws & FLTR_SW_GREY_OFF)
                || (cwp->cmn_fgs & CMN_FG_MX_GREY_OFF)) {
                  rcpt_st->grey_result = ASK_GREY_OFF;
                  continue;
            }

            if ((rcpt_st->fgs & RCPT_FG_BLACK)
                || grey_query_only
                || mta_grey_query)
                  grey_op = DCC_OP_GREY_QUERY;
            else if (rcpt_st->fgs & RCPT_FG_WHITE)
                  grey_op = DCC_OP_GREY_WHITE;
            else
                  grey_op = DCC_OP_GREY_REPORT;
            rcpt_st->grey_result = ask_grey(cwp->emsg, cwp->dcc_ctxt,
                                    grey_op,
                                    rcpt_st->msg_sum,
                                    rcpt_st->triple_sum,
                                    &cwp->cks,
                                    rcpt_st->env_to_sum,
                                    &rcpt_st->embargo_num,
                                    &cwp->early_grey_tgts,
                                    &cwp->late_grey_tgts);

            switch (rcpt_st->grey_result) {
            case ASK_GREY_OFF:
            case ASK_GREY_SPAM:
                  dcc_logbad(EX_SOFTWARE,
                           "cmn_ask_white grey_result=%d",
                           rcpt_st->grey_result);
                  break;
            case ASK_GREY_FAIL:
                  thr_error_msg(cwp, "%s", cwp->emsg);
                  /* If we are trying hard, assume the
                   * message would have been embargoed */
                  if (try_extra_hard)
                        cwp->ask_st |= (ASK_ST_GREY_EMBARGO
                                    | ASK_ST_GREY_LOGIT);
                  break;
            case ASK_GREY_EMBARGO:
                  if (rcpt_st->embargo_num == 1
                      && (rcpt_st->fgs & RCPT_FG_BLACK)) {
                        /* don't bother revoking non-existent entry */
                        rcpt_st->grey_result = ASK_GREY_OFF;
                  } else {
                        cwp->ask_st |= (ASK_ST_GREY_EMBARGO
                                    | ASK_ST_GREY_LOGIT);
                  }
                  break;
            case ASK_GREY_EMBARGO_END:
                  cwp->ask_st |= ASK_ST_GREY_LOGIT;
                  cwp->rcpt_fgs |= RCPT_FG_GREY_END;
                  break;
            case ASK_GREY_PASS:
                  break;
            case ASK_GREY_WHITE:
                  rcpt_st->fgs |= RCPT_FG_GREY_WHITE;
                  break;
            }
      }

      cwp->log_pos_white_last = log_lseek_get(cwp);
}



/* ask a DCC server */
int                           /* <0=big problem, 0=retryable, 1=ok */
cmn_ask_dcc(CMN_WORK *cwp)
{
      int i;

      /* Talk to the DCC server and make the X-DCC header.
       * If we have blacklist entries for it, then we'll tell the DCC
       * server it is spam and say so in the X-DCC header.
       * Note that a target count of 0 is a query. */
      if (dcc_query_only
          || (cwp->cmn_fgs & CMN_FG_MTA_QUERY)) {
            cwp->cmn_fgs &= ~CMN_FG_LOCAL_SPAM;
      } else if (cwp->ask_st & ASK_ST_CLNT_ISSPAM) {
            cwp->cmn_fgs |= CMN_FG_LOCAL_SPAM;
            cwp->local_tgts = cwp->tgts;
      } else if (cwp->ask_st & ASK_ST_GREY_EMBARGO) {
            /* if the message is under a greylist embargo,
             * then report to the DCC only the targets for
             * which it is an initial transmission or embargo #1 */
            cwp->cmn_fgs &= ~CMN_FG_LOCAL_SPAM;
            cwp->local_tgts = cwp->early_grey_tgts;
      } else {
            /* if this is the end of a greylist embargo
             * then do not tell the DCC about targets that were
             * counted with previous transmissions.  Those targets
             * are counted in late_grey_tgts this time, but were
             * counted in early_grey_tgts for previous transmissions */
            cwp->cmn_fgs &= ~CMN_FG_LOCAL_SPAM;
            cwp->local_tgts = cwp->tgts - cwp->late_grey_tgts;
      }
      i = ask_dcc(cwp->emsg, cwp->dcc_ctxt, try_extra_hard,
                &cwp->header, &cwp->cks, &cwp->ask_st,
                (cwp->cmn_fgs & CMN_FG_LOCAL_SPAM) != 0,
                cwp->local_tgts);
      if (i <= 0) {
            thr_error_msg(cwp, "%s", cwp->emsg);
            return i;
      }

      /* if we are talking to a new server,
       * remember to fix the X-DCC headers of the other contexts */
      if (cwp->xhdr_len != cwp->header.start_len
          || strncmp(cwp->header.buf, cwp->xhdr, cwp->xhdr_len)) {
            if (dcc_clnt_debug)
                  thr_trace_msg(cwp, DCC_XHDR_START
                              "header changed from %s to %.*s",
                              cwp->xhdr,
                              (int)cwp->header.start_len,
                              cwp->header.buf);
            cwp->xhdr_len = dcc_xhdr_start(cwp->xhdr, sizeof(cwp->xhdr),
                                     dcc_clnt_info);
            cwp->dcc_ctxt_sn = ++dcc_ctxt_sn;
      }

      return 1;
}


#define USER_LOG_CAPTION(rcpt_st, s) user_log_write((rcpt_st), (s), sizeof(s)-1)
#define USER_LOG_EOL(rcpt_st) USER_LOG_CAPTION((rcpt_st), "\n")

static u_char
user_log_write(RCPT_ST *rcpt_st, const void *buf, u_int len)
{
      DCC_PATH abs_nm;
      int result;

      if (rcpt_st->user_log_fd < 0)
            return 0;

      if (!len)
            len = strlen(buf);
      result = write(rcpt_st->user_log_fd, buf, len);
      if (result == (int)len)
            return 1;

      if (result < 0)
            thr_error_msg(rcpt_st->cwp, "write(%s): %s",
                        fnm2path_err(abs_nm, rcpt_st->user_log_nm),
                        ERROR_STR());
      else
            thr_error_msg(rcpt_st->cwp,
                        "write(%s)=%d instead of %d",
                        fnm2path_err(abs_nm, rcpt_st->user_log_nm),
                        result, (int)len);
      dcc_log_close(0, rcpt_st->user_log_nm, rcpt_st->user_log_fd,
                  &rcpt_st->cwp->ldate);
      rcpt_st->user_log_fd = -1;
      return 0;
}



static void PATTRIB(2,3)
user_log_print(RCPT_ST *rcpt_st, const char *p, ...)
{
      char logbuf[MAXHOSTNAMELEN*3];
      va_list args;

      va_start(args, p);
      vsnprintf(logbuf, sizeof(logbuf), p, args);
      va_end(args);
      user_log_write(rcpt_st, logbuf, 0);
}



static void
user_log_block(RCPT_ST *rcpt_st,    /* copy from main log file to this */
             off_t start,           /* starting here */
             off_t stop)            /* and ending here */
{
      CMN_WORK *cwp;
      char buf[4096];
      off_t len;
      int result;

      cwp = rcpt_st->cwp;

      if (rcpt_st->user_log_fd < 0)
            return;

      if (start == -1 || stop == -1) {
            thr_error_msg(cwp, "bogus user_log_block position");
            return;
      }

      if (!log_lseek_set(cwp, start))
            return;

      while ((len = stop - start) != 0) {
            if (len > (off_t)sizeof(buf))
                  len = sizeof(buf);
            result = read(cwp->log_fd2, buf, len);
            if (result != len) {
                  if (result < 0)
                        thr_error_msg(cwp,
                                    "user_log_block read(%s): %s",
                                    cwp->log_nm, ERROR_STR());
                  else
                        thr_error_msg(cwp, "user_log_block read(%s)=%d"
                                    " instead of %d",
                                    cwp->log_nm, result, (int)len);
                  log_fd2_close(cwp, -2);
                  return;
            }
            if (!user_log_write(rcpt_st, buf, len))
                  return;
            start += len;
      }
}



static void
print_addr_sum(LOG_WRITE_FNC out, void *arg,
             const char *addr, int addr_len,
             const char *sum, int sum_len,
             const char *tgts, int tgts_width)
{
      char buf[100];
      int i;

      if (addr_len <= PRINT_CK_PAT_LEN) {
            i = snprintf(buf, sizeof(buf), PRINT_CK_L1PAT_CK" %*s\n",
                       addr_len, addr,
                       addr_len > 0 ? ':' : ' ',
                       sum_len, sum,
                       tgts_width, tgts);
      } else {
            i = snprintf(buf, sizeof(buf), PRINT_CK_L2PAT_CK" %*s\n",
                       addr,
                       sum_len, sum,
                       tgts_width, tgts);
      }
      if (i >= ISZ(buf)) {
            i = sizeof(buf);
            buf[i-1] = '\n';
      }
      out(arg, buf, i);
}



static void
print_env_to(LOG_WRITE_FNC out, void *arg, const RCPT_ST *rcpt_st)
{
      const char *addr;
      int addr_len;
      char tgts_buf[16];

      if (rcpt_st->env_to_tgts != 0) {
            addr = rcpt_st->env_to;
            addr_len = strlen(addr);
            if (addr_len > 1 && addr[0] == '<' && addr[addr_len-1] == '>') {
                  ++addr;
                  addr_len -= 2;
            }
            print_addr_sum(out, arg,
                         DCC_XHDR_TYPE_ENV_TO, STRZ(DCC_XHDR_TYPE_ENV_TO),
                         addr, addr_len,
                         dcc_tgts2str(tgts_buf, sizeof(tgts_buf),
                                  rcpt_st->env_to_tgts, 0),
                         PRINT_CK_PAT_SRVR_LEN+PRINT_CK_PAT_WLIST_LEN);
      }

      if (rcpt_st->user_tgts != 0) {
            addr = rcpt_st->user;
            addr_len = strlen(addr);
            if (addr_len > 1 && addr[0] == '<' && addr[addr_len-1] == '>') {
                  ++addr;
                  addr_len -= 2;
            }
            print_addr_sum(out, arg,
                         DCC_XHDR_TYPE_ENV_TO, STRZ(DCC_XHDR_TYPE_ENV_TO),
                         addr, addr_len,
                         dcc_tgts2str(tgts_buf, sizeof(tgts_buf),
                                  rcpt_st->user_tgts, 0),
                         PRINT_CK_PAT_SRVR_LEN+PRINT_CK_PAT_WLIST_LEN);
      }
}



static void
print_grey(LOG_WRITE_FNC out, void *arg,
         const RCPT_ST *rcpt_st, u_char *headed)
{
#define CK_HEADING "       "DCC_XHDR_GREY_RECIP"\n"
      char cbuf[DCC_CK2STR_LEN];
      char embargo_buf[20];
      const char *addr;
      int addr_len;
      const char *embargo;

      embargo = 0;
      switch (rcpt_st->grey_result) {
      case ASK_GREY_FAIL:
            embargo = DCC_XHDR_EMBARGO_FAIL;
            break;
      case ASK_GREY_OFF:
            return;
      case ASK_GREY_EMBARGO:
            if (rcpt_st->embargo_num > 0) {
                  snprintf(embargo_buf, sizeof(embargo_buf),
                         DCC_XHDR_EMBARGO_NUM,
                         rcpt_st->embargo_num);
                  embargo = embargo_buf;
            } else {
                  embargo = DCC_XHDR_EMBARGO;
            }
            break;
      case ASK_GREY_EMBARGO_END:
            embargo = DCC_XHDR_EMBARGO_ENDED;
            break;
      case ASK_GREY_PASS:
            embargo = DCC_XHDR_EMBARGO_PASS;
            break;
      case ASK_GREY_WHITE:
            embargo = DCC_XHDR_EMBARGO_OK;
            break;
      case ASK_GREY_SPAM:
            snprintf(embargo_buf, sizeof(embargo_buf),
                   DCC_XHDR_EMBARGO_REST,
                   rcpt_st->embargo_num);
            embargo = embargo_buf;
            break;
      }

      if (!headed || !*headed) {
            if (headed)
                  *headed = 1;
            out(arg, CK_HEADING, STRZ(CK_HEADING));
      }

      addr = rcpt_st->env_to;
      addr_len = strlen(addr);
      if (addr_len > 1 && addr[0] == '<' && addr[addr_len-1] == '>') {
            ++addr;
            addr_len -= 2;
      }
      print_addr_sum(out, arg, addr, addr_len,
                   dcc_ck2str(cbuf, sizeof(cbuf),
                          DCC_CK_GREY_MSG, rcpt_st->msg_sum),
                   PRINT_CK_SUM_LEN,
                   "", 0);
      print_addr_sum(out, arg, "", 0,
                   dcc_ck2str(cbuf, sizeof(cbuf),
                          DCC_CK_GREY3, rcpt_st->triple_sum),
                   PRINT_CK_SUM_LEN,
                   embargo, 0);

      if (!headed)
            out(arg, "\n", 1);

#undef CK_HEADING
}



/* log external header, X-DCC header, and DCC results */
static void
log_isspam(LOG_WRITE_FNC fnc, void *cp, CMN_WORK *cwp,
         const char *ftype, int ftype_len,
         FLTR_SWS sws, RCPT_FGS rcpt_fgs)
{
      ASK_ST ask_st;

      ask_st = cwp->ask_st;
      if (rcpt_fgs & RCPT_FG_WLIST_NOTSPAM)
            ask_st |= ASK_ST_WLIST_NOTSPAM;
      if (rcpt_fgs & RCPT_FG_WLIST_ISSPAM)
            ask_st |= ASK_ST_WLIST_ISSPAM;
      log_ask_st(fnc, cp, ask_st, sws, ftype, ftype_len,
#ifdef USE_XFLTR
                cwp->xfltr_header, cwp->xfltr_header_len,
#endif
                &cwp->header);
}



static void
user_log_msg(CMN_WORK *cwp, RCPT_ST *rcpt_st)
{
      DCC_PATH rcpt_logdir;
      int log_mode;
      int i;

      cwp->ask_st |= ASK_ST_LOG_ONE;

      if (dcc_logdir[0] == '\0'
          || rcpt_st->dir[0] == '\0')
            return;

      snprintf(rcpt_logdir, sizeof(rcpt_logdir), "%s/log", rcpt_st->dir);
      i = dcc_logdir_ck(cwp->emsg, rcpt_logdir, &log_mode);
      if (i <= 0) {
            if (i < 0)
                  thr_error_msg(cwp, "%s", cwp->emsg);
            return;
      }

      /* since the user wants a log file, make one for the system */
      cwp->ask_st |= ASK_ST_LOGIT;

      if (!log2_start(cwp))
            return;

      /* create the user's log file */
      rcpt_st->user_log_fd = dcc_mkstemp(cwp->emsg, rcpt_st->user_log_nm,
                                 sizeof(rcpt_st->user_log_nm),
                                 cwp->id, sizeof(cwp->id),
                                 rcpt_logdir, DCC_FIN_LOG_PAT,
                                 0, -1, log_mode & 0644);
      if (rcpt_st->user_log_fd < 0) {
            thr_error_msg(cwp, "%s", cwp->emsg);
            return;
      }

      user_log_block(rcpt_st,       /* copy envelope before env_To line */
                   0, cwp->log_pos_to_first);
      user_log_block(rcpt_st,       /* copy this env_To line */
                   rcpt_st->log_pos_to,
                   rcpt_st->fwd
                   ? rcpt_st->fwd->log_pos_to
                   : cwp->log_pos_to_end);
      user_log_block(rcpt_st,       /* copy the body of the message */
                   cwp->log_pos_to_end,
                   cwp->log_pos_white_first);
      user_log_block(rcpt_st,       /* copy whitelist error messages */
                   rcpt_st->log_pos_white,
                   rcpt_st->fwd
                   ? rcpt_st->fwd->log_pos_white
                   : cwp->log_pos_white_last);

      /* log external header, X-DCC header, and DCC results */
      log_isspam((LOG_WRITE_FNC)user_log_write, rcpt_st, cwp,
               "per-user", STRZ("per-user"),
               rcpt_st->sws, rcpt_st->fgs);

      /* log the checksums and their counts */
      dcc_print_cks((LOG_WRITE_FNC)user_log_write, rcpt_st,
                  cwp->cmn_fgs & CMN_FG_LOCAL_SPAM, cwp->local_tgts,
                  &cwp->cks, rcpt_st->wtgts,
                  (cwp->cmn_fgs & CMN_FG_LOG_ENV_TO) != 0);
      print_env_to((LOG_WRITE_FNC)user_log_write, rcpt_st, rcpt_st);
      USER_LOG_EOL(rcpt_st);
      print_grey((LOG_WRITE_FNC)user_log_write, rcpt_st, rcpt_st, 0);
}



void
log_smtp_reply(CMN_WORK *cwp)
{
      thr_log_print(cwp, 1, DCC_XHDR_REJ_DATA_MSG);
      log_write(cwp, cwp->reply.rcode, 0);
      LOG_CMN_CAPTION(cwp, " ");
      log_write(cwp, cwp->reply.xcode, 0);
      LOG_CMN_CAPTION(cwp, " ");
      log_write(cwp, cwp->reply.buf, 0);
      LOG_CMN_EOL(cwp);
}



static void
user_log_smtp_reply(CMN_WORK *cwp, RCPT_ST *rcpt_st)
{
      user_log_print(rcpt_st, DCC_XHDR_REJ_DATA_MSG"%s %s %s\n",
                   cwp->reply.rcode, cwp->reply.xcode, cwp->reply.buf);
}



/* process the message for each user to decide what to do with it */
void
users_process(CMN_WORK *cwp)
{
      RCPT_ST *rcpt_st;
      u_char need_eol;

      /* log the DCC results and headers in the common log file */
      log_isspam((LOG_WRITE_FNC)log_write, cwp, cwp,
               "global", STRZ("global"),
               cwp->rcpts_sws, cwp->rcpt_fgs);

      /* log the checksums, DCC server counts and global whitelist values */
      dcc_print_cks((LOG_WRITE_FNC)log_write, cwp,
                  cwp->cmn_fgs & CMN_FG_LOCAL_SPAM, cwp->local_tgts,
                  &cwp->cks, cwp->wtgts,
                  (cwp->cmn_fgs & CMN_FG_LOG_ENV_TO) != 0);
      if (cwp->cmn_fgs & CMN_FG_LOG_ENV_TO) {
            for (rcpt_st = cwp->rcpt_st_first;
                 rcpt_st;
                 rcpt_st = rcpt_st->fwd) {
                  print_env_to((LOG_WRITE_FNC)log_write, cwp, rcpt_st);
            }
      }
      LOG_CMN_EOL(cwp);

      /* mark recipients who won't receive it */
      need_eol = 0;
      for (rcpt_st = cwp->rcpt_st_first;
           rcpt_st;
           rcpt_st = rcpt_st->fwd) {
            /* ignore rejected recipients */
            if (rcpt_st->temp_rej_msg[0] != '\0')
                  continue;

            /* decide whether it is spam for this target */
            if (!(rcpt_st->fgs & RCPT_FG_WHITE)) {
                  if (rcpt_st->fgs & RCPT_FG_BLACK) {
                        rcpt_st->fgs |= RCPT_FG_ISSPAM;
                  } else if ((cwp->ask_st & ASK_ST_SRVR_ISSPAM)
                           && !(rcpt_st->sws & FLTR_SW_DCC_OFF)) {
                        rcpt_st->fgs |= RCPT_FG_ISSPAM;
#ifdef USE_XFLTR
                  } else if ((cwp->ask_st & ASK_ST_XFLTR_ISSPAM)
                           && (rcpt_st->sws & FLTR_SW_XFLTR_ON)) {
                        rcpt_st->fgs |= RCPT_FG_ISSPAM;
#endif
                  } else if ((cwp->ask_st & ASK_ST_DNSBL_ISSPAM)
                           && (rcpt_st->sws & FLTR_SW_DNSBL_ON)) {
                        rcpt_st->fgs |= RCPT_FG_ISSPAM;
                  }
            }

            /* if one target is not using the DNS blacklists,
             * then do not use the STMP transaction status message */
            if (!(rcpt_st->sws & FLTR_SW_DNSBL_ON))
                  cwp->dnsbl_reply = 0;

            if (!(rcpt_st->fgs & RCPT_FG_ISSPAM)) {
                  ++cwp->deliver_tgts;

            } else {
                  ++cwp->reject_tgts;
                  /* Tell greylist to restore the embargo for targets
                   * that believe the message was spam and did not
                   * white- or blacklist it */
                  if (rcpt_st->grey_result == ASK_GREY_EMBARGO
                      && rcpt_st->embargo_num == 0) {
                        rcpt_st->grey_result = ASK_GREY_OFF;
                  } else if (rcpt_st->grey_result == ASK_GREY_EMBARGO
                           || rcpt_st->grey_result==ASK_GREY_EMBARGO_END
                           || rcpt_st->grey_result == ASK_GREY_PASS) {
                        rcpt_st->grey_result = ASK_GREY_SPAM;
                        if (!dcc_grey_spam(cwp->emsg,
                                       cwp->dcc_ctxt,
                                       &cwp->cks,
                                       rcpt_st->msg_sum,
                                       rcpt_st->triple_sum))
                              thr_error_msg(cwp, "%s", cwp->emsg);
                  }
            }

            print_grey((LOG_WRITE_FNC)log_write, cwp, rcpt_st, &need_eol);
      }
      if (need_eol)
            LOG_CMN_EOL(cwp);

      /* if it is spam for some targets and not for others,
       * but we cannot discard, then reject it for all targets */
      if (cwp->reject_tgts != 0 && cwp->deliver_tgts != 0
          && (cwp->cmn_fgs & CMN_FG_ALL_REJECT)) {
            thr_log_print(cwp, 0, "rejection forced for %d targets\n",
                        cwp->deliver_tgts);
            cwp->reject_tgts += cwp->deliver_tgts;
            cwp->deliver_tgts = 0;
      }

      /* do not embargo the message if no target wants it */
      if (cwp->deliver_tgts == 0)
            cwp->ask_st &= ~ASK_ST_GREY_EMBARGO;

      if (cwp->ask_st & ASK_ST_GREY_EMBARGO) {
            make_reply(&cwp->reply, &grey_reply, cwp);
            return;
      }

      /*  finished if we won't do anything about it */
      if (cwp->reject_tgts == 0
          || cwp->action == CMN_IGNORE)
            return;

      /* make an SMTP rejection message unless we have already have one */
      if (cwp->reply.log_result)
            return;

      if ((cwp->ask_st & ASK_ST_DNSBL_ISSPAM)
          && cwp->dnsbl_reply) {
            make_reply(&cwp->reply, cwp->dnsbl_reply, cwp);
            return;
      }

      make_reply(&cwp->reply, &reject_reply, cwp);
}



/* after having checked each user or recipient,
 *    dispose of the message for each */
static void
user_log_result(CMN_WORK *cwp, RCPT_ST *rcpt_st, const char *result)
{
      /* create the per-user log file */
      if ((rcpt_st->fgs & RCPT_FG_ISSPAM)
          || ((cwp->ask_st & ASK_ST_GREY_LOGIT)
            && !(rcpt_st->sws & FLTR_SW_GREY_LOG_OFF))
          || (rcpt_st->sws & FLTR_SW_LOG_ALL)
          || rcpt_st->temp_rej_msg[0] != '\0')
            user_log_msg(cwp, rcpt_st);

      if (rcpt_st->temp_rej_msg[0] != '\0') {
            thr_log_print(cwp, 0,
                        DCC_XHDR_RESULT_REJECT" %s: %s\n",
                        rcpt_st->env_to, rcpt_st->temp_rej_result);
            USER_LOG_CAPTION(rcpt_st, DCC_XHDR_REJ_RCPT_MSG);
            user_log_write(rcpt_st, rcpt_st->temp_rej_msg, 0);
            if (rcpt_st->temp_rej_msg[0] == '4')
                  USER_LOG_CAPTION(rcpt_st,
                               "\n"DCC_XHDR_RESULT
                               DCC_XHDR_RESULT_REJECT
                               " temporarily\n");
            else
                  USER_LOG_CAPTION(rcpt_st,
                               "\n"DCC_XHDR_RESULT
                               DCC_XHDR_RESULT_REJECT);
            return;
      }

      if (cwp->ask_st & ASK_ST_GREY_EMBARGO) {
            user_log_smtp_reply(cwp, rcpt_st);
            if (rcpt_st->embargo_num != 0) {
                  user_log_print(rcpt_st,
                               DCC_XHDR_RESULT"%s #%d\n",
                               cwp->reply.log_result,
                               rcpt_st->embargo_num);
            } else {
                  user_log_print(rcpt_st, DCC_XHDR_RESULT"%s\n",
                               cwp->reply.log_result);
            }
            return;
      }

      if (!(rcpt_st->fgs & RCPT_FG_ISSPAM)) {
            /* It is not spam for this target.
             *
             * If it was rejected late, e.g. by the SMTP server for dccifd
             * in proxy, mode, then log the rejection message */
            if (result) {
                  if (cwp->reply.buf[0] != '\0')
                        user_log_print(rcpt_st,
                                     DCC_XHDR_REJ_DATA_MSG"%s\n",
                                     cwp->reply.buf);
                  user_log_print(rcpt_st, DCC_XHDR_RESULT"%s\n",
                               result);
                  return;
            }

            /* If it was spam for some other target and we cannot
             * discard for that target, then log the rejection */
            if (cwp->reject_tgts != 0
                && (cwp->cmn_fgs & CMN_FG_ALL_REJECT)) {
                  user_log_smtp_reply(cwp, rcpt_st);
                  USER_LOG_CAPTION(rcpt_st,
                               DCC_XHDR_RESULT
                               DCC_XHDR_RESULT_REJECT
                               " forced by other recipients\n");
                  return;
            }

            if (cwp->ask_st & (ASK_ST_CLNT_ISSPAM
                           | ASK_ST_SRVR_ISSPAM
                           | ASK_ST_REP_ISSPAM)) {
                  if (cwp->rcpt_fgs & RCPT_FG_GREY_END)
                        USER_LOG_CAPTION(rcpt_st,
                                     DCC_XHDR_RESULT
                                     DCC_XHDR_RESULT_I_A
                                     " "DCC_XHDR_RESULT_A_GREY"\n");
                  else
                        USER_LOG_CAPTION(rcpt_st,
                                     DCC_XHDR_RESULT
                                     DCC_XHDR_RESULT_I_A"\n");
                  return;
            }
            if (cwp->rcpt_fgs & RCPT_FG_GREY_END) {
                  USER_LOG_CAPTION(rcpt_st,
                               DCC_XHDR_RESULT
                               DCC_XHDR_RESULT_ACCEPT
                               " "DCC_XHDR_RESULT_A_GREY"\n");
                  return;
            }
            if (rcpt_st->fgs & RCPT_FG_GREY_WHITE) {
                  USER_LOG_CAPTION(rcpt_st,
                               DCC_XHDR_RESULT
                               DCC_XHDR_RESULT_ACCEPT
                               ";  greylist whitelist\n");
                  return;
            }
            USER_LOG_CAPTION(rcpt_st,
                         DCC_XHDR_RESULT DCC_XHDR_RESULT_ACCEPT"\n");
            return;
      }

      /* It was spam for this target
       * If some other target wanted the message, then we must discard it
       * for this target.  Dccifd in SMTP proxy mode (and probably not in
       * ASCII protocol mode) cannot discard for an individual target,
       * and so must prevent the possibility */
      if (cwp->deliver_tgts != 0) {
            --cwp->reject_tgts;
            ++totals.tgts_discarded;
            user_reject_discard(cwp, rcpt_st);
            if (cwp->action == CMN_DISCARD) {
                  USER_LOG_CAPTION(rcpt_st, DCC_XHDR_RESULT
                               DCC_XHDR_RESULT_DISCARD"\n");
            } else {
                  thr_log_print(cwp, 0,
                              DCC_XHDR_RESULT
                              DCC_XHDR_RESULT_DISCARD
                              " forced for %s\n",
                              rcpt_st->env_to);
                  USER_LOG_CAPTION(rcpt_st,
                               DCC_XHDR_RESULT
                               DCC_XHDR_RESULT_DISCARD
                               " forced by other"
                               " target's whitelist\n");
            }
            return;
      }

      /* spam for all targets including this one */

      if (cwp->action == CMN_DISCARD) {
            USER_LOG_CAPTION(rcpt_st, DCC_XHDR_RESULT
                         DCC_XHDR_RESULT_DISCARD"\n");
            return;
      }

      if (cwp->action == CMN_IGNORE) {
            USER_LOG_CAPTION(rcpt_st, DCC_XHDR_RESULT
                         DCC_XHDR_RESULT_I_A"\n");
            return;
      }

      user_log_smtp_reply(cwp, rcpt_st);
      user_log_print(rcpt_st, DCC_XHDR_RESULT"%s\n",
                   cwp->reply.log_result);
}



/* log the consensus in each target's log file */
void
users_log_result(CMN_WORK *cwp,
             const char *result)    /* 0 or reject by dccifd's server */
{
      RCPT_ST *rcpt_st;

      /* create individual log files and trim target list */
      for (rcpt_st = cwp->rcpt_st_first; rcpt_st; rcpt_st = rcpt_st->fwd) {
            user_log_result(cwp, rcpt_st, result);

            if (rcpt_st->user_log_fd >= 0) {
                  dcc_log_close(0, rcpt_st->user_log_nm,
                              rcpt_st->user_log_fd, &cwp->ldate);
                  rcpt_st->user_log_fd = -1;
            }
      }

      log_fd2_close(cwp, -2);
}

Generated by  Doxygen 1.6.0   Back to index