Mercurial > notdcc
view thrlib/cmn.c @ 4:d329bb5c36d0
Changes making it compile the new upstream release
author | Peter Gervai <grin@grin.hu> |
---|---|
date | Tue, 10 Mar 2009 14:57:12 +0100 |
parents | c7f6b056b673 |
children |
line wrap: on
line source
/* 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)); }