Mercurial > notdcc
diff thrlib/cmn.c @ 0:c7f6b056b673
First import of vendor version
author | Peter Gervai <grin@grin.hu> |
---|---|
date | Tue, 10 Mar 2009 13:49:58 +0100 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/thrlib/cmn.c Tue Mar 10 13:49:58 2009 +0100 @@ -0,0 +1,2523 @@ +/* Distributed Checksum Clearinghouse + * + * threaded version of client library + * + * Copyright (c) 2008 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 without + * changes for any purpose with or without fee is hereby granted, provided + * that the above copyright notice and this permission notice appear in all + * copies and any distributed versions or copies are either unchanged + * or not called anything similar to "DCC" or "Distributed Checksum + * Clearinghouse". + * + * Parties not eligible to receive a license under this agreement can + * obtain a commercial license to use DCC by contacting Rhyolite Software + * at sales@rhyolite.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.103-1.148 $Revision$ + */ + + +#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; + DCC_WF wf; +} CWF; +static CWF *cur_cwf, cwfs[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; + dcc_wf_init(&cur_cwf->wf, DCC_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, sizeof(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, ERROR_STR()); + cwp->tmp_fd = -1; + } + 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; +} + + + +/* If the immediate SMTP client because it is a listed MX server, + * then we must ignore its IP address and keep looking for the + * real SMTP client. */ +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); + return 0; + } + + if (tgts == DCC_TGTS_OK) { + return 0; + } + + 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_FROM_SUBMIT; + thr_log_print(cwp, 1, + "%s is a listed 'submit' client\n", + dcc_trim_ffff(cwp->sender_str)); + return 0; + } + + 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 { + return 0; + } + + /* we cannot greylist or reject through our MX servers */ + cwp->cmn_fgs |= CMN_FG_FROM_MX; + if (cwp->action == CMN_REJECT) + cwp->action = CMN_DISCARD; + + cwp->sender_name[0] = '\0'; + cwp->sender_str[0] = '\0'; + dcc_unget_ip_ck(&cwp->cks); + + /* tell caller to look at the next Received: header */ + 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) /* 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 = 0; + cwp->mail_host[0] = '\0'; + cwp->env_from[0] = '\0'; + cwp->early_log.len = 0; + cwp->emsg[0] = '\0'; + cwp->id[0] = '\0'; + cwp->header.used = 0; + cwp->cmn_fgs = CMN_FG_LOG_EARLY; + if (dcc_query_only) + cwp->ask_st |= 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->sws = 0; + rcpt_st->user[0] = '\0'; + rcpt_st->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) +{ + /* 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) +{ + char date_buf[40]; + + /* don't even whine if there is no log directory */ + if (dcc_main_logdir[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); + whined = now; + cwp->emsg[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_CLNT_LOCK + 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", + fnm2abs_err(abs_nm, cwp->log_nm), + ERROR_STR()); + + log_fd2 = open(cwp->log_nm, O_RDWR, 0); + if (log_fd2 < 0) { + thr_error_msg(cwp, "log_fd2 open(%s): %s", + fnm2abs_err(abs_nm, cwp->log_nm), + 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, 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); + } else { + /* give it a permanent name if it is interesting */ + dcc_log_keep(0, 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); + 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, 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) +{ + 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, ERROR_STR()); + dcc_log_close(0, cwp->log_nm, 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_CLNT_LOCK + 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, (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 dcc_vearly_log(&cwp->early_log, p, args); + } + + i = vsnprintf(logbuf, sizeof(logbuf), p, args); + if (i < ISZ(logbuf)) { + log_write(cwp, logbuf, i); + return i; + } + log_write(cwp, logbuf, sizeof(logbuf)); + log_write(cwp, "...", 3); + return i+3; +} + + + +/* does not append '\n' */ +int PATTRIB(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 ip_placekeeper) +{ + 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) + dcc_get_ipv6_ck(&cwp->cks, &cwp->clnt_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 -= LITZ(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); + + if (cwp->mail_host[0] == '\0') + parse_mail_host(cwp->env_from, cwp->mail_host, + sizeof(cwp->mail_host)); + + 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); + if (rcpt_st->fgs & RCPT_FG_BAD_USERNAME) { + LOG_CMN_CAPTION(cwp, " "DCC_XHDR_MTA_REJECTION"\n"); + } else { + 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); +} + + + +/* 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_PICK_SRVR + | 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_fname_len = get_xhdr_fname(cwp->xhdr_fname, + sizeof(cwp->xhdr_fname), + 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 (!fnm2rel(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)) + break; /* found old DCC_WF for target file */ + + 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_CONTINUE: + thr_error_msg(rcpt_st->cwp, "%s", rcpt_st->cwp->emsg); + /* fall through */ + case DCC_WHITE_OK: + /* notice if the file contains no checksums or CIDR 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.cidr.len == 0 + && !(cwf->wf.wtbl_flags & DCC_WHITE_FG_TRAPS)) { + rcpt_st->fgs |= RCPT_FG_NULL_WHITECLNT; + } else { + rcpt_st->fgs &= ~RCPT_FG_NULL_WHITECLNT; + memcpy(rcpt_st->wf_sum, cwf->wf.wtbl->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_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 one recipient's 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. + * It is the original, deprecated mechanism for turn DCC checks + * on and off for individual targets + * We get this value only from dcc_white_sum() */ + if (rcpt_st) + rcpt_st->sws &= ~FLTR_SW_DCC_OFF; + /* fall through */ + case DCC_WHITE_UNLISTED: + /* a spam trap rejects everything + * or accepts but marks everything as spam */ + if (rcpt_st && (rcpt_st->sws & FLTR_SW_TRAPS)) { + 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; + + 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_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) +{ + /* 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_HIT_BITS(cwp->ask_st))) + cwp->ask_st |= (ASK_ST_CLNT_ISSPAM | ASK_ST_LOGIT); +} + + + +/* set the global defaults for the switches or options */ +static void +rcpt_sws_def(CMN_WORK *cwp, u_char locked) +{ + if (!locked) + lock_wf(); + + if (to_white_only) + cwp->init_sws |= FLTR_SW_DCC_OFF; + cwp->init_sws |= FLTR_SW_NO_DISCARD; + cwp->init_sws = wf2sws(cwp->init_sws, &cmn_wf); + if (cannot_discard) /* enforce the default if necessary */ + cwp->init_sws |= FLTR_SW_NO_DISCARD; + cwp->rcpts_sws = cwp->init_sws; + + if (!locked) + unlock_wf(); +} + + + +/* merge global and per-user thresholds */ +static void +make_tholds(DCC_CKSUM_THOLDS out, CWF *cwf) +{ + dcc_merge_tholds(out, dcc_tholds_rej, cmn_wf.wtbl); + + if (cwf && cwf->wf.wtbl) + dcc_merge_tholds(out, out, cwf->wf.wtbl); +} + + + +/* set per-user switches and compute env_to whitelisting + * If we return a pointer, then we grabbed and have kept the lock */ +static CWF * +rcpt_sws_env_to(CMN_WORK *cwp, RCPT_ST *rcpt_st) +{ + CWF *cwf; + DCC_WHITE_LISTING listing; + + /* 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) + return 0; + + /* after mapping the 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) + return cwf; + + /* The first time for the first recipient, + * 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 */ + if (!(cwp->init_sws & FLTR_SW_SET)) + rcpt_sws_def(cwp, cwf != 0); + + /* get flags and filter settings for recipient, + * including setting FLTR_SW_SET so that we won't do this again */ + if (cwf) + rcpt_st->sws = wf2sws(cwp->init_sws, &cwf->wf); + else + rcpt_st->sws = cwp->init_sws; + if (cannot_discard) /* dccifd cannot discard */ + rcpt_st->sws |= FLTR_SW_NO_DISCARD; + + /* 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(); + + /* check the env_to address in the global whitelist */ + 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.) 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)) { + 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 per-user envelope Rcpt_To value and 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); + } + } + + /* remove any reset _ON bits from the consensus */ + cwp->rcpts_sws &= (rcpt_st->sws | ~FLTR_SWS_SETTINGS_ON); + /* add any set _OFF bits to the consensus */ + cwp->rcpts_sws |= (rcpt_st->sws & FLTR_SWS_SETTINGS_OFF); + + 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; + CWF *cwf; + FLTR_SWS save_rcpts_sws; + DCC_CKSUM_THOLDS tholds_rej; + + /* everything is compatible if we won't reject + * or if we are forced to reject for all if we reject for any */ + if ((cwp->action != CMN_REJECT) + || cannot_reject + || (cwp->cmn_fgs & CMN_FG_FROM_SUBMIT)) + return 1; + + /* postpone poking at whitelists on the first not-rejected recipient */ + rcpt_st2 = cwp->rcpt_st_first; + for (;;) { + /* wait until later if this is the first recipient */ + if (rcpt_st2 == rcpt_st_new) + return 1; + + if (!(rcpt_st2->fgs & (RCPT_FG_REJ_FILTER + | RCPT_FG_BAD_USERNAME))) + break; + rcpt_st2 = rcpt_st2->fwd; + if (!rcpt_st2) + return 1; + } + + /* we are dealing with a second recipient + * get the settings that we postponed when we saw the first recipient */ + if (!(rcpt_st2->sws & FLTR_SW_SET)) { + cwf = rcpt_sws_env_to(cwp, rcpt_st2); + make_tholds(cwp->cks.tholds_rej, cwf); + cwp->cmn_fgs |= CMN_FG_THOLDS_SET; + if (cwf) + unlock_wf(); + } + + /* See if this message might be accepted 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. + * If so, reject this recipient. */ + + save_rcpts_sws = cwp->rcpts_sws; + cwf = rcpt_sws_env_to(cwp, rcpt_st_new); + make_tholds(tholds_rej, cwf); + if (cwf) + unlock_wf(); + + /* Differing DCC thresholds conflict */ + if (memcmp(tholds_rej, cwp->cks.tholds_rej, sizeof(tholds_rej))) { + cwp->rcpts_sws = save_rcpts_sws; + cwp->ask_st |= ASK_ST_LOGIT; + rcpt_st_new->fgs |= RCPT_FG_INCOMPAT_REJ; + return 0; + } + + /* Accept-traps prefer to avoid all rejections including Mail_From + * commands and implicitly discard but tolerate rejections. + * Reject-traps require rejections. */ + if (rcpt_st_new->sws & FLTR_SW_TRAP_ACC) + return 1; + + /* everything other than differing thesholds is compatible + * if discarding is ok for all recipients so far */ + if (!(cwp->rcpts_sws & FLTR_SW_NO_DISCARD)) + return 1; + + do { + /* ignore already rejected recipients */ + if (rcpt_st2->fgs & (RCPT_FG_REJ_FILTER | RCPT_FG_BAD_USERNAME)) + continue; + + /* Traps are fexible. */ + if (rcpt_st2->sws & FLTR_SW_TRAP_ACC) + continue; + + /* 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_NO_DISCARD) + || (rcpt_st_new->sws & FLTR_SW_NO_DISCARD)) + && (((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; + cwp->ask_st |= ASK_ST_LOGIT; + rcpt_st_new->fgs |= RCPT_FG_INCOMPAT_REJ; + return 0; + } + + /* Stronger filter choices for a preceding recipient are + * potential reasons to reject the message not shared by the + * new recipient and that could force the message to be + * discarded for the preceding recipient */ + if ((rcpt_st2->sws & FLTR_SW_NO_DISCARD) + && ((FLTR_SWS_ON(rcpt_st2->sws) + & ~FLTR_SWS_ON(rcpt_st_new->sws)) != 0)) { + cwp->rcpts_sws = save_rcpts_sws; + cwp->ask_st |= ASK_ST_LOGIT; + rcpt_st_new->fgs |= RCPT_FG_INCOMPAT_REJ; + return 0; + } + + /* weaker filter choices for a preceding 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_NO_DISCARD) + && ((~FLTR_SWS_ON(rcpt_st2->sws) + & FLTR_SWS_ON(rcpt_st_new->sws)) != 0)) { + cwp->rcpts_sws = save_rcpts_sws; + cwp->ask_st |= ASK_ST_LOGIT; + rcpt_st_new->fgs |= RCPT_FG_INCOMPAT_REJ; + 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 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; + + /* Get the switch settings and the env_to whitelist results. */ + cwf = rcpt_sws_env_to(cwp, rcpt_st); + if (!(cwp->cmn_fgs & CMN_FG_THOLDS_SET)) { + cwp->cmn_fgs |= CMN_FG_THOLDS_SET; + make_tholds(cwp->cks.tholds_rej, 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 */ + 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", + fnm2abs_err(abs_nm, cwf->wf.ascii_nm), + (rcpt_st->env_to_fgs & RCPT_FG_WHITE) + ? DCC_XHDR_ISOK + : (rcpt_st->sws & FLTR_SW_TRAP_ACC) + ? "-->"DCC_XHDR_TRAP_ACC + : (rcpt_st->sws & FLTR_SW_TRAP_REJ) + ? "-->"DCC_XHDR_TRAP_REJ + : 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) +{ + RCPT_ST *rcpt_st; + RCPT_FGS global_fgs; + DCC_OPS grey_op; + DCC_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.dnsbl); + + 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 */ + if (!(cwp->init_sws & FLTR_SW_SET)) + rcpt_sws_def(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) { + 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 | FLTR_SW_TRAPS)) + || (cwp->cmn_fgs & (CMN_FG_FROM_MX | CMN_FG_FROM_SUBMIT)) + || (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); + /* 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; + + /* 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 (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 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 + cwp->mta_rej_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 + + 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->local_tgts); + if (i <= 0) { + cwp->log_pos_ask_error += 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_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", + fnm2abs_err(abs_nm, rcpt_st->user_log_nm), + ERROR_STR()); + else + thr_error_msg(rcpt_st->cwp, + "write(%s)=%d instead of %d", + fnm2abs_err(abs_nm, rcpt_st->user_log_nm), + result, (int)len); + dcc_log_close(0, rcpt_st->user_log_nm, user_log_fd, + &rcpt_st->cwp->ldate); + user_log_fd = -1; + return 0; +} + + + +static void PATTRIB(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, len, ERROR_STR()); + else + thr_error_msg(cwp, "user_log_block" + " read(%s,%d)=%d", + cwp->log_nm, 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, + 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, LITZ(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" +#define CK_HEADING_QUERY " "DCC_XHDR_GREY_RECIP" query\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_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, + dcc_ck2str(cbuf, sizeof(cbuf), + DCC_CK_GREY_MSG, rcpt_st->msg_sum, 0), + PRINT_CK_SUM_LEN, + "", 0); + print_addr_sum(out, arg, "", 0, + dcc_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 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, log_type, &cwp->header); +} + + + +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[0] == '\0' + || dcc_main_logdir[0] == '\0') + return; + + if (!log2_start(cwp)) + return; + + /* create the user's log file */ + snprintf(rcpt_logdir, sizeof(rcpt_logdir), "%s/log", rcpt_st->dir); + /* try to use the same name as the main log file */ + old_log = rindex(cwp->log_nm, '.'); + 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, 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); + 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->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.str, 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.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; + } + + memset(&gs, 0, sizeof(gs)); + + if (cwp->cks.sums[DCC_CK_IP].type != DCC_CK_IP) { + thr_error_msg(cwp, "missing IP checksum for dcc_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); + memcpy(&gs.triple.sum, rcpt_st->triple_sum, sizeof(DCC_SUM)); + + gs.msg.type = DCC_CK_GREY_MSG; + gs.msg.len = sizeof(gs.msg); + memcpy(&gs.msg.sum, rcpt_st->msg_sum, sizeof(DCC_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); + } + + 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; + int trap_acc_tgts; + DNSBL_GBITS dnsbl_sws, dnsbl_hits, common_dnsbl_hits; + int dnsbl_delay_tgts; + u_char dnsbl_timeo; + const DNSBL_GROUP *blg; + const REPLY_TPLT *reply; + int i; + + /* log the DCC results and headers in the common log file */ + log_isspam((LOG_WRITE_FNC)log_write, cwp, cwp, 2, + 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; + trap_acc_tgts = 0; + common_dnsbl_hits = -1; + dnsbl_delay_tgts = 0; + 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 targets 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; + dnsbl_timeo = 0; + } else { + /* decide whether it is spam for this target */ + 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; + + dnsbl_sws = FLTR_SW_DNSBL_BITS(rcpt_st->sws); + dnsbl_hits = (dnsbl_sws + & ASK_ST_DNSBL_HIT_BITS(cwp->ask_st)); + common_dnsbl_hits &= dnsbl_hits; + if (dnsbl_hits != 0) { + dnsbl_timeo = 0; + rcpt_st->fgs |= RCPT_FG_ISSPAM; + } else if (0 != (ASK_ST_DNSBL_TFAIL_BITS(cwp->ask_st) + & dnsbl_sws)) { + dnsbl_timeo = 1; + } else { + dnsbl_timeo = 0; + } + } + + if (rcpt_st->fgs & RCPT_FG_ISSPAM) { + if (rcpt_st->sws & FLTR_SW_TRAP_ACC) + ++trap_acc_tgts; + else + ++cwp->reject_tgts; + } else if (dnsbl_timeo) { + ++dnsbl_delay_tgts; + } else { + ++cwp->deliver_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->fgs & RCPT_FG_ISSPAM) + && !(rcpt_st->sws & FLTR_SW_TRAPS)) + 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 real targets or rejection-traps need to reject the message, + * then treat any accept-traps as reject-traps to avoid telling the + * SMTP client that the message was accepted. */ + if (cwp->reject_tgts != 0 && cwp->deliver_tgts == 0) { + cwp->reject_tgts += trap_acc_tgts; + } else { + cwp->deliver_tgts += trap_acc_tgts; + } + + /* temp-fail ambiguous mail if DNSBL checks timed out */ + if (dnsbl_delay_tgts != 0) { + if (cwp->deliver_tgts != 0) { + /* It is not ambiguous if it must be delivered to + * at least one recipient. */ + cwp->deliver_tgts += dnsbl_delay_tgts; + dnsbl_delay_tgts = 0; + } else { + cwp->reject_tgts += dnsbl_delay_tgts; + } + } + + if (cwp->reject_tgts != 0 && cwp->deliver_tgts != 0) { + /* It is spam for some targets and not for others. + * If we cannot discard, then reject it for all targets */ + if ((cwp->cmn_fgs & CMN_FG_FROM_SUBMIT) + || cannot_discard) { + thr_log_print(cwp, 0, + "rejection forced for %d targets\n", + cwp->deliver_tgts); + cwp->reject_tgts += cwp->deliver_tgts; + cwp->deliver_tgts = 0; + } else { + thr_log_print(cwp, 0, + "discard forced for %d targets\n", + cwp->reject_tgts); + } + } + + /* 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, 0); + return; + } + + /* finished if it is not spam or 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 possible, use a DNSBL rejection message that applies + * to all recipients */ + if (common_dnsbl_hits != 0) { + for (i = 0; i < MAX_DNSBL_GROUPS; ++i) { + if ((common_dnsbl_hits & DNSBL_G2B(i)) != 0 + && 0 != (blg = &cwp->cks.dnsbl->groups[i]) + && 0 != (reply = blg->dnsbl->reply)) { + make_reply(&cwp->reply, reply, cwp, blg); + return; + } + } + } + + if (dnsbl_delay_tgts != 0) { + make_reply(&cwp->reply, &dnsbl_timeo_reply, cwp, 0); + 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 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)) + user_log_msg(cwp, rcpt_st); + + if (cwp->ask_st & ASK_ST_INVALID_MSG) { + user_log_print(rcpt_st, DCC_XHDR_RESULT"%s\n", result); + return; + } + + if (rcpt_st->rej_msg[0] != '\0') { + /* rejection result for this recipient in the main log */ + thr_log_print(cwp, 0, + DCC_XHDR_RESULT_REJECT" %s: %s\n", + rcpt_st->env_to, rcpt_st->rej_result); + /* per-user log file */ + if (!(rcpt_st->fgs & RCPT_FG_INCOMPAT_REJ) + || (rcpt_st->sws & FLTR_SW_LOG_ALL)) { + USER_LOG_CAPTION(rcpt_st, DCC_XHDR_REJ_RCPT_MSG); + user_log_write(rcpt_st, rcpt_st->rej_msg, 0); + if (rcpt_st->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.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", + 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_FROM_SUBMIT) + || cannot_discard)) { + 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; + } + + /* It was spam for this target + * + * If some other target wanted the message, then we should discard it + * for this target. Dccifd in proxy mode cannot discard for an + * individual target, and so must avoid the possibility by + * recipients that might have differing choices with cannot_discard. */ + if (cwp->deliver_tgts != 0) { + /* Prefer to discard for spam traps, but just accept + * spam when we cannot discard it for dccifd */ + if (rcpt_st->sws & FLTR_SW_TRAPS) { + if (!cannot_discard) { + user_reject_discard(cwp, rcpt_st); + USER_LOG_CAPTION(rcpt_st, DCC_XHDR_RESULT + DCC_XHDR_RESULT_DISCARD"\n"); + } else { + USER_LOG_CAPTION(rcpt_st, DCC_XHDR_RESULT + DCC_XHDR_RESULT_ACCEPT"\n"); + } + + } else { + --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 + DCC_XHDR_RESULT_FORCED"\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); + if (rcpt_st->sws & FLTR_SW_TRAP_ACC) { + 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 *result) /* 0 or reject by dccifd's server */ +{ + 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, result); + + if (user_log_fd >= 0) { + dcc_log_close(0, rcpt_st->user_log_nm, + 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)); +}