Mercurial > notdcc
view dcclib/ask.c @ 5:0a7a5940ee3a
Change description per license
author | Peter Gervai <grin@grin.hu> |
---|---|
date | Tue, 10 Mar 2009 15:03:24 +0100 |
parents | c7f6b056b673 |
children |
line wrap: on
line source
/* Distributed Checksum Clearinghouse * * ask about a batch of checksums * * 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.146 $Revision$ */ #include "dcc_ck.h" #include "dcc_heap_debug.h" #include "dcc_xhdr.h" static DCC_CKSUM_THOLDS dcc_tholds_log; DCC_CKSUM_THOLDS dcc_tholds_rej; static u_char dcc_honor_nospam[DCC_DIM_CKS]; static u_char trim_grey_ip_addr; /* remove IP address from grey triple */ static struct in6_addr grey_ip_mask; static void honor_cnt(const DCC_GOT_CKS *cks, u_int *, DCC_CK_TYPES, DCC_TGTS); #ifdef DCC_PKT_VERSION5 /* figure old server's target count before our latest report */ static DCC_TGTS /* return corrected current count */ save_p_tgts(DCC_GOT_SUM *g, /* put previous count in g->tgts */ DCC_OPS op, const DCC_TGTS local_tgts, /* real local target count */ const DCC_TGTS gross_tgts, /* local count adjusted by blacklist */ DCC_TGTS c_tgts) /* what the old DCC server said */ { DCC_CK_TYPES type = g->type; if (op == DCC_OP_QUERY) { /* if we switched servers and converted a report * to a query, then guess the total that the * server would have produced for a report * instead of the query we sent. * * Assume the server is not running with -K. * If the server's current value is 0 for a body checksum * then assume the report we sent to the other server has not * been flooded. * Assume other checksums will always be zero/unknown. */ if (DB_GLOBAL_NOKEEP(0, type)) return 0; /* Assume the current value is really the previous value * because flooding has not happened */ g->tgts = c_tgts; if (c_tgts < DCC_TGTS_TOO_MANY && DCC_CK_IS_BODY(type)) { c_tgts += local_tgts; if (c_tgts > DCC_TGTS_TOO_MANY) c_tgts = DCC_TGTS_TOO_MANY; } return c_tgts; } else if (c_tgts >= gross_tgts && gross_tgts < DCC_TGTS_TOO_MANY) { /* if possible infer server's value before our report */ if (c_tgts >= DCC_TGTS_TOO_MANY) g->tgts = c_tgts; else g->tgts = c_tgts - gross_tgts; } return c_tgts; } #endif /* DCC_PKT_VERSION5 */ int /* 1=ok, 0=no answer, -1=fatal */ ask_dcc(DCC_EMSG emsg, DCC_CLNT_CTXT *ctxt, DCC_CLNT_FGS clnt_fgs, /* DCC_CLNT_FG_* */ DCC_HEADER_BUF *hdr, /* put results here */ DCC_GOT_CKS *cks, /* and here */ ASK_ST *ask_stp, /* and here */ u_char spam, /* spam==0 && local_tgts==0 --> query */ DCC_TGTS local_tgts) /* report these targets to DCC server */ { union { DCC_HDR hdr; DCC_REPORT r; } rpt; DCC_OP_RESP resp; DCC_OPS op; DCC_CK *ck; DCC_GOT_SUM *g; DCC_TGTS gross_tgts; DCC_TGTS c_tgts; /* server's current, total count */ DCC_CKS_WTGTS hdr_tgts; /* values for X-DCC header */ DCC_CK_TYPES type; DCC_SRVR_ID srvr_id; int pkt_len, recv_len, exp_len; int num_cks, ck_num, result; memset(hdr_tgts, 0, sizeof(hdr_tgts)); /* prepare a report for the nearest DCC server */ if (local_tgts == 0 && !spam) { /* because of greylisting, we can have a target count of 0 * but need to report spam discovered by a DNSBL */ op = DCC_OP_QUERY; gross_tgts = 0; rpt.r.tgts = 0; } else { op = DCC_OP_REPORT; if (local_tgts == DCC_TGTS_TOO_MANY || local_tgts == 0) { spam = 1; local_tgts = 1; } if (spam) { *ask_stp |= (ASK_ST_CLNT_ISSPAM | ASK_ST_LOGIT); gross_tgts = DCC_TGTS_TOO_MANY; rpt.r.tgts = htonl(local_tgts | DCC_TGTS_SPAM); } else { gross_tgts = local_tgts; rpt.r.tgts = htonl(local_tgts); } } ck = rpt.r.cks; num_cks = 0; for (g = cks->sums; g <= &cks->sums[DCC_CK_TYPE_LAST]; ++g) { /* never tell the DCC server about some headers */ if (!g->rpt2srvr) continue; ck->len = sizeof(*ck); ck->type = g->type; memcpy(ck->sum, g->sum, sizeof(ck->sum)); ++ck; ++num_cks; } if (num_cks == 0) { /* pretend we always have at least a basic body checksum * guess the DCC would have answered 0 */ xhdr_init(hdr, 0); xhdr_add_ck(hdr, DCC_CK_BODY, gross_tgts); honor_cnt(cks, ask_stp, DCC_CK_BODY, local_tgts); return 1; } /* send the report and see what the DCC has to say */ pkt_len = (sizeof(rpt.r) - sizeof(rpt.r.cks) + num_cks * sizeof(rpt.r.cks[0])); result = dcc_clnt_op(emsg, ctxt, clnt_fgs, 0, &srvr_id, 0, &rpt.hdr, pkt_len, op, &resp, sizeof(resp)); /* try a query to different server if the first failed * but a second was found */ if (!result && srvr_id != DCC_ID_INVALID) { if (dcc_clnt_debug) { if (emsg && *emsg != '\0') { dcc_trace_msg("retry with different server" " after: %s", emsg); *emsg = '\0'; } else { dcc_trace_msg("retry with different server"); } } op = DCC_OP_QUERY; result = dcc_clnt_op(emsg, ctxt, clnt_fgs | DCC_CLNT_FG_RETRY, 0, &srvr_id, 0, &rpt.hdr, pkt_len, op, &resp, sizeof(resp)); } if (!result) { *ask_stp |= ASK_ST_LOGIT; } else { /* forget about it if the DCC server responded too strangely */ recv_len = ntohs(resp.hdr.len); #ifdef DCC_PKT_VERSION5 if (resp.hdr.pkt_vers <= DCC_PKT_VERSION5) exp_len = (sizeof(resp.ans5) - sizeof(resp.ans5.b) + num_cks*sizeof(DCC_TGTS)); else #endif exp_len = (sizeof(resp.ans) - sizeof(resp.ans.b) + num_cks*sizeof(resp.ans.b[0])); if (recv_len != exp_len) { dcc_pemsg(EX_UNAVAILABLE, emsg, "DCC %s: answered with %d instead of %d bytes", dcc_srvr_nm(0), recv_len, exp_len); *ask_stp |= ASK_ST_LOGIT; result = -1; } } /* check the server's response to see if we have spam */ ck_num = 0; for (g = cks->sums; g <= &cks->sums[DCC_CK_TYPE_LAST]; ++g) { if (!g->rpt2srvr) { /* pretend we always have a basic body checksum */ if (g == &cks->sums[DCC_CK_BODY]) honor_cnt(cks, ask_stp, DCC_CK_BODY, local_tgts); continue; } type = g->type; /* g->type is valid only if rpt2srvr */ if (result <= 0) { c_tgts = (DCC_CK_IS_BODY(type)) ? gross_tgts : 0; #ifdef DCC_PKT_VERSION5 } else if (resp.hdr.pkt_vers <= DCC_PKT_VERSION5) { c_tgts = save_p_tgts(g, op, local_tgts, gross_tgts, ntohl(resp.ans5.b[ck_num])); } else { #endif /* DCC_PKT_VERSION5 */ /* server's total before our report */ g->tgts = ntohl(resp.ans.b[ck_num].p); /* new total */ c_tgts = ntohl(resp.ans.b[ck_num].c); #ifdef DCC_PKT_VERSION5 } #endif ++ck_num; hdr_tgts[type] = c_tgts; /* notice DCC server's whitelist */ if (dcc_honor_nospam[type]) { if (c_tgts == DCC_TGTS_OK) { *ask_stp |= ASK_ST_SRVR_NOTSPAM; } else if (c_tgts == DCC_TGTS_OK2) { /* if server says it is half ok, * look for two halves */ if (*ask_stp & ASK_ST_SRVR_OK2) { *ask_stp |= ASK_ST_SRVR_NOTSPAM; } else { *ask_stp |= ASK_ST_SRVR_OK2; } } } honor_cnt(cks, ask_stp, type, c_tgts); } /* honor server whitelist */ if (*ask_stp & ASK_ST_SRVR_NOTSPAM) *ask_stp &= ~ASK_ST_SRVR_ISSPAM; /* generate the header line now that we have checked all of * the counts against their thresholds and so know if we * must add "bulk". Add the header even if checking is turned off * and we won't reject affected messages. Say "many" for DNSBL * or local blacklist spam even without an answer from the DCC server * so that SpamAssassin gets the message. */ xhdr_init(hdr, srvr_id); if (*ask_stp & ASK_ST_SRVR_ISSPAM) { xhdr_add_str(hdr, DCC_XHDR_BULK); } else if (*ask_stp & ASK_ST_CLNT_ISSPAM) { xhdr_add_str(hdr, DCC_XHDR_BULK); hdr_tgts[DCC_CK_BODY] = DCC_TGTS_TOO_MANY; } else if (*ask_stp & ASK_ST_REP_ISSPAM) { xhdr_add_str(hdr, DCC_XHDR_BULK_REP); hdr_tgts[DCC_CK_BODY] = DCC_TGTS_TOO_MANY; } for (g = cks->sums; g <= &cks->sums[DCC_CK_TYPE_LAST]; ++g) { if (!g->rpt2srvr) { /* pretend we always have a body checksum */ if (g == &cks->sums[DCC_CK_BODY]) xhdr_add_ck(hdr, DCC_CK_BODY, hdr_tgts[DCC_CK_BODY]); continue; } /* Add interesing counts to the header. * Body checksums are always interestig if we have them. * Pretend we always have a basic body checksum. */ type = g->type; if (DCC_CK_IS_BODY(type)) { xhdr_add_ck(hdr, type, hdr_tgts[type]); continue; } if (hdr_tgts[type] != 0) xhdr_add_ck(hdr, type, hdr_tgts[type]); } return result; } /* check message's checksums in whiteclnt for dccproc or dccsight */ u_char /* 1=ok 0=something to complain about */ unthr_ask_white(DCC_EMSG emsg, ASK_ST *ask_stp, FLTR_SWS *swsp, const char *white_nm, DCC_GOT_CKS *cks, DCC_CKS_WTGTS wtgts) { DCC_WHITE_LISTING listing; int retval; /* assume DNSBLs are on unless turned off, because there is no reason * to use `dccproc -B` if you don't want to use them */ *swsp |= FLTR_SW_DNSBL_M; /* fake whiteclnt if not specified */ if (!white_nm) { dcc_merge_tholds(cks->tholds_rej, dcc_tholds_rej, 0); return 1; } /* don't filter if something is wrong with the file */ if (!dcc_new_white_nm(emsg, &cmn_wf, white_nm)) { *ask_stp |= ASK_ST_WLIST_NOTSPAM | ASK_ST_LOGIT; return 0; } /* let whiteclnt file turn off the DCC and other filters */ *swsp = wf2sws(*swsp, &cmn_wf); /* combine the command-line thresholds with the thresholds from * from the common /var/dcc/whiteclnt file */ dcc_merge_tholds(cks->tholds_rej, dcc_tholds_rej, cmn_wf.wtbl); retval = 1; switch (dcc_white_cks(emsg, &cmn_wf, cks, wtgts, &listing)) { case DCC_WHITE_OK: case DCC_WHITE_NOFILE: break; case DCC_WHITE_SILENT: *ask_stp |= ASK_ST_LOGIT; break; case DCC_WHITE_COMPLAIN: case DCC_WHITE_CONTINUE: retval = 0; *ask_stp |= ASK_ST_LOGIT; break; } switch (listing) { case DCC_WHITE_LISTED: /* do not send whitelisted checksums to DCC server */ *ask_stp |= ASK_ST_WLIST_NOTSPAM; break; case DCC_WHITE_USE_DCC: case DCC_WHITE_UNLISTED: if (*swsp & FLTR_SW_TRAPS) *ask_stp |= (ASK_ST_CLNT_ISSPAM | ASK_ST_WLIST_ISSPAM | ASK_ST_LOGIT); break; case DCC_WHITE_BLACK: *ask_stp |= (ASK_ST_WLIST_ISSPAM | ASK_ST_CLNT_ISSPAM | ASK_ST_LOGIT); break; } if (*swsp & FLTR_SW_LOG_ALL) *ask_stp |= ASK_ST_LOGIT; return retval; } /* ask the DCC for dccproc or dccsight but not dccifd or dccm */ u_char /* 1=ok 0=something to complain about */ unthr_ask_dcc(DCC_EMSG emsg, DCC_CLNT_CTXT *ctxt, DCC_HEADER_BUF *hdr, /* put header here */ ASK_ST *ask_stp, /* put state bites here */ DCC_GOT_CKS *cks, /* these checksums */ u_char spam, /* spam==0 && local_tgts==0 --> query */ DCC_TGTS local_tgts) /* number of addressees */ { if (*ask_stp & ASK_ST_WLIST_NOTSPAM) { if (spam) { /* if dccproc says it is spam, then it is, even if * the whiteclnt file says we cannot report it */ *ask_stp |= (ASK_ST_CLNT_ISSPAM | ASK_ST_LOGIT); xhdr_init(hdr, 0); xhdr_add_ck(hdr, DCC_CK_BODY, DCC_TGTS_TOO_MANY); } else { xhdr_whitelist(hdr); } /* honor log threshold for whitelisted messages */ dcc_honor_log_cnts(ask_stp, cks, local_tgts); return 1; } else { /* if allowed by whitelisting, report our checksums to the DCC * and return with that result including setting logging */ return (0 < ask_dcc(emsg, ctxt, DCC_CLNT_FG_NONE, hdr, cks, ask_stp, spam, local_tgts)); } } /* parse -g for dccm and dccproc */ void dcc_parse_honor(const char *arg0) { const char *arg; DCC_CK_TYPES type, t2; int i; arg = arg0; if (!CLITCMP(arg, "not_") || !CLITCMP(arg, "not-")) { arg += LITZ("not_"); i = 0; } else if (!CLITCMP(arg, "no_") || !CLITCMP(arg, "no-")) { arg += LITZ("no_"); i = 0; } else { i = 1; } /* allow -g for ordinary checksums but not reputations or greylisting */ type = dcc_str2type_thold(arg, -1); if (type == DCC_CK_INVALID) { dcc_error_msg("unrecognized checksum type in \"-g %s\"", arg0); return; } for (t2 = DCC_CK_TYPE_FIRST; t2 <= DCC_CK_TYPE_LAST; ++t2) { if (t2 == type || (type == SET_ALL_THOLDS && IS_ALL_CKSUM(t2)) || (type == SET_CMN_THOLDS && IS_CMN_CKSUM(t2))) dcc_honor_nospam[t2] = i; } } void dcc_clear_tholds(void) { DCC_CK_TYPES type; memset(dcc_honor_nospam, 0, sizeof(dcc_honor_nospam)); dcc_honor_nospam[DCC_CK_IP] = 1; dcc_honor_nospam[DCC_CK_ENV_FROM] = 1; dcc_honor_nospam[DCC_CK_FROM] = 1; for (type = DCC_CK_TYPE_FIRST; type <= DCC_CK_TYPE_LAST; ++type) { dcc_tholds_log[type] = DCC_THOLD_UNSET; dcc_tholds_rej[type] = DCC_THOLD_UNSET; } } u_char /* 1=merged from whiteclnt wtbl */ dcc_merge_tholds(DCC_CKSUM_THOLDS out, const DCC_CKSUM_THOLDS in, const DCC_WHITE_TBL *wtbl) { DCC_CK_TYPES type; DCC_TGTS tgts; u_char result; if (in != out) memcpy(out, in, sizeof(DCC_CKSUM_THOLDS)); if (!wtbl) return 0; result = 0; for (type = DCC_CK_TYPE_FIRST; type <= DCC_CK_TYPE_LAST; ++type) { tgts = wtbl->hdr.tholds_rej[type]; if (tgts != DCC_THOLD_UNSET) { out[type] = tgts; result = 1; } } return result; } /* parse type,[log-thold,]rej-thold */ u_char /* 1=need a log directory */ dcc_parse_tholds(const char *f, /* "-c " or "-t " */ const char *arg) /* optarg */ { DCC_CK_TYPES type; DCC_TGTS log_tgts, rej_tgts; char *thold_rej, *thold_log; u_char log_tgts_set, rej_tgts_set; thold_log = strchr(arg, ','); if (!thold_log) { dcc_error_msg("missing comma in \"%s%s\"", f, arg); return 0; } type = dcc_str2type_thold(arg, thold_log-arg); if (type == DCC_CK_INVALID) { dcc_error_msg("unrecognized checksum type in \"%s%s\"", f, arg); return 0; } thold_log = dcc_strdup(++thold_log); /* if there is only one threshold, take it as the spam threshold */ thold_rej = strchr(thold_log, ','); if (!thold_rej) { thold_rej = thold_log; thold_log = 0; } else { *thold_rej++ = '\0'; } log_tgts_set = log_tgts = 0; if (thold_log && *thold_log != '\0') { log_tgts = dcc_str2thold(type, thold_log); if (log_tgts == DCC_TGTS_INVALID) dcc_error_msg("unrecognized logging threshold" " \"%s\" in \"%s%s\"", thold_log, f, arg); else log_tgts_set = 1; } rej_tgts_set = rej_tgts = 0; if (!thold_rej || *thold_rej == '\0') { if (!thold_log || *thold_log == '\0') dcc_error_msg("no thresholds in \"%s%s\"", f, arg); } else { rej_tgts = dcc_str2thold(type, thold_rej); if (rej_tgts == DCC_TGTS_INVALID) dcc_error_msg("unrecognized rejection threshold" " \"%s\" in \"%s%s\"", thold_rej, f, arg); else rej_tgts_set = 1; } if (log_tgts_set || rej_tgts_set) { DCC_CK_TYPES t2; for (t2 = DCC_CK_TYPE_FIRST; t2 <= DCC_CK_TYPE_LAST; ++t2) { if (t2 == type || (type == SET_ALL_THOLDS && IS_ALL_CKSUM(t2)) || (type == SET_CMN_THOLDS && IS_CMN_CKSUM(t2))) { if (log_tgts_set) dcc_tholds_log[t2] = log_tgts; if (rej_tgts_set) dcc_tholds_rej[t2] = rej_tgts; } } } dcc_free(thold_log); return log_tgts_set; } static void honor_cnt(const DCC_GOT_CKS *cks, ASK_ST *ask_stp, /* previous flag bits */ DCC_CK_TYPES type, /* which kind of checksum */ DCC_TGTS type_tgts) /* total count for the checksum */ { if (type >= DIM(dcc_honor_nospam)) return; /* reject and log spam */ if (cks->tholds_rej[type] <= DCC_TGTS_TOO_MANY && cks->tholds_rej[type] <= type_tgts && type_tgts <= DCC_TGTS_TOO_MANY) { *ask_stp |= (ASK_ST_SRVR_ISSPAM | ASK_ST_LOGIT); return; } /* log messages that are bulkier than the log threshold */ if (dcc_tholds_log[type] <= DCC_TGTS_TOO_MANY && dcc_tholds_log[type] <= type_tgts) *ask_stp |= ASK_ST_LOGIT; } /* honor log threshold for local counts and white-/blacklists */ void dcc_honor_log_cnts(ASK_ST *ask_stp, /* previous flag bits */ const DCC_GOT_CKS *cks, /* these server counts */ DCC_TGTS tgts) { const DCC_GOT_SUM *g; DCC_CK_TYPES type; if (*ask_stp & ASK_ST_LOGIT) return; if (tgts == DCC_TGTS_TOO_MANY) { *ask_stp |= ASK_ST_LOGIT; return; } /* pretend we always have a body checksum for the log threshold */ if (dcc_tholds_log[DCC_CK_BODY] <= DCC_TGTS_TOO_MANY && dcc_tholds_log[DCC_CK_BODY] <= tgts) { *ask_stp |= ASK_ST_LOGIT; return; } for (g = cks->sums; g <= LAST(cks->sums); ++g) { type = g->type; if (type == DCC_CK_INVALID || type == DCC_CK_ENV_TO) continue; if (dcc_tholds_log[type] > DCC_TGTS_TOO_MANY) continue; if (dcc_tholds_log[type] <= tgts) { *ask_stp |= ASK_ST_LOGIT; return; } } } /* compute switch settings from bits in a whiteclnt file */ FLTR_SWS wf2sws(FLTR_SWS sws, const DCC_WF *wf) { static time_t complained; time_t now; DCC_PATH abs_nm; int i; if (!grey_on && (wf->wtbl_flags & (DCC_WHITE_FG_GREY_ON | DCC_WHITE_FG_GREY_LOG_ON)) && (now = time(0)) > complained+24*60*60) { complained = now; dcc_error_msg("%s wants greylisting" " but it is turned off", fnm2abs_err(abs_nm, wf->ascii_nm)); } /* compute switch values from whiteclnt bits */ if (wf->wtbl_flags & DCC_WHITE_FG_NO_DISCARD) sws |= FLTR_SW_NO_DISCARD; else if (wf->wtbl_flags & DCC_WHITE_FG_DISCARD_OK) sws &= ~FLTR_SW_NO_DISCARD; if ((wf->wtbl_flags & DCC_WHITE_FG_DCC_OFF)) sws |= FLTR_SW_DCC_OFF; else if (wf->wtbl_flags & DCC_WHITE_FG_DCC_ON) sws &= ~FLTR_SW_DCC_OFF; if (grey_on && (wf->wtbl_flags & DCC_WHITE_FG_GREY_ON)) { sws &= ~FLTR_SW_GREY_OFF; } else if (!grey_on || (wf->wtbl_flags & DCC_WHITE_FG_GREY_OFF)) { sws |= FLTR_SW_GREY_OFF; } if (wf->wtbl_flags & DCC_WHITE_FG_LOG_ALL) { sws |= FLTR_SW_LOG_ALL; } else if (wf->wtbl_flags & DCC_WHITE_FG_LOG_NORMAL) { sws &= ~FLTR_SW_LOG_ALL; } if (wf->wtbl_flags & DCC_WHITE_FG_GREY_LOG_ON) { sws &= ~FLTR_SW_GREY_LOG_OFF; } else if (wf->wtbl_flags & DCC_WHITE_FG_GREY_LOG_OFF) { sws |= FLTR_SW_GREY_LOG_OFF; } if (wf->wtbl_flags & DCC_WHITE_FG_LOG_M) { sws |= FLTR_SW_LOG_M; } else if (wf->wtbl_flags & DCC_WHITE_FG_LOG_H) { sws |= FLTR_SW_LOG_H; } else if (wf->wtbl_flags & DCC_WHITE_FG_LOG_D) { sws |= FLTR_SW_LOG_D; } if (wf->wtbl_flags & DCC_WHITE_FG_MTA_FIRST) { sws |= FLTR_SW_MTA_FIRST; } else if (wf->wtbl_flags & DCC_WHITE_FG_MTA_LAST) { sws &= ~FLTR_SW_MTA_FIRST; } for (i = 0; i < MAX_DNSBL_GROUPS; ++i) { if ((wf->wtbl_flags & DCC_WHITE_FG_DNSBL_ON(i)) && dnsbls) { sws |= FLTR_SW_DNSBL(i); } else if (wf->wtbl_flags & DCC_WHITE_FG_DNSBL_OFF(i)) { sws &= ~FLTR_SW_DNSBL(i); } } if (wf->wtbl_flags & DCC_WHITE_FG_TRAP_ACC) { sws |= FLTR_SW_TRAP_ACC; } else if (wf->wtbl_flags & DCC_WHITE_FG_TRAP_REJ) { sws |= FLTR_SW_TRAP_REJ; } return sws | FLTR_SW_SET; } #define LOG_ASK_ST_BLEN 160 #define LOG_ASK_ST_OFF "(off)" #define LOG_ASK_ST_OVF " ...\n\n" static int log_ask_st_sub(char *buf, int blen, const char *s, int slen, u_char off) { int dlen, tlen; /* quit if no room at all in the log */ if (blen >= LOG_ASK_ST_BLEN) return LOG_ASK_ST_BLEN; /* quit if nothing to say */ if (!s || !slen) return blen; dlen = LOG_ASK_ST_BLEN - blen; tlen = LITZ(LOG_ASK_ST_OVF)+2+slen; if (off) /* notice if we need to say "(off)" */ tlen += LITZ(LOG_ASK_ST_OFF); if (dlen <= tlen) { /* show truncation of the message with "..." */ memcpy(&buf[blen], LOG_ASK_ST_OVF, LITZ(LOG_ASK_ST_OVF)); blen += LITZ(LOG_ASK_ST_OVF); if (blen < LOG_ASK_ST_BLEN) memset(&buf[blen], ' ', LOG_ASK_ST_BLEN-blen); return LOG_ASK_ST_BLEN; } if (blen > 0 && buf[blen-1] != '\n') { buf[blen++] = ' '; buf[blen++] = ' '; } memcpy(buf+blen, s, slen); blen += slen; if (off) { memcpy(buf+blen, LOG_ASK_ST_OFF, LITZ(LOG_ASK_ST_OFF)); blen += LITZ(LOG_ASK_ST_OFF); } return blen; } /* generate log file line of results */ void log_ask_st(LOG_WRITE_FNC fnc, void *cp, ASK_ST ask_st, FLTR_SWS sws, u_char log_type, /* 0="" 1="per-user" 2="global" */ const DCC_HEADER_BUF *hdr) { char buf[LOG_ASK_ST_BLEN+3]; char dnsbl_buf[24]; int blen, len, i; #define S(str,off) (blen = log_ask_st_sub(buf, blen, str, LITZ(str), off)) #define S0(bit,off,str) if (ask_st & bit) S(str,off) #define S1(bit,s1off,str) S0(bit,(log_type != 2 && (s1off)),str) #define S2(bit,str) S0(bit,0,str) blen = 0; S2(ASK_ST_QUERY, "query"); /* the CGI scripts want to know why */ if (sws & FLTR_SW_MTA_FIRST) { S2(ASK_ST_MTA_ISSPAM, "MTA"DCC_XHDR_ISSPAM); S2(ASK_ST_MTA_NOTSPAM, "MTA"DCC_XHDR_ISOK); } S2(ASK_ST_WLIST_NOTSPAM, "wlist"DCC_XHDR_ISOK); if (log_type != 2 && !(ask_st & ASK_ST_WLIST_NOTSPAM)) S2(ASK_ST_WLIST_ISSPAM, "wlist"DCC_XHDR_ISSPAM); S1(ASK_ST_SRVR_ISSPAM, (sws & FLTR_SW_DCC_OFF), "DCC"DCC_XHDR_ISSPAM); S1(ASK_ST_SRVR_NOTSPAM, (sws & FLTR_SW_DCC_OFF), "DCC"DCC_XHDR_ISOK); S1(ASK_ST_REP_ISSPAM, !(sws & FLTR_SW_REP_ON), "Rep"DCC_XHDR_ISSPAM); for (i = 0; i < MAX_DNSBL_GROUPS; ++i) { if (ask_st & ASK_ST_DNSBL_HIT(i)) { if (have_dnsbl_groups) len = snprintf(dnsbl_buf, sizeof(dnsbl_buf), "DNSBL%d"DCC_XHDR_ISSPAM, i+1); else len = snprintf(dnsbl_buf, sizeof(dnsbl_buf), "DNSBL"DCC_XHDR_ISSPAM); /* log "DNSBLx-->spam" or "DNSBLx-->spam(off)" */ blen = log_ask_st_sub(buf, blen, dnsbl_buf, len, log_type != 2 && !(sws & FLTR_SW_DNSBL(i))); } else if (ask_st & ASK_ST_DNSBL_TIMEO(i)) { if (have_dnsbl_groups) len = snprintf(dnsbl_buf, sizeof(dnsbl_buf), "DNSBL%d(timeout)", i+1); else len = snprintf(dnsbl_buf, sizeof(dnsbl_buf), "DNSBL(timeout)"); blen = log_ask_st_sub(buf, blen, dnsbl_buf, len, 0); } } if (!(sws & FLTR_SW_MTA_FIRST)) { S2(ASK_ST_MTA_ISSPAM, "MTA"DCC_XHDR_ISSPAM); S2(ASK_ST_MTA_NOTSPAM, "MTA"DCC_XHDR_ISOK); } blen = log_ask_st_sub(buf, blen, dcc_progname, dcc_progname_len, 0); if (log_type == 1) { blen = log_ask_st_sub(buf, blen, "per-user", LITZ("per-user"), 0); } else if (log_type == 2) { blen = log_ask_st_sub(buf, blen, "global", LITZ("global"), 0); } blen = log_ask_st_sub(buf, blen, "\n\n", 2, 0); fnc(cp, buf, blen); if (hdr->used != 0) xhdr_write(fnc, cp, hdr->buf, hdr->used, 0); #undef S #undef S0 #undef S1 #undef S2 } /* parse -G options for DCC clients */ u_char /* 0=bad */ dcc_parse_client_grey(const char *arg) { int bits; const char *p; while (*arg != '\0') { if (dcc_ck_word_comma(&arg, "on")) { grey_on = 1; continue; } if (dcc_ck_word_comma(&arg, "off")) { grey_on = 0; continue; } if (dcc_ck_word_comma(&arg, "query")) { grey_query_only = 1; continue; } if (dcc_ck_word_comma(&arg, "noIP")) { grey_on = 1; trim_grey_ip_addr = 1; memset(&grey_ip_mask, 0, sizeof(grey_ip_mask)); continue; } if (!CLITCMP(arg, "IPmask/")) { bits = 0; for (p = arg+LITZ("IPmask/"); *p >= '0' && *p <= '9'; ++p) bits = bits*10 + *p - '0'; if (bits > 0 && bits < 128 && (*p == '\0' || *p == ',')) { arg = p; if (*p == ',') ++arg; grey_on = 1; trim_grey_ip_addr = 1; /* assume giant blocks are really IPv4 */ if (bits <= 32) bits += 128-32; dcc_bits2mask(&grey_ip_mask, bits); continue; } } return 0; } return 1; } /* sanity check the DCC server's answer */ u_char dcc_ck_grey_answer(DCC_EMSG emsg, const DCC_OP_RESP *resp) { int recv_len; recv_len = ntohs(resp->hdr.len); if (resp->hdr.op != DCC_OP_ANSWER) { dcc_pemsg(EX_UNAVAILABLE, emsg, "DCC %s: %s %*s", dcc_srvr_nm(1), dcc_hdr_op2str(0, 0, &resp->hdr), (resp->hdr.op == DCC_OP_ERROR ? (recv_len - (ISZ(resp->error) - ISZ(resp->error.msg))) : 0), resp->error.msg); return 0; } if (recv_len != sizeof(DCC_GREY_ANSWER)) { dcc_pemsg(EX_UNAVAILABLE, emsg, "greylist server %s answered with %d instead of" " %d bytes", dcc_srvr_nm(1), recv_len, ISZ(DCC_GREY_ANSWER)); return 0; } return 1; } ASK_GREY_RESULT ask_grey(DCC_EMSG emsg, DCC_CLNT_CTXT *ctxt, DCC_OPS op, /* DCC_OP_GREY_{REPORT,QUERY,WHITE} */ DCC_SUM msg_sum, /* put msg+sender+target cksum here */ DCC_SUM triple_sum, /* put greylist triple checksum here */ const DCC_GOT_CKS *cks, const DCC_SUM env_to_sum, DCC_TGTS *pembargo_num, DCC_TGTS *pearly_tgts, /* ++ report to DCC even if embargoed */ DCC_TGTS *plate_tgts) /* ++ don't report to DCC */ { MD5_CTX ctx; DCC_REPORT rpt; DCC_OP_RESP resp; DCC_CK *ck; DCC_CK_TYPES type; const DCC_GOT_SUM *g; DCC_TGTS result_tgts; int num_cks; if (cks->sums[DCC_CK_IP].type != DCC_CK_IP) { dcc_pemsg(EX_UNAVAILABLE, emsg, "IP address not available for greylisting"); memset(triple_sum, 0, sizeof(*triple_sum)); memset(msg_sum, 0, sizeof(*msg_sum)); return ASK_GREY_FAIL; } if (cks->sums[DCC_CK_ENV_FROM].type != DCC_CK_ENV_FROM) { dcc_pemsg(EX_UNAVAILABLE, emsg, "env_From not available for greylisting"); memset(triple_sum, 0, sizeof(*triple_sum)); memset(msg_sum, 0, sizeof(*msg_sum)); return ASK_GREY_FAIL; } /* Check the common checksums for whitelisting at the greylist server. * This assumes DCC_CK_GREY_TRIPLE > DCC_CK_GREY_MSG > other types */ ck = rpt.cks; num_cks = 0; for (type = 0, g = cks->sums; type <= DCC_CK_TYPE_LAST; ++type, ++g) { /* greylisting needs a body checksum, even if * it is the fake checksum for a missing body */ if (!g->rpt2srvr && type != DCC_CK_BODY) continue; ck->type = type; ck->len = sizeof(*ck); memcpy(ck->sum, g->sum, sizeof(ck->sum)); ++ck; ++num_cks; } /* include in the request the grey message checksum as the checksum * of the body, the env_From sender, and env_To target checksums */ MD5Init(&ctx); MD5Update(&ctx, cks->sums[DCC_CK_BODY].sum, sizeof(DCC_SUM)); MD5Update(&ctx, cks->sums[DCC_CK_ENV_FROM].sum, sizeof(DCC_SUM)); MD5Update(&ctx, env_to_sum, sizeof(DCC_SUM)); MD5Final(msg_sum, &ctx); ck->type = DCC_CK_GREY_MSG; ck->len = sizeof(*ck); memcpy(ck->sum, msg_sum, sizeof(ck->sum)); ++ck; ++num_cks; /* include the triple checksum of the sender, the sender's IP * address, and the target */ MD5Init(&ctx); if (trim_grey_ip_addr) { struct in6_addr addr; DCC_SUM sum; int wno; for (wno = 0; wno < 4; ++wno) { addr.s6_addr32[wno] = (cks->ip_addr.s6_addr32[wno] & grey_ip_mask.s6_addr32[wno]); } dcc_ck_ipv6(sum, &addr); MD5Update(&ctx, sum, sizeof(DCC_SUM)); } else { MD5Update(&ctx, cks->sums[DCC_CK_IP].sum, sizeof(DCC_SUM)); } MD5Update(&ctx, cks->sums[DCC_CK_ENV_FROM].sum, sizeof(DCC_SUM)); MD5Update(&ctx, env_to_sum, sizeof(DCC_SUM)); MD5Final(triple_sum, &ctx); ck->type = DCC_CK_GREY3; ck->len = sizeof(*ck); memcpy(ck->sum, triple_sum, sizeof(ck->sum)); ++num_cks; if (!dcc_clnt_op(emsg, ctxt, DCC_CLNT_FG_GREY, 0, 0, 0, &rpt.hdr, (sizeof(rpt) - sizeof(rpt.cks) + num_cks*sizeof(rpt.cks[0])), op, &resp, sizeof(resp))) { return ASK_GREY_FAIL; } if (!dcc_ck_grey_answer(emsg, &resp)) return ASK_GREY_FAIL; /* see what the greylist server had to say */ result_tgts = ntohl(resp.gans.triple); switch (result_tgts) { case DCC_TGTS_OK: /* embargo ended just now */ /* if we have previously included this target in a count of * targets sent to the DCC, then do not include it now */ if (resp.gans.msg != 0 && plate_tgts) ++*plate_tgts; if (pembargo_num) *pembargo_num = 0; return ASK_GREY_EMBARGO_END; case DCC_TGTS_TOO_MANY: /* no current embargo */ if (pembargo_num) *pembargo_num = 0; return ((resp.gans.msg != 0) ? ASK_GREY_EMBARGO_END : ASK_GREY_PASS); case DCC_TGTS_GREY_WHITE: /* whitelisted for greylisting */ if (pembargo_num) *pembargo_num = 0; return ASK_GREY_WHITE; default: /* embargoed */ /* if this is a brand new embargo, * then count this target in the DCC report */ if (resp.gans.msg == 0 && pearly_tgts) ++*pearly_tgts; if (pembargo_num) *pembargo_num = result_tgts+1; return ASK_GREY_EMBARGO; } }