Mercurial > notdcc
diff dcclib/ask.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/dcclib/ask.c Tue Mar 10 13:49:58 2009 +0100 @@ -0,0 +1,1092 @@ +/* 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; + } +}