# HG changeset patch # User Carl Byington # Date 1488330127 28800 # Node ID 879a470c6ac359afe532263881ead60a9c61e7c7 # Parent 0495e767bfb7dee0528c8faa912f29f3812ecac4 fetch spf txt records for required dkim signers diff -r 0495e767bfb7 -r 879a470c6ac3 ChangeLog --- a/ChangeLog Mon Feb 20 08:43:41 2017 -0800 +++ b/ChangeLog Tue Feb 28 17:02:07 2017 -0800 @@ -1,7 +1,10 @@ -6.50 2017-02-18 +6.51 2017-02-28 + fetch spf txt records for required dkim signers + +6.50 2017-02-22 reject if dkim signer is listed on surbl -6.49 2017-02-07 +6.49 2017-02-08 RHEL7 systemd and /var/run on tmpfs. 6.48 2016-12-17 diff -r 0495e767bfb7 -r 879a470c6ac3 NEWS --- a/NEWS Mon Feb 20 08:43:41 2017 -0800 +++ b/NEWS Tue Feb 28 17:02:07 2017 -0800 @@ -1,5 +1,6 @@ -6.50 2017-02-18 reject if dkim signer is listed on surbl -6.49 2017-02-07 RHEL7 systemd and /var/run on tmpfs +6.51 2017-02-28 fetch spf txt records for required dkim signers +6.50 2017-02-22 reject if dkim signer is listed on surbl +6.49 2017-02-08 RHEL7 systemd and /var/run on tmpfs 6.48 2016-12-17 Add dkim white/black listing 6.47 2016-09-21 Better smtp verify logging 6.46 2016-09-19 Enable smtp verify logging diff -r 0495e767bfb7 -r 879a470c6ac3 configure.in --- a/configure.in Mon Feb 20 08:43:41 2017 -0800 +++ b/configure.in Tue Feb 28 17:02:07 2017 -0800 @@ -1,6 +1,6 @@ AC_PREREQ(2.59) -AC_INIT(dnsbl,6.50,carl@five-ten-sg.com) +AC_INIT(dnsbl,6.51,carl@five-ten-sg.com) AC_CONFIG_SRCDIR([config.h.in]) AC_CONFIG_HEADER([config.h]) AC_CONFIG_MACRO_DIR([m4]) diff -r 0495e767bfb7 -r 879a470c6ac3 dnsbl.spec.in --- a/dnsbl.spec.in Mon Feb 20 08:43:41 2017 -0800 +++ b/dnsbl.spec.in Tue Feb 28 17:02:07 2017 -0800 @@ -25,7 +25,7 @@ %endif Requires: sendmail >= 8.12.1 Requires: sendmail-cf -Requires: spamassassin +Requires: spamassassin, dcc %if "%{?dist}" == ".el7" Requires(pre): systemd-sysv, shadow-utils Requires(post): systemd-units @@ -143,7 +143,8 @@ %docdir %{_datadir}/doc/%{name}-%{version} %{_datadir}/doc/%{name}-%{version} %config(noreplace) %attr(0750,%{name},root) %{_sysconfdir}/%{name} -%config(noreplace) %attr(0644,root,root) /var/dcc/userdirs/dnsblnogrey/whiteclnt +%dir %attr(0750,dcc,dcc) /var/dcc/userdirs/dnsblnogrey +%config(noreplace) %attr(0644,dcc,dcc) /var/dcc/userdirs/dnsblnogrey/whiteclnt %if "%{?dist}" == ".el7" %{_prefix}/lib/tmpfiles.d/%{name}.conf %{_unitdir}/%{name}.service @@ -154,8 +155,12 @@ %changelog -* Sat Feb 18 2017 Carl Byington - 6.50-1 +* Tue Feb 28 2017 Carl Byington - 6.51-1 +- fetch spf txt records for required dkim signers. + +* Wed Feb 22 2017 Carl Byington - 6.50-1 - reject if dkim signer is listed on surbl +- require dcc, this package owns /var/dcc/userdirs/dnsblnogrey * Wed Feb 08 2017 Carl Byington - 6.49-1 - RHEL7 systemd and /var/run is on tmpfs diff -r 0495e767bfb7 -r 879a470c6ac3 src/context.cpp --- a/src/context.cpp Mon Feb 20 08:43:41 2017 -0800 +++ b/src/context.cpp Tue Feb 28 17:02:07 2017 -0800 @@ -1122,7 +1122,20 @@ } -const char *CONTEXT::acceptable_content(recorder &memory, int score, int bulk, const char *queueid, string_set &signers, const char *from, string& msg) { +#ifdef NS_PACKETSZ +bool CONTEXT::resolve_spf(const char *from, int32_t ip, mlfiPriv *priv) +{ + char buf[maxlen]; + dns_interface(*priv, from, ns_t_txt, false, NULL, buf, maxlen); + if (*buf) { + log(priv->queueid, "found txt record %s", buf); + } + return false; +} +#endif + + +const char *CONTEXT::acceptable_content(recorder &memory, int score, int bulk, const char *queueid, string_set &signers, const char *from, mlfiPriv *priv, string& msg) { DKIMP dk = find_dkim_from(from); for (string_set::iterator s=signers.begin(); s!=signers.end(); s++) { @@ -1156,6 +1169,13 @@ } } if (st == token_require_signed) { +#ifdef NS_PACKETSZ + // not signed by the required signers, but maybe passes strong spf check + if (resolve_spf(from, priv->ip, priv) { + log(queueid, "spf pass for %s rather than required dkim signer", from); + return token_white; + } +#endif char buf[maxlen]; snprintf(buf, sizeof(buf), "Mail rejected - not dkim signed by %s", dk->signer); msg = string(buf); diff -r 0495e767bfb7 -r 879a470c6ac3 src/context.h --- a/src/context.h Mon Feb 20 08:43:41 2017 -0800 +++ b/src/context.h Tue Feb 28 17:02:07 2017 -0800 @@ -23,6 +23,7 @@ class WHITELISTER; class DELAYWHITE; class recorder; +class mlfiPriv; typedef map string_map; typedef set int_set; @@ -314,7 +315,7 @@ void log(const char *queueid, const char *msg, const char *v); bool in_signing_set(const char *s, const char *signers); - const char *acceptable_content(recorder &memory, int score, int bulk, const char *queueid, string_set &signers, const char *from, string& msg); + const char *acceptable_content(recorder &memory, int score, int bulk, const char *queueid, string_set &signers, const char *from, mlfiPriv *priv, string& msg); bool ignore_host(const char *host); void dump(bool isdefault, bool &spamass, int level = 0); diff -r 0495e767bfb7 -r 879a470c6ac3 src/dnsbl.cpp --- a/src/dnsbl.cpp Mon Feb 20 08:43:41 2017 -0800 +++ b/src/dnsbl.cpp Tue Feb 28 17:02:07 2017 -0800 @@ -302,13 +302,15 @@ //////////////////////////////////////////////// -// ask a dns question and get an A record answer in network byte order -// we don't try very hard, just using the default resolver retry settings. +// Ask a dns question and get an A record answer in network byte order. +// We don't try very hard, just using the default resolver retry settings. // If we cannot get an answer, we just accept the mail. +// If the qtype is ns_t_txt, the answer is placed in txt_answer which +// must be non-null, and the return value can be ignored. +// If the qtype is ns_t_a, the ip address is returned in network byte order. // -// -uint32_t dns_interface(mlfiPriv &priv, const char *question, bool maybe_ip, ns_map *nameservers); -uint32_t dns_interface(mlfiPriv &priv, const char *question, bool maybe_ip, ns_map *nameservers) { +uint32_t dns_interface(mlfiPriv &priv, const char *question, int qtype, bool maybe_ip = false, ns_map *nameservers = NULL, char *txt_answer = NULL, size_t txt_size = 0); +uint32_t dns_interface(mlfiPriv &priv, const char *question, int qtype, bool maybe_ip, ns_map *nameservers, char *txt_answer, size_t txt_size) { // tell sendmail we are still working #if _FFR_SMFI_PROGRESS if (priv.eom) smfi_progress(priv.ctx); @@ -318,13 +320,15 @@ // milter thread is talking over its own socket to a separate resolver // process, which does the actual dns resolution. if (priv.err) return 0; // cannot ask more questions on this socket. - if (maybe_ip) { + if (maybe_ip && (qtype == ns_t_a)) { // might be a bare ip address, try this first to avoid dns lookups that may not be needed in_addr ip; if (inet_aton(question, &ip)) { return ip.s_addr; } } + int8_t qt = qtype; + priv.my_write((const char *)&qt, 1);// write the query type size_t n = strlen(question); if (question[n-1] == '.') { priv.my_write(question, n+1); // write the question including the null terminator @@ -405,14 +409,39 @@ } } int rrnum = 0; + if (qtype == ns_t_a) { while (ns_parserr(&handle, ns_s_an, rrnum++, &rr) == 0) { - if (ns_rr_type(rr) == ns_t_a) { + if (ns_rr_type(rr) == qtype) { uint32_t address; memcpy(&address, ns_rr_rdata(rr), sizeof(address)); ret_address = address; } } } + if ((qtype == ns_t_txt) && (txt_answer) && (txt_size > 5)) { + txt_size--; // allow room for terminating null; + txt_answer[0] = '\0'; // return null string if there are no answers + while (ns_parserr(&handle, ns_s_an, rrnum++, &rr) == 0) { + size_t offset = 0; + if (ns_rr_type(rr) == qtype) { + size_t rdlen = ns_rr_rdlen(rr); + const unsigned char *rdata = ns_rr_rdata(rr); + while ((offset < txt_size) && rdlen) { + size_t slen = size_t(*(rdata++)); + rdlen--; + size_t m = min(slen, rdlen); + m = min(m, txt_size-offset); + memcpy(txt_answer+offset, rdata, m); + offset += m; + rdata += m; + rdlen -= m; + } + } + txt_answer[offset] = '\0'; // trailing null + if (strcasecmp(txt_answer, "v=spf1 ") == 0) break; + } + } + } pthread_mutex_unlock(&resolve_mutex); #ifdef RESOLVER_DEBUG snprintf(text, sizeof(text), "dns_interface() found ip %d", ret_address); @@ -436,7 +465,7 @@ bool uriblookup(mlfiPriv &priv, string_set &hosts, const char *hostname, const char *&found) { char buf[maxlen]; snprintf(buf, sizeof(buf), "%s.%s.", hostname, priv.uribl_suffix); - uint32_t ip = ntohl(dns_interface(priv, buf, false, NULL)); + uint32_t ip = ntohl(dns_interface(priv, buf, ns_t_a)); if (ip and (ip != 0x7f000000)) { if (debug_syslog > 2) { char tmp[maxlen]; @@ -721,7 +750,6 @@ } const char *mlfiPriv::check_uribl_signers() { - const char *st; if (uribl_suffix) { for (string_set::iterator s=dkim_signers.begin(); s!=dkim_signers.end(); s++) { if (check_uribl(*this, hosts_uribl, *s, host_uribl)) return host_uribl; @@ -853,16 +881,16 @@ #ifdef NS_PACKETSZ #ifdef RESOLVER_DEBUG char text[1000]; - snprintf(text, sizeof(text), "process_resolver_requests() has a question %s", question); + snprintf(text, sizeof(text), "process_resolver_requests() has a question %s qtype %d", question+1, int8_t(question[0])); my_syslog(text); #endif - int res_result = res_search(question, ns_c_in, ns_t_a, glom.answer, sizeof(glom.answer)); + int res_result = res_search(question+1, ns_c_in, int8_t(question[0]), glom.answer, sizeof(glom.answer)); if (res_result < 0) glom.length = 0; // represent all errors as zero length answers else glom.length = (size_t)res_result; #else glom.length = sizeof(glom.answer); glom.answer = 0; - struct hostent *host = gethostbyname(question); + struct hostent *host = gethostbyname(question+1); if (host && (host->h_addrtype == AF_INET)) { memcpy(&glom.answer, host->h_addr, sizeof(glom.answer)); } @@ -913,7 +941,7 @@ #endif snprintf(question, sizeof(question), "%u.%u.%u.%u.%s.", src[3], src[2], src[1], src[0], suffix); // ask the question, if we get an A record it implies a blacklisted ip address - return dns_interface(priv, question, false, NULL); + return dns_interface(priv, question, ns_t_a); } @@ -1028,7 +1056,7 @@ } } count++; - ip = dns_interface(priv, host, true, &nameservers); + ip = dns_interface(priv, host, ns_t_a, true, &nameservers); if (debug_syslog > 2) { char buf[maxlen]; if (ip) { @@ -1066,7 +1094,7 @@ if ((count > limit) && (limit > 0)) return false; // too many name servers to check them all host = (*i).first; // a transient reference that needs to be replaced before we return it ip = (*i).second; - if (!ip) ip = dns_interface(priv, host, false, NULL); + if (!ip) ip = dns_interface(priv, host, ns_t_a); if (debug_syslog > 2) { char buf[maxlen]; if (ip) { @@ -1655,7 +1683,7 @@ for (context_map::iterator i=priv.env_to.begin(); i!=priv.env_to.end(); i++) { const char *rcpt = (*i).first; CONTEXT &con = *((*i).second); - const char *st = con.acceptable_content(*priv.memory, score, bulk, priv.queueid, priv.dkim_signers, priv.fromaddr, msg); + const char *st = con.acceptable_content(*priv.memory, score, bulk, priv.queueid, priv.dkim_signers, priv.fromaddr, &priv, msg); if (st == token_black) { // bad html tags or excessive hosts or // high spam assassin score or dcc bulk threshold exceedeed