/* Distributed Checksum Clearinghouse
 *
 * threaded version of client library
 *
 * --C-LICENSE--
 * $Revision: 1.182 $
 */


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

CMN_ACTION action = CMN_REJECT;

CHGHDR chghdr = SETHDR;

const char *userdirs;
static DCC_PATH userdirs_path;
static int userdirs_len;

u_char dcc_query_only;
u_char try_extra_hard;			/* 0 or DCC_CLNT_FG_NO_FAIL */
u_char to_white_only;

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

const char *max_max_work_src = "FD_SETSIZE limit";
int max_work;
int init_work;
int total_work;

static int total_rcpt_sts;
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;
    WF	    wf;
} CWF;
static CWF *cur_cwf, cwfs[DCC_NUM_CWFS];

/* protected by user_log_mutex */
static int user_log_fd = -1;
static int log_fd2 = -1;


/* the work lock must be held or not yet exist */
static void
add_rcpt_sts(int i)
{
	RCPT_ST *rcpt_st;

	total_rcpt_sts += i;

	rcpt_st = dcc_malloc(sizeof(*rcpt_st)*i);
	memset(rcpt_st, 0, sizeof(*rcpt_st)*i);

	while (i-- != 0) {
		rcpt_st->fwd = rcpt_st_free;
		rcpt_st_free = rcpt_st;
		++rcpt_st;
	}
}



void
cmn_init(void)
{
	init_work = 50;
	if (init_work > max_max_work)
		init_work = max_max_work;
	add_rcpt_sts(init_work);

	finish_replies();

	/* 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;
		wf_init(&cur_cwf->wf, WF_PER_USER);
	}
	cur_cwf = cwfs;
	LAST(cwfs)->older = cur_cwf;
	cur_cwf->newer = LAST(cwfs);

	totals_init();
}



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



u_char
cmn_open_tmp(CMN_WORK *cwp)
{
	cwp->tmp_fd = dcc_mkstemp(&cwp->emsg, &cwp->tmp_nm,
				  cwp->id, sizeof(cwp->id),
				  0, 0, DCC_TMP_LOG_PREFIX, 1, 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.c, ERROR_STR());
		cwp->tmp_fd = -1;
	}
	cwp->tmp_nm.c[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.c, len, ERROR_STR());
	else
		thr_error_msg(cwp, "write(%s,%d)=%d",
			      cwp->tmp_nm.c, len, i);
	cmn_close_tmp(cwp);
	return 0;
}



/* If the immediate SMTP client is a listed MX server,
 *	then we must ignore its IP address and keep looking for the
 *	real SMTP client. */
void
check_mx_listing(CMN_WORK *cwp)
{
	DCC_TGTS tgts;
	u_char result;

	if (cwp->sender_str[0] == '\0') {
		cwp->cmn_fgs |= CMN_FG_PARSE_RCVD;
		return;
	}

	lock_wf();
	result = white_mx(&cwp->emsg, &tgts, &cwp->cks);
	unlock_wf();
	if (!result) {
		thr_error_msg(cwp, "%s", cwp->emsg.c);
		return;
	}

	if (tgts == DCC_TGTS_OK) {
		/* do not tell the server about the IP address */
		no_ip_rpt2srvr(&cwp->cks);
		cwp->cmn_fgs &= ~CMN_FG_PARSE_RCVD;
		return;
	}

	if (tgts == DCC_TGTS_SUBMIT_CLIENT) {
		/* Common SMTP submission clients are too dumb to do the
		 * right thing with 4yz rejections of individual Rcpt_To
		 * commands.  So reject the message for all or no recipients. */
		cwp->cmn_fgs |= CMN_FG_VIA_SUBMIT;
		thr_log_print(cwp, 1,
			      "%s is a listed 'submit' client\n",
			      dcc_trim_ffff(cwp->sender_str));
		/* do not tell the server about the IP address */
		no_ip_rpt2srvr(&cwp->cks);
		cwp->cmn_fgs &= ~CMN_FG_PARSE_RCVD;
		return;
	}

	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->ask_st |= ASK_ST_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 {
		/* not listed */
		cwp->cmn_fgs &= ~CMN_FG_PARSE_RCVD;
		return;
	}

	/* we should not greylist or reject through our MX servers */
	cwp->cmn_fgs |= (CMN_FG_VIA_MX | CMN_FG_PARSE_RCVD);
	if (cwp->action == CMN_REJECT)
		cwp->action = CMN_DISCARD;

	no_ip_rpt2srvr(&cwp->cks);
}



/* 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)		/* 1=first message for the connection */
{
	log_stop(cwp);

	cmn_close_tmp(cwp);

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

	memset(&cwp->CMN_WORK_ZERO, 0,
	       sizeof(*cwp) - ((char*)&cwp->CMN_WORK_ZERO - (char*)cwp));
	cwp->cmn_fgs = CMN_FG_LOG_EARLY;
	cwp->mail_host[0] = '\0';
	cwp->env_from[0] = '\0';
	cwp->early_log.len = 0;
	cwp->emsg.c[0] = '\0';
	cwp->id[0] = '\0';
	cwp->header.used = 0;
	if (dcc_query_only)
		cwp->ask_st |= (ASK_ST_QUERY | ASK_ST_QUERY_GREY);
	cwp->action = action;

	if (new_conn) {
		cwp->wp = wp;
		cwp->helo[0] = '\0';
		cwp->clnt_name[0] = '\0';
		cwp->clnt_str[0] = '\0';
	} else {
		/* 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);
	}
}



/* 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;
		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) {
		if (dcc_clnt_debug > 1)
			thr_trace_msg(cwp,
				      "add %d recipient blocks to %d",
				      init_work, total_rcpt_sts);
		add_rcpt_sts(init_work);
		rcpt_st = rcpt_st_free;
	}
	rcpt_st_free = rcpt_st->fwd;
	if (unlocked)
		unlock_work();

	rcpt_st->fwd = 0;
	rcpt_st->log_pos_white = -1;
	rcpt_st->log_pos_to = -1;
	memset(&rcpt_st->wtgts, 0, sizeof(rcpt_st->wtgts));
	rcpt_st->env_to_tgts = 0;
	rcpt_st->user_tgts = 0;
	rcpt_st->grey_result = ASK_GREY_OFF;
	rcpt_st->embargo_num = 0;
	rcpt_st->fgs = 0;
	rcpt_st->global_env_to_fgs = 0;
	rcpt_st->env_to_fgs = 0;
	rcpt_st->sws = 0;
	rcpt_st->user[0] = '\0';
	rcpt_st->rej_msg[0] = '\0';
	rcpt_st->dir.c[0] = '\0';
	rcpt_st->user_log_nm.c[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)
{
	/* add '/' to end of the path without converting it to "/" */
	if (*arg == '\0') {
		userdirs_path.c[0] = '\0';
		userdirs_len = 0;
	} else {
		STRLCPY(userdirs_path.c, arg,
			sizeof(userdirs_path));
		userdirs_len = strlen(userdirs_path.c)-1;
		while (userdirs_len > 1 && userdirs_path.c[userdirs_len] == '/')
			--userdirs_len;
		userdirs_path.c[++userdirs_len] = '/';
		userdirs_path.c[++userdirs_len] = '\0';

	}
	userdirs = userdirs_path.c;
}



/* 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.c[0] = '\0';
		return 1;
	}

	memcpy(rcpt_st->dir.c, userdirs, userdirs_len);
	i = userdirs_len;
	if (i+str1_len < ISZ(rcpt_st->dir))
		memcpy(&rcpt_st->dir.c[i], str1, str1_len);
	i += str1_len;
	if (str2) {
		if (i+str2_len+1 < ISZ(rcpt_st->dir)) {
			rcpt_st->dir.c[i++] = '/';
			memcpy(&rcpt_st->dir.c[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.c);
		rcpt_st->dir.c[0] = '\0';
		return 0;
	}
	rcpt_st->dir.c[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.c[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.c);
				rcpt_st->dir.c[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)
{
	char date_buf[40];

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

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

	cwp->log_size = 0;
	cwp->log_fd = dcc_main_log_open(&cwp->emsg, &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.c);
		whined = now;
		cwp->emsg.c[0] = '\0';
		return;
	}

	gettimeofday(&cwp->ldate, 0);

	thr_log_print(cwp, 0, DCC_LOG_DATE_PAT"\n",
		      dcc_time2str(date_buf, sizeof(date_buf), DCC_LOG_DATE_FMT,
				   cwp->ldate.tv_sec));
}



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

#ifdef DCC_DEBUG_LOCKS
	if (user_log_owner != pthread_self())
		dcc_logbad(EX_SOFTWARE, "don't have user_log lock");
#endif

	if (log_fd2 >= 0)
		return 1;

	/* give up if things are already broken */
	if (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",
			      dcc_fnm2abs_msg(&abs_nm, cwp->log_nm.c),
			      ERROR_STR());

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

	return 1;
}



static void
log_fd2_close(int flag)
{
	if (user_log_owner == pthread_self()) {
		if (log_fd2 >= 0)
			close(log_fd2);
		log_fd2 = flag;
	}
}



void
log_stop(CMN_WORK *cwp)
{
	thr_log_late(cwp);
	log_fd2_close(-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.c, cwp->log_fd, &cwp->ldate);
	if (!(cwp->ask_st & ASK_ST_LOGIT)) {
		/* Delete the log file if it is not interesting */
		unlink(cwp->log_nm.c);
	} else {
		/* give it a permanent name if it is interesting */
		dcc_log_keep(0, &cwp->log_nm);
	}
	cwp->log_nm.c[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);
	cwp->log_size += buflen;

	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.c, ERROR_STR());
	else
		dcc_error_msg("write(%s,%d)=%d", cwp->log_nm.c, buflen, result);
	dcc_log_close(0, cwp->log_nm.c, cwp->log_fd, &cwp->ldate);
	cwp->log_fd = -1;
}



void
log_body_write(CMN_WORK *cwp, const char *buf, u_int buflen)
{
	int trimlen;
	const char *p, *lim;

	if (cwp->log_fd < 0)
		return;

	/* just write if there is room */
	trimlen = MAX_LOG_KBYTE*1024 - 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_KBYTE*1024+1;
}



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.c, ERROR_STR());
		dcc_log_close(0, cwp->log_nm.c, cwp->log_fd, &cwp->ldate);
		cwp->log_fd = -1;
		return 0;
	}
	return result;
}



static u_char
log_lseek_set(CMN_WORK *cwp, off_t pos)
{
#ifdef DCC_DEBUG_LOCKS
	if (user_log_owner != pthread_self())
		dcc_logbad(EX_SOFTWARE, "don't have user_log lock");
#endif

	if (log_fd2 < 0)
		return 0;

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

	return 1;
}



/* put something into a log file
 * does not append '\n' */
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[LOGBUF_SIZE*2];
	int i;

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

	i = vsnprintf(logbuf, sizeof(logbuf), p, args);
	if (i >= ISZ(logbuf)) {
		i = strlen(p);
		if (i != 0 && p[i-1] == '\n')
			memcpy(&logbuf[ISZ(logbuf)-LITZ("...\n")], "...\n",
			       LITZ("...\n"));
		else
			memcpy(&logbuf[ISZ(logbuf)-LITZ("...")], "...",
			       LITZ("..."));
		i = ISZ(logbuf);
	}
	log_write(cwp, logbuf, i);
	return i;
}



/* does not append '\n' */
int DCC_PF(3,4)				/* bytes written */
thr_log_print(void *cwp, u_char error, const char *pat, ...)
{
	va_list args;
	int i;

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



int					/* bytes written */
thr_error_msg(void *cwp0, const char *pat, ...)
{
	CMN_WORK *cwp = cwp0;
	va_list args;
	int i;

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

	va_start(args, pat);
	i = vthr_log_print(cwp, 1, pat, args);
	va_end(args);
	thr_log_print(cwp, 1, "\n");

	cwp->ask_st |= ASK_ST_LOGIT;

	return i+1;
}



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 dccm_placekeeper)   /* leave room to replace IP */
{
	RCPT_ST *rcpt_st;
	off_t cur_pos;
	int i;

	cwp->cmn_fgs |= CMN_FG_ENV_LOGGED;

	/* install the sender in blank area in the log file that we skipped */
	cwp->log_ip_pos = log_lseek_get(cwp) + LITZ(DCC_XHDR_TYPE_IP": ");
	if (cwp->sender_str[0] != '\0') {
		/* Dccm will not have computed the checksum
		 * if there were no envelope Mail_From commands
		 * On a second message in a session, dccm will not have
		 * looked at cwp->clnt_addr */
		if (cwp->cks.sums[DCC_CK_IP].type == DCC_CK_INVALID)
			get_ipv6_ck(&cwp->cks, &cwp->clnt_addr);

		/* pad host name & IP address with blanks for replacement
		 * by dccm */
		if (!dccm_placekeeper) {
			i = 0;
		} else {
			i = 80 - (strlen(cwp->sender_name)
				  + LITZ(DCC_LOG_IP1 DCC_LOG_IP2 "\n"));
		}
		i = thr_log_print(cwp, 0, DCC_LOG_IP1"%s"DCC_LOG_IP2"%-*s\n",
				  cwp->sender_name, i, cwp->sender_str);
		i -= LITZ(DCC_LOG_IP1 DCC_LOG_IP2 "\n");
		cwp->log_ip_len = i > 0 ? i : 0;
	} else {
		cwp->log_ip_len = 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, DCC_LOG_HELO"%s\n", cwp->helo);
	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);
		get_cks(&cwp->cks, DCC_CK_ENV_FROM, cwp->env_from, 1);

		if (cwp->mail_host[0] == '\0')
			parse_mail_host(cwp->env_from, cwp->mail_host,
					sizeof(cwp->mail_host));

		LOG_CMN_CAPTION(cwp, DCC_LOG_MAIL_HOST);
		log_write(cwp, cwp->mail_host, 0);
		if (cwp->mail_host[0] != '\0')
			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.c, 0);
		if (rcpt_st->fgs & RCPT_FG_BAD_USERNAME) {
			LOG_CMN_CAPTION(cwp, "  "DCC_XHDR_MTA_REJECTION"\n");
		} else {
			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);
}



/* 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_BAD_SRVR_OK
					      | DCC_CLNT_FG_NO_MEASURE_RTTS
					      | DCC_CLNT_FG_NO_FAIL);
		if (!cwp->dcc_ctxt) {
			/* failed to create context */
			thr_error_msg(cwp, "%s", cwp->emsg.c);
			cwp->dcc_ctxt_sn = 0;
			return 0;
		}
		cwp->xhdr_fname_len = get_xhdr_fname(cwp->xhdr_fname,
						     sizeof(cwp->xhdr_fname),
						     dcc_clnt_info);
	}
	return 1;
}



/* find and lock a per-user 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.c[0] == '\0') {
		rcpt_st->fgs |= RCPT_FG_NULL_WHITECLNT;
		return 0;
	}

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

	lock_wf();
	cwf = cur_cwf;
	for (;;) {
		if (!strcmp(white_nm_ptr, cwf->wf.ascii_nm.c))
			break;		/* found old WF for target file */

		if (cwf->older == cur_cwf) {
			/* We do not know this file.
			 * Recycle the oldest 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.c);
				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 (wf_rdy(&rcpt_st->cwp->emsg, &cwf->wf, &cmn_tmp_wf)) {
	case WHITE_CONTINUE:
		thr_error_msg(rcpt_st->cwp, "%s", rcpt_st->cwp->emsg.c);
		/* fall through */
	case WHITE_OK:
		/* notice if the file contains no checksums or IP address blocks
		 * or flag bits that make the file differ from an empty
		 * or non-existent file */
		if (cwf->wf.wtbl->hdr.entries == 0
		    && cwf->wf.wtbl->hdr.ranges.len == 0
		    && WHITE_SET(cwf->wf.wtbl_fgs, cwf->wf.wtbl_sws)) {
			rcpt_st->fgs |= RCPT_FG_NULL_WHITECLNT;
		} else {
			rcpt_st->fgs &= ~RCPT_FG_NULL_WHITECLNT;
			rcpt_st->wf_sum = cwf->wf.wtbl->hdr.ck_sum;
		}
		return cwf;
	case WHITE_NOFILE:
		break;
	case WHITE_SILENT:
		if (dcc_clnt_debug)
			thr_error_msg(rcpt_st->cwp, "%s", rcpt_st->cwp->emsg.c);
		break;
	case WHITE_COMPLAIN:
		thr_error_msg(rcpt_st->cwp, "%s", rcpt_st->cwp->emsg.c);
		break;
	}

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



/* digest the results of one recipient's whitelist */
static RCPT_FGS
white_results(RCPT_ST *rcpt_st,
	      CMN_WORK *cwp,
	      RCPT_FGS *fgsp,
	      WHITE_RESULT result,
	      WHITE_LISTING listing)
{
	u_char have_wlist_tgts;
	RCPT_FGS new_cwp_rcpt_fgs;

	/* override if the result of the whitelist lookup was bad */
	switch (result) {
	case WHITE_OK:
		break;
	case WHITE_SILENT:
	case WHITE_NOFILE:
		listing = WHITE_UNLISTED;
		break;
	case WHITE_COMPLAIN:
	case WHITE_CONTINUE:
		thr_error_msg(cwp, "%s", cwp->emsg.c);
		return 0;
	}

	have_wlist_tgts = 0;
	new_cwp_rcpt_fgs = 0;

	switch (listing) {
	case 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.
		 * It is the original, deprecated mechanism for turn DCC checks
		 * on and off for individual targets
		 * We get this value only from wf_sum() */
		if (rcpt_st)
			rcpt_st->sws &= ~FLTR_SW_DCC_OFF;
		break;

	case WHITE_UNLISTED:
		break;

	case WHITE_LISTED:
		*fgsp &= ~RCPT_FG_BLACK;
		*fgsp |= (RCPT_FG_WHITE | RCPT_FG_WLIST_NOTSPAM);
		new_cwp_rcpt_fgs |= RCPT_FG_WLIST_NOTSPAM;
		have_wlist_tgts = 1;
		break;

	case WHITE_BLACK:
		if (!(*fgsp & RCPT_FG_WHITE))
			*fgsp |= RCPT_FG_BLACK;
		*fgsp |= RCPT_FG_WLIST_ISSPAM;
		new_cwp_rcpt_fgs |= RCPT_FG_WLIST_ISSPAM;
		have_wlist_tgts = 1;
		break;
	}

	/* a spam trap marks everything a spam and rejects or accepts it */
	if (!(*fgsp & RCPT_FG_WHITE)
	    && rcpt_st && (rcpt_st->sws & FLTR_SW_TRAP)) {
		*fgsp |= RCPT_FG_BLACK;
		*fgsp |= RCPT_FG_WLIST_ISSPAM;
		new_cwp_rcpt_fgs |= RCPT_FG_WLIST_ISSPAM;
		listing = WHITE_BLACK;

	} else if (have_wlist_tgts) {
		if (rcpt_st)
			rcpt_st->fgs |= RCPT_FG_LOG_WTGTS;
		new_cwp_rcpt_fgs |= RCPT_FG_LOG_WTGTS;
	}

	return new_cwp_rcpt_fgs;
}



static void
rcpt_fgs2ask_st(CMN_WORK *cwp, FLTR_SWS sws, RCPT_FGS *fgs)
{
	/* notice DNS whitelist hits */
	if (0 != (FLTR_SW_DNSBL_BITS(sws) & ASK_ST_DNSBL_W_BITS(cwp->ask_st))
	    && !(*fgs & RCPT_FG_BLACK))
		*fgs |= RCPT_FG_WHITE;

	/* 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 whitelisted targets */
	if (*fgs & RCPT_FG_WHITE) {
		++cwp->white_tgts;
		return;
	}

	/* 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);
		return;
	}

	/* If we had a DNS blacklist hit
	 * and if this recipient believes the DNS blacklist,
	 * then it is spam to report to DCC server.
	 * We need to know if it is spam for at least one target before
	 * deciding what to do for each target. */
	if (0 != (FLTR_SW_DNSBL_BITS(sws) & ASK_ST_DNSBL_B_BITS(cwp->ask_st)))
		cwp->ask_st |= (ASK_ST_CLNT_ISSPAM | ASK_ST_LOGIT);
}



static FLTR_SWS
get_sws(const CMN_WORK *cwp, WF *wf)
{
	FLTR_SWS sws;

	sws = cwp->init_sws;

	if (wf)
		sws = wf2sws(sws, wf);

	/* greylisting our MX secondaries makes no sense
	 * and browsers cannot handle temporary rejections */
	if (cwp->cmn_fgs & (CMN_FG_VIA_MX | CMN_FG_VIA_SUBMIT))
		sws |= FLTR_SW_GREY_OFF;

	/* Discard traps should discard but can leak.
	 * Refusing to discard for discard traps makes no sense.
	 * Reject traps should act like no-such-user errors.
	 * A dccifd SMTP proxy cannot discard for 1 recipient.
	 * If we will discard spam and can discard for individual recipients,
	 * then we cannot have conflicts among recipients about discarding. */
	if (sws & FLTR_SW_TRAP_DIS) {
		sws &= ~FLTR_SW_DISCARD_NO;
	} else if (!can_discard_1
		   && (cwp->action != CMN_IGNORE || (sws & FLTR_SW_TRAP_REJ))) {
		sws |= FLTR_SW_DISCARD_NO;
	} else if (cwp->action == CMN_DISCARD) {
		sws &= ~FLTR_SW_DISCARD_NO;
	}

	return sws;
}



static void
init_sws(CMN_WORK *cwp, u_char locked)
{
	/* set defaults */
	cwp->init_sws = 0;
	if (cwp->action == CMN_REJECT)
		cwp->init_sws = FLTR_SW_DISCARD_NO;
	if (to_white_only)
		cwp->init_sws |= FLTR_SW_DCC_OFF;

	if (!locked)
		lock_wf();
	cwp->init_sws = get_sws(cwp, &cmn_wf);
	if (!locked)
		unlock_wf();

	merge_tholds(&cwp->tholds_base, &tholds_rej, cmn_wf.wtbl);

	cwp->or_sws = cwp->and_sws = cwp->init_sws & ~FLTR_SW_SET;
	cwp->cks.tholds_rej = cwp->tholds_base;
}



/* set per-user switches and compute env_to whitelisting
 *	If we return a pointer, then we grabbed and have kept the lock */
static RCPT_FGS
rcpt_sws_env_to(CMN_WORK *cwp, RCPT_ST *rcpt_st, CWF **cwfp)
{
	CWF *cwf;
	WHITE_RESULT result;
	WHITE_LISTING listing;
	RCPT_FGS new_cwp_rcpt_fgs;

	new_cwp_rcpt_fgs = cwp->rcpt_fgs;

	/* We are finished after finding the recipient's whitelist if we
	 * have already checked its settings.
	 *
	 * In this case, we have been here before for this recipient
	 * and fetched the global as well as this recipient's switch settings
	 * and action.
	 *
	 * If we found that the file was empty of checksums or non-existent
	 * before, then assume it is still is and so repeat the previous 0
	 * answer without wasting time looking.  This is an extremely
	 * common case that deserves optimizing. */
	if (rcpt_st->fgs & RCPT_FG_NULL_WHITECLNT) {
		if (cwfp)
			*cwfp = 0;
		return new_cwp_rcpt_fgs;
	}

	/* after mapping this per-recipient whiteclnt file,
	 * do not repeat the work of examining it if we have already done it */
	cwf = find_cwf(rcpt_st);
	if (rcpt_st->sws & FLTR_SW_SET) {
		if (cwfp)
			*cwfp = cwf;
		else if (cwf)
			unlock_wf();
		return new_cwp_rcpt_fgs;
	}

	/* We must get the global switch settings the first time for the
	 * first recipient.
	 * If we have a per-user whitelist, then we have locked cmn_wf
	 * to protect the per-user whitelist data structures */
	if (!(cwp->init_sws & FLTR_SW_SET))
		init_sws(cwp, cwf != 0);

	/* get flags and filter settings for recipient,
	 * including setting FLTR_SW_SET so that we won't do this again */
	rcpt_st->sws = get_sws(cwp, cwf ? &cwf->wf : 0);
	merge_tholds(&rcpt_st->tholds, &cwp->tholds_base,
		     cwf ? cwf->wf.wtbl : 0);

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

	str2ck(&rcpt_st->env_to_sum, 0, 0, rcpt_st->env_to);

	/* check the env_to address in the global whitelist */
	result = wf_sum(&cwp->emsg, &cmn_wf,
			DCC_CK_ENV_TO, &rcpt_st->env_to_sum,
			&rcpt_st->env_to_tgts, &listing);
	new_cwp_rcpt_fgs = white_results(rcpt_st, cwp,
					 &rcpt_st->global_env_to_fgs,
					 result, listing);

	/* check the mailbox name (after aliases etc.) in the global whitelist
	 * if we did not just check it as the envelope Rcpt_To value */
	if (rcpt_st->user[0] != '\0'
	    && strcmp(rcpt_st->env_to, rcpt_st->user)) {
		str2ck(&rcpt_st->user_sum, 0, 0, rcpt_st->user);
		result = wf_sum(&cwp->emsg, &cmn_wf,
				DCC_CK_ENV_TO, &rcpt_st->user_sum,
				&rcpt_st->user_tgts, &listing);
		new_cwp_rcpt_fgs |= white_results(rcpt_st, cwp,
						  &rcpt_st->global_env_to_fgs,
						  result, listing);
	}

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

	if (cwfp)
		*cwfp = cwf;
	else if (cwf)
		unlock_wf();
	return new_cwp_rcpt_fgs;
}



static inline void
merge_rcpt(CMN_WORK *cwp, RCPT_FGS new_cwp_rcpt_fgs,
	   FLTR_SWS new_and_sws, FLTR_SWS new_or_sws,
	   const CKSUM_THOLDS *new_tholds_rej)
{
	cwp->rcpt_fgs = new_cwp_rcpt_fgs;
	cwp->and_sws = new_and_sws;
	cwp->or_sws = new_or_sws;
	cwp->cks.tholds_rej = *new_tholds_rej;
}



/* either tell the caller of cmn_compat_whitelist() to reject a recipient
 * or complain that it cannot be done */
static u_char
rej_rcpt(CMN_WORK *cwp, RCPT_ST *rcpt_st2, RCPT_FGS new_cwp_rcpt_fgs,
	 FLTR_SWS new_and_sws, FLTR_SWS new_or_sws,
	 const CKSUM_THOLDS *new_tholds_rej)
{
	/* if the new recipient cannot be rejected
	 * then whine and integrate its preferences */
	if (!can_reject_1
	    || (cwp->cmn_fgs & CMN_FG_VIA_SUBMIT)) {
		if (!(cwp->rcpt_fgs & RCPT_FG_INCOMPAT_REJ)) {
			cwp->rcpt_fgs |= RCPT_FG_INCOMPAT_REJ;
			thr_log_print(cwp, 1, "incompatible whitelists\n");
		}
		merge_rcpt(cwp, new_cwp_rcpt_fgs, new_and_sws, new_or_sws,
			   new_tholds_rej);
		return 1;
	}

	cwp->ask_st |= ASK_ST_LOGIT;
	rcpt_st2->fgs |= RCPT_FG_INCOMPAT_REJ;
	return 0;
}



/* See if a recipient's whitelist decision is certain to be the same
 *	as all preceding recipients.  At the end of the DATA command,
 *	we can only reject the message for all recipients or in some cases
 *	deliver to some recipients and discard for the rest.
 *	When using CMN_DISCARD and individual recipients can be trimmed,
 *	only conflicting thresholds are incompatible.  Individual whiteclnt
 *	files affect only individual recipients.
 *	When using CMN_REJECT, the entire message must be accepted or
 *	rejected for all recipients, and so individual whiteclnt files conflict
 *	if they differ.
 *	CMN_IGNORE must be treated like CMN_REJECT, because the X-DCC header
 *	is often used later to discard mail.  All recipients see the same
 *	X-DCC header, just all all recipients share the single SMTP OK
 *	or rejection after the DATA command. */
int					/* 1=ok -1=whitelist -2=thold conflict */
cmn_compat_whitelist(CMN_WORK *cwp,
		     RCPT_ST *rcpt_st2)
{
	const RCPT_ST *rcpt_st1;
	RCPT_FGS new_cwp_rcpt_fgs;
	FLTR_SWS new_and_sws, new_or_sws;
	CKSUM_THOLDS new_tholds;
	DCC_CK_TYPES type;
	u_char found_trap;

	new_cwp_rcpt_fgs = rcpt_sws_env_to(cwp, rcpt_st2, 0);

	/* The first recipient's preferences set the global defaults.
	 * We are finished for now if this is the first recipient. */
	if (!(cwp->or_sws & FLTR_SW_SET)) {
		merge_rcpt(cwp, new_cwp_rcpt_fgs, rcpt_st2->sws, rcpt_st2->sws,
			   &rcpt_st2->tholds);
		return 1;
	}

	for (type = 0; type < DCC_DIM_CKS; ++type) {
		new_tholds.t[type] = min(rcpt_st2->tholds.t[type],
					 cwp->cks.tholds_rej.t[type]);
	}
	new_and_sws = cwp->and_sws & rcpt_st2->sws;
	new_or_sws = cwp->or_sws | rcpt_st2->sws;

	/* find the first recipient that was not rejected
	 * and is not a discarding spam trap */
	found_trap = 0;
	for (rcpt_st1 = cwp->rcpt_st_first; ; rcpt_st1 = rcpt_st1->fwd) {
		if (!rcpt_st1)
			dcc_logbad(EX_SOFTWARE, "broken recipient chain");
		if (rcpt_st1 == rcpt_st2) {
			/* do not worry if all other recipients are
			 * discarding spam traps */
			if (found_trap) {
				merge_rcpt(cwp, new_cwp_rcpt_fgs,
					   new_and_sws, new_or_sws,
					   &new_tholds);
				return 1;
			}
			dcc_logbad(EX_SOFTWARE, "failed to find 1st recipient");
		}

		if ((rcpt_st1->fgs & (RCPT_FG_REJ_FILTER
				      | RCPT_FG_BAD_USERNAME)))
			continue;
		if (!(rcpt_st1->sws & FLTR_SW_TRAP_DIS))
			break;
		found_trap = 1;
	}

	/* See if the mail might be accepted for this recipient
	 * but rejected for a preceding recipient that does not want
	 * forced discarding or if this recipient does not want forced
	 * discarding and has weaker filtering than preceding recipients.
	 * If so, reject this recipient.
	 *
	 * If this recipient's thresholds could affect the result for it
	 * and if they differ, then they could conflict. */
	if (0 == (new_cwp_rcpt_fgs & (RCPT_FG_WLIST_ISSPAM
				      | RCPT_FG_WLIST_NOTSPAM))
	    && memcmp(&cwp->cks.tholds_rej, &new_tholds,
		      sizeof(cwp->cks.tholds_rej))) {
		if (rej_rcpt(cwp, rcpt_st2, new_cwp_rcpt_fgs,
			     new_and_sws, new_or_sws, &new_tholds))
			return 1;
		return -2;
	}

	/* Everything other than relevant differing thesholds is compatible
	 * if discarding is ok for all recipients.
	 */
	if (!(new_or_sws & FLTR_SW_DISCARD_NO)) {
		merge_rcpt(cwp, new_cwp_rcpt_fgs, new_and_sws, new_or_sws,
			   &new_tholds);
		return 1;
	}

	/* Two whitelists are incompatible if they give differing
	 * reject/accept answers and the mailbox that does not want the
	 * message also does not want it discarded.
	 *
	 * Check the other recipients individually */
	do {
		/* ignore already rejected recipients */
		if (rcpt_st1->fgs & (RCPT_FG_REJ_FILTER | RCPT_FG_BAD_USERNAME))
			continue;

		/* Differing whitelists make it possible that the message
		 * could need to be rejected for one recipient but accepted
		 * for another */
		if ((!(rcpt_st1->fgs & FLTR_SW_DISCARD_NO)
		     || !(rcpt_st2->fgs & FLTR_SW_DISCARD_NO))
		    && (0 != FLTR_SWS_CONFLICT(rcpt_st1->sws ^ rcpt_st2->sws)
			|| (0 != ((rcpt_st1->fgs ^rcpt_st2->fgs)
				  & RCPT_FG_NULL_WHITECLNT))
			|| (!(rcpt_st1->fgs & RCPT_FG_NULL_WHITECLNT)
			    && memcmp(&rcpt_st1->wf_sum, &rcpt_st2->wf_sum,
				      sizeof(rcpt_st1->wf_sum))))) {
			if (rej_rcpt(cwp, rcpt_st2, new_cwp_rcpt_fgs,
				     new_and_sws, new_or_sws, &new_tholds))
				return 1;
			return -1;
		}
	} while ((rcpt_st1 = rcpt_st1->fwd) != rcpt_st2);

	return 1;
}



/* check the whitelists for a single user or recipient */
static void
ask_white_rcpt(CMN_WORK *cwp,
	       RCPT_ST *rcpt_st,
	       RCPT_FGS global_fgs)
{
	WHITE_RESULT result;
	WHITE_LISTING listing;
	DCC_PATH fnm;
	CWF *cwf;

	rcpt_st->log_pos_white = log_lseek_get(cwp);

	/* Quit after capturing the log position if the recipient has
	 * been rejected.
	 * We cannot do more because dccm does not have the mailer and so
	 * cannot find the likely userdirs/local/user/whiteclnt file */
	if (rcpt_st->fgs & (RCPT_FG_REJ_FILTER | RCPT_FG_BAD_USERNAME))
		return;

	/* map the per-user whiteclnt file to check the rest of the checksums */
	rcpt_sws_env_to(cwp, rcpt_st, &cwf);

	/* 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 */
		result = wf_cks(&cwp->emsg, &cwf->wf, &cwp->cks,
				&rcpt_st->wtgts, &listing);
		cwp->rcpt_fgs |= white_results(rcpt_st, cwp,
					       &rcpt_st->env_to_fgs,
					       result, listing);

		/* 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_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 {
			rcpt_st->fgs &= ~(RCPT_FG_WHITE | RCPT_FG_BLACK);
			rcpt_st->fgs |= rcpt_st->env_to_fgs;
		}
	}

	/* announce result from the whitelist */
	if (rcpt_st->fgs & (RCPT_FG_WHITE | RCPT_FG_BLACK))
		thr_log_print(cwp, 0, "%s%s\n",
			      rcpt_st->user[0] ? rcpt_st->user
			      : rcpt_st->env_to[0] ? rcpt_st->env_to
			      : cwf ? dcc_fnm2abs_msg(&fnm, cwf->wf.ascii_nm.c)
			      : "",

			      (rcpt_st->fgs & RCPT_FG_WHITE)
			      ? DCC_XHDR_ISOK
			      : (rcpt_st->sws & FLTR_SW_TRAP_DIS)
			      ? "-->"DCC_XHDR_TRAP_DIS
			      : (rcpt_st->sws & FLTR_SW_TRAP_REJ)
			      ? "-->"DCC_XHDR_TRAP_REJ
			      : DCC_XHDR_ISSPAM);

	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;
		}
	}

	/* If any recipient cares about reputations, then report mail from
	 * IP addresses with bulk mail reputations with counts of "MANY" */
	if ((rcpt_st->sws & FLTR_SW_REP_ON)
	    && !(rcpt_st->fgs & RCPT_FG_WHITE))
		cwp->cmn_fgs |= CMN_FG_CHECK_REP;
}



/* Check DNS white- and blacklists for STMP client and envelope sender
 * after knowing all of the recipients. */
void
cmn_ask_dnsbl(CMN_WORK *cwp)
{
	if (!cwp->cks.dlw)
		return;

	/* turn off DNS lists for traps */
	if (cwp->and_sws & FLTR_SW_TRAP) {
		memset(&cwp->cks.dlw->tgt_hits, 0,
		       sizeof(cwp->cks.dlw->tgt_hits));
		return;
	}

	if (cwp->cks.sums[DCC_CK_IP].type == DCC_CK_IP)
		dcc_client_dnsbl(cwp->cks.dlw, &cwp->cks.ip_addr,
				cwp->sender_name);
	if (cwp->mail_host[0] != '\0')
		dcc_mail_host_dnsbl(cwp->cks.dlw, cwp->mail_host);
}



/* check the whitelists for all recipients at the end of the message */
void
cmn_ask_white(CMN_WORK *cwp)
{
	RCPT_ST *rcpt_st;
	RCPT_FGS global_fgs;
	DCC_OPS grey_op;
	WHITE_RESULT result;
	WHITE_LISTING listing;

	/* log sendmail access_db spam */
	if (cwp->ask_st & ASK_ST_MTA_ISSPAM)
		cwp->ask_st |= ASK_ST_LOGIT;

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

	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;
	result = wf_cks(&cwp->emsg, &cmn_wf, &cwp->cks, &cwp->wtgts, &listing);
	cwp->rcpt_fgs |= (white_results(0, cwp, &global_fgs, result, listing)
			  & RCPT_FG_LOG_WTGTS);

	/* get the defaults for the options if there were no valid recipients */
	if (!(cwp->init_sws & FLTR_SW_SET))
		init_sws(cwp, 1);
	unlock_wf();

	/* kludge similar to ask_white_rcpt() for no recipients for dccifd with
	 * the ASCII protocol */
	if ((rcpt_st = cwp->rcpt_st_first) == 0) {
		/* announce result from the whitelist */
		if (cwp->rcpt_fgs & (RCPT_FG_WHITE | RCPT_FG_BLACK))
			thr_log_print(cwp, 0, "whiteclnt%s\n",
				      (cwp->rcpt_fgs & RCPT_FG_WHITE)
				      ? DCC_XHDR_ISOK
				      : (cwp->init_sws & FLTR_SW_TRAP_DIS)
				      ? "-->"DCC_XHDR_TRAP_DIS
				      : (cwp->init_sws & FLTR_SW_TRAP_REJ)
				      ? "-->"DCC_XHDR_TRAP_REJ
				      : DCC_XHDR_ISSPAM);

		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;
			}
		}

		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);

		rcpt_fgs2ask_st(cwp, rcpt_st->sws, &rcpt_st->fgs);

		/* no greylist check if it is off or should not be done */
		if ((rcpt_st->sws & FLTR_SW_GREY_OFF)
		    || (cwp->ask_st & ASK_ST_GREY_OFF)
		    || (rcpt_st->fgs & (RCPT_FG_REJ_FILTER
					| RCPT_FG_BAD_USERNAME))
		    || (cwp->ask_st & ASK_ST_INVALID_MSG))
			continue;

		if (rcpt_st->fgs & RCPT_FG_BLACK) {
			if (cwp->cks.sums[DCC_CK_IP].type != DCC_CK_IP
			    || (cwp->cks.sums[DCC_CK_ENV_FROM].type
				!= DCC_CK_ENV_FROM))
				continue;
			grey_op = DCC_OP_GREY_QUERY;
		} else if (cwp->ask_st & ASK_ST_QUERY_GREY) {
			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.c);
			/* 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
						| ASK_ST_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
						| ASK_ST_LOGIT);
				if (cwp->max_embargo_num < rcpt_st->embargo_num)
				    cwp->max_embargo_num = rcpt_st->embargo_num;
			}
			break;
		case ASK_GREY_EMBARGO_END:
			cwp->ask_st |= (ASK_ST_GREY_LOGIT | ASK_ST_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);
	cwp->log_pos_ask_error = cwp->log_pos_white_last;
}



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

	/* 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 recipient count of 0 is a query. */
	if (cwp->ask_st & ASK_ST_QUERY) {
		cwp->cmn_fgs &= ~CMN_FG_LOCAL_SPAM;
		cwp->local_tgts = 0;
	} else if (cwp->ask_st & ASK_ST_CLNT_ISSPAM) {
		cwp->cmn_fgs |= CMN_FG_LOCAL_SPAM;
		cwp->local_tgts = cwp->tgts + cwp->mta_rej_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 recipients for
		 * which it is an initial transmission or embargo #1 */
		cwp->cmn_fgs &= ~CMN_FG_LOCAL_SPAM;
		cwp->local_tgts = cwp->early_grey_tgts + cwp->mta_rej_tgts;
		total_tgts = cwp->tgts + cwp->mta_rej_tgts;
	} else {
		/* if this is the end of a greylist embargo
		 * then do not tell the DCC about recipients that were
		 * counted with previous transmissions.  Those recipients
		 * 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
				   + cwp->mta_rej_tgts);
		total_tgts = cwp->tgts + cwp->mta_rej_tgts;
	}

	/* talk to the DCC server */
	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->cmn_fgs & CMN_FG_CHECK_REP) != 0,
		    total_tgts,
		    cwp->local_tgts);
	if (i <= 0) {
		cwp->log_pos_ask_error += thr_error_msg(cwp, "%s", cwp->emsg.c);
		return i;
	}

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

	return 1;
}


#define USER_LOG_CAPTION(rcpt_st, s) user_log_write((rcpt_st), (s), LITZ(s))
#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 (user_log_fd < 0)
		return 0;

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

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



static void DCC_PF(2,3)
user_log_print(RCPT_ST *rcpt_st, const char *p, ...)
{
	char logbuf[LOGBUF_SIZE*2];
	int i;
	va_list args;

	if (user_log_fd < 0)
		return;

	va_start(args, p);
	i = vsnprintf(logbuf, sizeof(logbuf), p, args);
	va_end(args);
	if (i < ISZ(logbuf)) {
		user_log_write(rcpt_st, logbuf, i);
		return;
	}
	user_log_write(rcpt_st, logbuf, sizeof(logbuf));
	user_log_write(rcpt_st, "...", 3);
}



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];
	int len;
	int result;

	if (user_log_fd < 0)
		return;

	if (start == stop)
		return;

	cwp = rcpt_st->cwp;

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

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

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


/* print
 *	      env_To: user@example.com			    ok
 *  user@example.com: f7a48ff4 70d29d39 4ed1e36f 104e4fa0
 *		      86e0517b b455b130 4cfccc8c f1a1ff37 Pass
 */
static void
print_addr_sum(LOG_WRITE_FNC out, void *arg,
	       const char *addr,
	       int addr_len,		/* trim trailing '>' from grey addr */
	       const char *sum,
	       int sum_len,		/* trim trailing '>' from env_To */
	       const char *tgts,
	       int tgts_width)		/* to right-justify env_To tgts */
{
	char buf[100];
	int i;

	i = snprintf(buf, sizeof(buf), PRINT_CK_PAT_LIM_CK" %*s\n",
		     addr_len, addr,
		     addr_len > 0 ? ':' : ' ',
		     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, LITZ(DCC_XHDR_TYPE_ENV_TO),
			       addr, addr_len,
			       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, LITZ(DCC_XHDR_TYPE_ENV_TO),
			       addr, addr_len,
			       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"
#define CK_HEADING_QUERY    "       "DCC_XHDR_GREY_RECIP" query\n"
	char cbuf[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_RESET, rcpt_st->embargo_num);
		embargo = embargo_buf;
		break;
	}

	if (!headed || !*headed) {
		if (headed)
			*headed = 1;
		if (rcpt_st->cwp->ask_st & ASK_ST_QUERY)
			out(arg, CK_HEADING_QUERY, LITZ(CK_HEADING_QUERY));
		else
			out(arg, CK_HEADING, LITZ(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,
		       ck2str(cbuf, sizeof(cbuf), DCC_CK_GREY_MSG,
			      &rcpt_st->msg_sum, 0),
		       PRINT_CK_SUM_LEN,
		       "", 0);
	print_addr_sum(out, arg, "", 0,
		       ck2str(cbuf, sizeof(cbuf), DCC_CK_GREY3,
			      &rcpt_st->triple_sum, 0),
		       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,
	   u_char log_type,		/* 0="" 1="per-user" 2="global" */
	   FLTR_SWS and_sws, FLTR_SWS or_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, and_sws, or_sws,
		   log_type, &cwp->header);
}



/* log the message for one user */
static void
user_log_msg(CMN_WORK *cwp, RCPT_ST *rcpt_st)
{
	DCC_PATH rcpt_logdir;
	const char *old_log;

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

	/* we need the main log file to create per-user log files */
	if (rcpt_st->dir.c[0] == '\0'
	    || dcc_main_logdir.c[0] == '\0')
		return;

	if (!log2_start(cwp))
		return;

	/* create the user's log file */
	snprintf(rcpt_logdir.c, sizeof(rcpt_logdir), "%s/log", rcpt_st->dir.c);
	/* try to use the same name as the main log file */
	old_log = strrchr(cwp->log_nm.c, '.');
	if (old_log) {
		if (strlen(++old_log) != DCC_MKSTEMP_LEN)
			old_log = 0;
	}
	user_log_fd = dcc_log_open(&cwp->emsg, &rcpt_st->user_log_nm,
				   cwp->id, sizeof(cwp->id), old_log,
				   rcpt_logdir.c, DCC_FIN_LOG_PREFIX,
				   (rcpt_st->sws & FLTR_SW_LOG_M)
				   ? LOG_MODE_MINUTE
				   : (rcpt_st->sws & FLTR_SW_LOG_H)
				   ? LOG_MODE_HOUR
				   : (rcpt_st->sws & FLTR_SW_LOG_D)
				   ? LOG_MODE_DAY
				   : LOG_MODE_FLAT);
	if (user_log_fd < 0) {
		if (user_log_fd == -1)
			thr_error_msg(cwp, "%s", cwp->emsg.c);
		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);
	user_log_block(rcpt_st,		/* copy DCC error messages */
		       cwp->log_pos_white_last,
		       cwp->log_pos_ask_error);

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

	/* log the checksums and their counts */
	print_cks((LOG_WRITE_FNC)user_log_write, rcpt_st,
		  (cwp->ask_st & ASK_ST_RPT_SPAM) != 0, cwp->local_tgts,
		  &cwp->cks, &rcpt_st->tholds,
		  ((cwp->rcpt_fgs & RCPT_FG_LOG_WTGTS)
		   || (rcpt_st->fgs & RCPT_FG_LOG_WTGTS))
		  ? &rcpt_st->wtgts : 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"%s %s %s\n",
		      cwp->reply.rcode, cwp->reply.xcode, cwp->reply.str);
}



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



/* tell the grey list to restore the embargo on a triple */
static void
grey_spam(CMN_WORK *cwp, RCPT_ST *rcpt_st)
{
	DCC_GREY_SPAM gs;
	DCC_OP_RESP resp;

	switch (rcpt_st->grey_result) {
	case ASK_GREY_FAIL:
	case ASK_GREY_OFF:
	case ASK_GREY_WHITE:
		return;
	case ASK_GREY_EMBARGO:
		if (rcpt_st->embargo_num == 0) {
			rcpt_st->grey_result = ASK_GREY_OFF;
			return;
		}
		break;
	case ASK_GREY_EMBARGO_END:
	case ASK_GREY_PASS:
		break;
	case ASK_GREY_SPAM:
		dcc_logbad(EX_SOFTWARE, "cmn_ask_white grey_result=%d",
			   rcpt_st->grey_result);
		return;
	}

	/* do not restore the embargo for traps
	 * or when greylisting is ignoring spam */
	if ((rcpt_st->sws & FLTR_SW_TRAP)
	    || (rcpt_st->sws & FLTR_SW_GREY_IGN_SPAM))
		return;

	memset(&gs, 0, sizeof(gs));

	if (cwp->cks.sums[DCC_CK_IP].type != DCC_CK_IP) {
		thr_error_msg(cwp, "missing IP checksum for grey_spam()");
		return;
	}

	gs.ip.type = DCC_CK_IP;
	gs.ip.len = sizeof(gs.ip);
	memcpy(&gs.ip.sum, &cwp->cks.sums[DCC_CK_IP].sum, sizeof(DCC_SUM));

	gs.triple.type = DCC_CK_GREY3;
	gs.triple.len = sizeof(gs.triple);
	gs.triple.sum = rcpt_st->triple_sum;

	gs.msg.type = DCC_CK_GREY_MSG;
	gs.msg.len = sizeof(gs.msg);
	gs.msg.sum = rcpt_st->msg_sum;

	if (!dcc_clnt_op(&cwp->emsg, cwp->dcc_ctxt, DCC_CLNT_FG_GREY,
			   0, 0, 0, &gs.hdr, sizeof(gs),
			   DCC_OP_GREY_SPAM, &resp, sizeof(resp))) {
		thr_error_msg(cwp, "%s", cwp->emsg.c);
	}

	rcpt_st->grey_result = ASK_GREY_SPAM;
}



/* 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, discard_ok;
	DCC_TGTS trap_dis;
	DNSBL_GBITS common_dnsbl_hits;
	const REPLY_TPLT *reply;
	u_char rep_brag;		/* 2=brag, 1=might brag, 0=no brag */
	int i;

	/* log the DCC results and headers in the common log file */
	log_isspam((LOG_WRITE_FNC)log_write, cwp, cwp, 2,
		   cwp->and_sws, cwp->or_sws, cwp->rcpt_fgs);

	/* log the checksums, DCC server counts and global whitelist values */
	print_cks((LOG_WRITE_FNC)log_write, cwp,
		  (cwp->ask_st & ASK_ST_RPT_SPAM) != 0, cwp->local_tgts,
		  &cwp->cks, &cwp->cks.tholds_rej,
		  (cwp->rcpt_fgs & RCPT_FG_LOG_WTGTS) ? &cwp->wtgts : 0);
	if (cwp->rcpt_fgs & RCPT_FG_LOG_WTGTS) {
		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;
	trap_dis = 0;
	discard_ok = 1;
	common_dnsbl_hits = DNSBL_GMASK;
	rep_brag = 1;
	for (rcpt_st = cwp->rcpt_st_first;
	     rcpt_st;
	     rcpt_st = rcpt_st->fwd) {
		/* Ignore recipients whose RCPT_TO commands were rejected */
		if (rcpt_st->fgs & RCPT_FG_REJ_FILTER)
			continue;

		/* We cannot decide whether the message might be spam
		 * for recipients rejected by the MTA because dccm does not
		 * have the mailer and so cannot find the likely
		 * userdirs/local/user/whiteclnt file */
		if (rcpt_st->fgs & RCPT_FG_BAD_USERNAME)
			continue;

		if (rcpt_st->fgs & RCPT_FG_WHITE) {
			common_dnsbl_hits = 0;
		} else {
			DNSBL_GBITS dnsbl_hits;

			dnsbl_hits = (FLTR_SW_DNSBL_BITS(rcpt_st->sws)
				      & ASK_ST_DNSBL_B_BITS(cwp->ask_st));
			common_dnsbl_hits &= dnsbl_hits;
			if (dnsbl_hits != 0)
				rcpt_st->fgs |= RCPT_FG_ISSPAM;

			/* decide whether it is spam for this recipient */
			if ((rcpt_st->fgs & RCPT_FG_BLACK)
			    || ((cwp->ask_st & ASK_ST_SRVR_ISSPAM)
				&& !(rcpt_st->sws & FLTR_SW_DCC_OFF)))
				rcpt_st->fgs |= RCPT_FG_ISSPAM;

			if ((cwp->ask_st & ASK_ST_REP_ISSPAM)
			    && (rcpt_st->sws & FLTR_SW_REP_ON)) {
				if (rep_brag)
					rep_brag = 2;
				rcpt_st->fgs |= RCPT_FG_ISSPAM;
			}
		}

		/* if one recipient is not using DNS Reputations,
		 * then do not brag about them */
		if (!(rcpt_st->sws & FLTR_SW_REP_ON))
			rep_brag = 0;
		if (rcpt_st->fgs & RCPT_FG_ISSPAM) {
			if (rcpt_st->sws & FLTR_SW_TRAP_DIS) {
				/* spam to a discarding trap */
				++trap_dis;
			} else if ((rcpt_st->sws & FLTR_SW_GREY_IGN_SPAM)
				   && (cwp->ask_st & ASK_ST_GREY_EMBARGO)) {
				/* spam to recipient that greylists even spam */
				 ++cwp->deliver_tgts;
			} else {
				/* normal spam */
				++cwp->reject_tgts;
				if (!(rcpt_st->sws & FLTR_SW_DISCARD_NO)) {
					discard_ok = 0;
				} else if (rcpt_st->sws & FLTR_SW_TRAP_REJ) {
					/* Reject traps should act like
					 * no-such-user errors. */
					cwp->action = CMN_REJECT;
				}
			}
		} else {
			/* normal mail */
			++cwp->deliver_tgts;
		}

		/* Tell greylist to restore the embargo for recipients that
		 * believe the message was spam
		 * and did not white- or blacklist it */
		if ((rcpt_st->fgs & RCPT_FG_ISSPAM)
		    && cwp->action != CMN_IGNORE)
			grey_spam(cwp, rcpt_st);

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

	if (cwp->action == CMN_IGNORE) {
		cwp->deliver_tgts += cwp->reject_tgts;
		cwp->reject_tgts = 0;
	}

	if (cwp->reject_tgts != 0 && cwp->deliver_tgts != 0) {
		/* The mail should be rejected or discarded for some targets
		 * but delivered to others.
		 * We can give the DATA command only one answer. */
		if (!discard_ok) {
			/* Reject the SMTP transaction if at least one of
			 * recipient for which it is spam refuses to let it be
			 * discarded. */
			thr_log_print(cwp, 0, "forced reject for %d targets\n",
				      cwp->deliver_tgts);
			cwp->reject_tgts += cwp->deliver_tgts;
			cwp->deliver_tgts = 0;
		} else {
			/* Otherwise accept the SMTP transaction and discard
			 * the message for recipients that don't want it. */
			thr_log_print(cwp, 0, "forced discard for %d targets\n",
				      cwp->reject_tgts);
			cwp->deliver_tgts += cwp->reject_tgts;
			cwp->reject_tgts = 0;
		}
	}

	/* Prefer to accept the SMTP transaction for discard traps, but let
	 * them leak. */
	if (trap_dis != 0) {
		if (cwp->reject_tgts != 0) {
			cwp->reject_tgts += trap_dis;
		} else if (cwp->deliver_tgts == 0) {
			/* discard mail sent only to discard traps with one
			 * operation */
			cwp->action = CMN_DISCARD;
			cwp->reject_tgts += trap_dis;
		} else {
			cwp->deliver_tgts += trap_dis;
		}
	}

	/* do not greylist 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, 0);
		return;
	}

	/*  finished if it is not spam or we won't do anything about it */
	if (cwp->reject_tgts == 0 || cwp->action != CMN_REJECT)
		return;

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

	/* brag about reputations */
	if (rep_brag > 1) {
		make_reply(&cwp->reply, &reputation_reply, cwp, 0);
		return;
	}

	/* if possible, use a DNSBL rejection message that applies
	 * to all recipients */
	if (common_dnsbl_hits != 0) {
		const DNSBL_HGROUP *hg;

		for (i = 0; i < NUM_DNSBL_GROUPS; ++i) {
			if (!(common_dnsbl_hits & DNSBL_G2B(i)))
				continue;
			hg = &cwp->cks.dlw->hgroups[i];
			reply = hg->dnsbl->reply;
			if (reply) {
				make_reply(&cwp->reply, reply, cwp, hg);
				return;
			}
		}
	}

	/* use the generic DCC rejection message */
	make_reply(&cwp->reply, &reject_reply, cwp, 0);
}



/* after having checked each user or recipient,
 *	dispose of the message for one of them */
static void
user_log_result(CMN_WORK *cwp, RCPT_ST *rcpt_st, const char *mta_msg)
{
	if (rcpt_st->rej_msg[0] != '\0') {
		/* log rejection result for this recipient in the main log */
		thr_log_print(cwp, 0, DCC_XHDR_REJ_RCPT_PAT,
			      rcpt_st->env_to, rcpt_st->rej_msg);

		/* per-user log file */
		if (!(rcpt_st->fgs & RCPT_FG_REJ_FILTER)
		    || (rcpt_st->sws & FLTR_SW_LOG_ALL)) {
			user_log_msg(cwp, rcpt_st);

			user_log_print(rcpt_st, DCC_XHDR_REJ_RCPT_PAT,
				       rcpt_st->env_to, rcpt_st->rej_msg);
			if (rcpt_st->rej_msg[0] == '4')
				USER_LOG_CAPTION(rcpt_st,
						 DCC_XHDR_RESULT
						 DCC_XHDR_RESULT_REJECT
						 " temporarily\n");
			else
				USER_LOG_CAPTION(rcpt_st,
						 DCC_XHDR_RESULT
						 DCC_XHDR_RESULT_REJECT);
		}
		return;
	}

	/* 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))
		user_log_msg(cwp, rcpt_st);

	if (cwp->ask_st & ASK_ST_INVALID_MSG) {
		user_log_print(rcpt_st, DCC_XHDR_RESULT"%s\n", mta_msg);
		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 (mta_msg) {
			if (cwp->reply.str && cwp->reply.str[0] != '\0')
				user_log_print(rcpt_st,
					       DCC_XHDR_REJ_DATA_MSG"%s\n",
					       cwp->reply.str);
			user_log_print(rcpt_st, DCC_XHDR_RESULT"%s\n",
				       mta_msg);
			return;
		}

		/* Log the rejection if it was spam for some other target */
		if (cwp->reject_tgts != 0) {
			user_log_smtp_reply(cwp, rcpt_st);
			USER_LOG_CAPTION(rcpt_st,
					 DCC_XHDR_RESULT
					 DCC_XHDR_RESULT_REJECT
					 DCC_XHDR_RESULT_FORCED"\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;
	}


	/* spam for this target */

	if (cwp->deliver_tgts != 0
	    && cwp->action != CMN_IGNORE) {
		/* other recipients do not want the SMTP transaction rejected
		 * or the message discarded by the MTA */
		if (can_discard_1) {
			++totals.tgts_discarded;
			user_reject_discard(cwp, rcpt_st);

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

			if (cwp->action == CMN_REJECT) {
				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
						 DCC_XHDR_RESULT_FORCED"\n");
				return;
			}
		}

		thr_log_print(cwp, 0,
			      DCC_XHDR_RESULT
			      DCC_XHDR_RESULT_I_A
			      " forced for %s\n",
			      rcpt_st->env_to);
		USER_LOG_CAPTION(rcpt_st,
				 DCC_XHDR_RESULT
				 DCC_XHDR_RESULT_I_A
				 DCC_XHDR_RESULT_FORCED"\n");
		return;
	}

	/* spam for all targets including this one */

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

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

	user_log_smtp_reply(cwp, rcpt_st);
	if (rcpt_st->sws & FLTR_SW_TRAP_DIS) {
		user_log_print(rcpt_st, DCC_XHDR_RESULT
			       "%s"DCC_XHDR_RESULT_FORCED"\n",
			       cwp->reply.log_result);
	} else {
		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 *mta_msg)	/* 0 or reject by MTA */
{
	RCPT_ST *rcpt_st;
	int error;

	error = pthread_mutex_lock(&user_log_mutex);
	if (error)
		dcc_logbad(EX_SOFTWARE, "pthread_mutex_lock(user_log): %s",
			   ERROR_STR1(error));
	user_log_owner = pthread_self();

	/* create individual log files and trim target list */
	for (rcpt_st = cwp->rcpt_st_first; rcpt_st; rcpt_st = rcpt_st->fwd) {
		if (rcpt_st->fgs & RCPT_FG_BAD_USERNAME)
			continue;
		if ((cwp->ask_st & ASK_ST_INVALID_MSG)
		    && !(rcpt_st->sws & FLTR_SW_LOG_ALL))
			continue;

		user_log_result(cwp, rcpt_st, mta_msg);

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

	log_fd2_close(-1);

	user_log_owner = 0;
	error = pthread_mutex_unlock(&user_log_mutex);
	if (error)
		dcc_logbad(EX_SOFTWARE, "pthread_mutex_unlock(user_log): %s",
			   ERROR_STR1(error));
}
