Mail::...AskDNS(3pm)

✨ NAME ✨

AskDNS β€” form a DNS query using tag values, and look up the DNSxL lists πŸ¦‹

okay bestie, quick context: we're living in Mail::...AskDNS(3pm) land, straight out of the User Contributed Perl Documentation corner of the world, and ngl this plugin is so underrated once it clicks. the whole vibe is: you build a DNS query out of tag values, go knock on the door of the DNSxL lists, and let the replies decide whether the rule hits. iconic behavior, honestly. πŸ’…


πŸŒ€ SYNOPSIS πŸŒ€

loadplugin  Mail::SpamAssassin::Plugin::AskDNS
askdns D_IN_DWL _DKIMDOMAIN_._vouch.dwl.spamhaus.org TXT /\b(transaction|list|all)\b/

πŸ’– DESCRIPTION πŸ’–

so here's the tea β˜• β€” you hand an askdns rule a DNS query template, and the plugin goes through it swapping every tag name it finds for that tag's actual value. the moment those tag values become available, the DNS queries get launched. then, as the DNS responses trickle back in, it filters them according to the DNS resource record type you asked for, plus an optional subrule filtering expression if you gave one. and if a response actually meets those filtering conditions? boom, rule hit. πŸ’₯

if you set skip_uribl_checks to 1, AskDNS queries get fully disabled. like, switched off, no queries, nothing. 🚫

also, any host β€” or any domain part of a host β€” that matches uridnsbl_skip_domains gets ignored by default. want to bypass that check for specific rules? use the noskip tflag, like so:

askdns   MY_RULE  _AUTHORDOMAIN_.dbl.example.com  A  127.0.1.2
tflags   MY_RULE  noskip

πŸ‘‘ RULE DEFINITIONS AND PRIVILEGED SETTINGS πŸ‘‘

askdns NAME_OF_RULE query_template [rr_type [subqueryfilter]]

A query template is a string that gets expanded into the domain name you're gonna use in a DNS query. it can contain SpamAssassin tag names, and those tags get swapped out for their values to build the final query domain. 🧚

That final query domain has to actually obey the rules of DNS domains β€” like, each field is up to 63 characters long, fields are separated by dots, and the whole thing stays under 255 characters. International domain names in UTF-8 are allowed and get encoded into ASCII-compatible encoding (ACE) following IDN rules. and if the resulting query is syntactically invalid, the DNS resolver code just straight up discards it (with some info warnings, so at least it tells you). πŸ™ˆ

A trailing dot at the end is allowed, but it's redundant / carries no semantics, because SpamAssassin uses a Net::DSN::Resolver::send method to query DNS, and that method ignores any search or domain DNS resolver options. (yes, that's spelled exactly like that in the source, no I am not touching it. βœ‹) also, domain names in DNS queries are case-insensitive.

A tag name is a string of capital letters, preceded and followed by an underscore character. this syntax mirrors the add_header setting, except that tags cannot have parameters in parenthesis when used in askdns templates (there are exceptions, they're coming below). tag names can show up anywhere in the template β€” each DNS zone you query has its own opinion on how a query should be formed, so it's up to the zone.

There's one special supported tag, HEADER(), which pulls out the content of any mail header field by naming that field with optional modifiers as an argument β€” same as what header rules support. for example, _HEADER(Reply-To:addr:domain)_ gets you a trimmed domain part of an address found in a Reply-To header field. go read the Mail::SpamAssassin::Conf documentation about header rules and their modifiers for the full menu. πŸ“¬

A query template can contain any number of tag names, including none at all β€” although in the most common scenario people expect, exactly one tag name shows up per askdns rule. the tag names you list count as dependencies. askdns rules that depend on the same set of tags get grouped together, and every query in a group fires as soon as all its dependencies are met β€” meaning the last awaited tag value arrived via a set_tag() call from some other plugin or from elsewhere in the SpamAssassin code. 🎯

Launched queries from all askdns rules also get grouped, by a pair of things: the query type and the expanded query domain name. and the way that even if multiple rules produce the same type/domain pair, only one DNS query goes out β€” and the reply to it counts toward all the constituent rules. sharing is caring fr fr. 🫢

A tag can produce none, one, or multiple values. askdns rules waiting on a tag that never gets its value will never produce a DNS query. tags that produce multiple values launch multiple queries, each one expanding the template with one of those tag values. classic example: a DKIMDOMAIN tag handing you a list of signing domains, one for each valid signature in a signed message. πŸ“§

When more than one distinct tag name appears in a template, and each one might produce multiple values, a Cartesian product gets formed β€” and each tuple launches one DNS query (duplicates excluded). for example, a query template _A_._B_.example._A_.com where tag A is a list (11,22) and B is (xx,yy,zz) results in these queries:

11.xx.example.11.com
22.xx.example.22.com
11.yy.example.11.com
22.yy.example.22.com
11.zz.example.11.com
22.zz.example.22.com

A parameter rr_type following the query template is a comma-separated list of the DNS resource record (RR) types you expect. if you skip the rr_type parameter entirely, it implies an 'A'. a DNS result can bring back resource records of multiple types, but only records whose type is in your rr_type list get considered β€” anything else hanging out in the answer section of a DNS reply is ignored for that rule. a value ANY in the rr_type list matches any resource record type. and heads up: an empty DNS answer section does not match ANY. not even close. πŸ’€

The rr_type parameter isn't just a filter for RR types in the DNS answer β€” it also decides the DNS query type. if only a single RR type is specified (e.g. TXT), that's also the RR type of the query. but when more than one RR type is specified (e.g. A, AAAA, TXT), or if ANY is specified, then the DNS query type becomes ANY and the rr_type parameter only acts as a filter on the result. πŸ‘‘

Currently recognized RR types in the rr_type parameter are: ANY, A, AAAA, MX, TXT, PTR, NAPTR, NS, SOA, CERT, CNAME, DNAME, DHCID, HINFO, MINFO, RP, HIP, IPSECKEY, KX, LOC, GPOS, SRV, OPENPGPKEY, SSHFP, SPF, TLSA, URI, CAA, CSYNC.

https://www.iana.org/assignments/dns-parameters/dns-parameters.xml

The last optional parameter of a rule is a filtering expression, a.k.a. a subrule. its job is a lot like the subrule in URIDNSBL plugin rules, or in the check_rbl eval rules. the main difference? with askdns rules you don't have to manually group rules by their queried zone, because the grouping is automatic and duplicate queries get implicitly eliminated. chef's kiss. ✨

The subrule filtering parameter can be: a plain string, a regular expression, a single numerical value, a pair of numerical values, or a list of rcodes (DNS status codes of a response). if you don't give a filtering parameter at all, that means no filtering β€” so any positive DNS response (rcode=NOERROR) of the requested RR type results in a rule hit, regardless of whatever RR value came back with it. πŸŽ€

  • plain string β€” has to be wrapped in single or double quotes. for the rule to hit, the response must match the filtering string exactly, and the RR type of a response must match the query type. typical use is an exact text string for TXT queries, or an exact dotted-quad IPv4 address. if it's a TXT or SPF resource record that can return multiple character-strings (as defined in Section 3.3 of [RFC1035]), those strings get concatenated with no delimiters before the result is compared to the filtering string β€” that's per the requirements of several documents, like RFC 5518, RFC 7208, RFC 4871, and RFC 5617. examples of a plain text filtering parameter: "127.0.0.1", "transaction", 'list'.
  • regular expression β€” follows the familiar perl syntax like /.../ or m{...}, optionally followed by regexp flags (like 'i' for case-insensitivity). if the DNS response matches the requested RR type and the regular expression, the rule hits. examples: /^127\.0\.0\.\d+$/, m{\bdial up\b}i.
  • single numerical value β€” can be a decimal number, or a hexadecimal number prefixed by 0x. this numeric filtering expression is typically used with RR type-A DNS queries. the returned value (an IPv4 address) gets masked with the specified filtering value and tested to see whether it falls within a 127.0.0.0/8 network range β€” the rule hits if the result is nonzero: ((r & n) != 0) && ((r & 0xff000000) == 0x7f000000). example: 0x10.
  • pair of numerical values β€” each value being a decimal, hexadecimal, or dotted-quad, delimited by a -, specifies an IPv4 address range. a pair delimited by a / specifies an IPv4 address followed by a bitmask. again, this type of filtering expression is primarily intended for RR type-A DNS queries. the rule hits if the RR type matches and the returned IP address falls within the specified range: (r >= n1 && r <= n2), or, masked with a bitmask, matches the specified value: (r & m) == (n & m).
  • shorthand β€” a single dotted-quad value is equivalent to an n-n form, i.e. it must match the returned value exactly, with all its bits.

Some typical examples of a numeric filtering parameter are: 127.0.1.2, 127.0.1.20-127.0.1.39, 127.0.1.0/255.255.255.0, 0.0.0.16/0.0.0.16, 0x10/0x10, 16, 0x10.

  • rcode list β€” lastly, the filtering parameter can be a comma-separated list of DNS status codes (rcode), enclosed in square brackets. rcodes can be written as their numeric decimal values (0=NOERROR, 3=NXDOMAIN, ...) or by their names. see https://www.iana.org/assignments/dns-parameters for the list of names. when you're testing for an rcode where the rcode is nonzero, the RR type parameter is ignored as a filter, since there's typically no answer section in a DNS reply when the rcode is indicating an error. example: [NXDOMAIN], or [FormErr,ServFail,4,5].

πŸ“ NOTES πŸ“

DNS timeout can be set with the rbl_timeout option. πŸ•°οΈ peek at the Mail::SpamAssassin::Conf POD for the deets on rbl_timeout.


okay that's the whole page, we ate and left no crumbs πŸ’– this was Mail::...AskDNS(3pm) from the User Contributed Perl Documentation, brought to you by perl v5.40.1 and dated 2026-07-23. go forth and query some DNSxL lists, bestie. periodt. πŸŒΈπŸ‘‘

πŸ“¦ where this page comes from