NAME

Mail::MIMEDefang::Async - Asynchronous I/O engine for MIMEDefang external checks

DESCRIPTION

Mail::MIMEDefang::Async provides concurrent DNS, socket-based, and process-based checks for use in MIMEDefang filter callbacks. All checks share a single AnyEvent event loop and the call blocks until every check has completed or the global timeout fires.

Requires the optional modules AnyEvent, AnyEvent::DNS, AnyEvent::Socket, and AnyEvent::Util from CPAN.

METHODS

md_async_init(%opts)

Initialise the async engine. Call once per process (e.g. at the top of your mimedefang-filter, after use Mail::MIMEDefang::Async).

Options:

max_concurrency  Max parallel checks in flight (default: 10)
global_timeout   Hard wall-clock limit for the whole batch (default: 10s)
dns_timeout      Per-DNS-query timeout (default: 5s)
socket_timeout   Per-socket-connection timeout (default: 5s)
md_async_run_checks(\@checks)

Run a list of check descriptors concurrently. Blocks until all checks complete or the global timeout fires. Returns { results => \%r, errors => \%e }.

Each check is a hashref with name, type ('dns', 'socket', or 'process'), and args. Use Mail::MIMEDefang::Async::Checks to build them.

md_async_relay_is_blacklisted($addr, $zone)

Async drop-in replacement for relay_is_blacklisted from Mail::MIMEDefang::Net.

Looks up reverse_ip($addr).$zone as a DNS A record. Returns the first matching IP string on a listing, 0 if not listed, or undef on error/timeout.

md_async_email_is_blacklisted($email, $zone, $hash_type)

Async drop-in replacement for email_is_blacklisted from Mail::MIMEDefang::Net.

Hashes $email using MD5 or SHA1 (controlled by $hash_type), then looks up $hash.$zone. Returns the first matching IP string, 0 if not listed, or undef on error/timeout.

md_async_spf_verify($mail, $relayip, $helo)

Async-enhanced replacement for md_spf_verify from Mail::MIMEDefang::SPF.

Pre-fetches the sender domain's SPF TXT record via the async engine, then evaluates it using Mail::SPF synchronously. Returns the same values as md_spf_verify. Returns undef immediately if Mail::SPF is not installed.

md_async_dmarc_verify($domain)

Async replacement for md_get_dmarc_record from Mail::MIMEDefang::Net.

Performs an async TXT lookup on _dmarc.$domain and returns the raw DMARC policy string, or undef if none exists. Applies the same parent-domain fallback logic as the original.

md_async_message_contains_virus_clamd($clamd_sock)

Async replacement for message_contains_virus_clamd from Mail::MIMEDefang::Antivirus.

Sends a SCAN $CWD/Work command to the clamd daemon over a socket and interprets the response. $clamd_sock may be a Unix socket path or a host:port string; defaults to $ClamdSock.

Note: the SCAN command instructs clamd to open the path on its own filesystem. This only works when clamd runs on the same host as MIMEDefang. For remote clamd, use md_async_message_contains_virus_clamdscan instead.

Returns the standard virus-scanner triplet ($code, $category, $action):

(0,   'ok',             'ok')          clean
(1,   'virus',          'quarantine')  virus found
(999, 'cannot-execute', 'tempfail')    cannot connect
(999, 'swerr',          'tempfail')    scan error
md_async_message_contains_virus_clamdscan($conf)

Async replacement for message_contains_virus_clamdscan from Mail::MIMEDefang::Antivirus.

Spawns clamdscan --stream which uses the INSTREAM wire protocol, streaming file data to clamd rather than asking it to open a local path. This makes it suitable for both a local Unix-socket clamd and a remote TCP clamd. $conf is the path to clamd.conf; defaults to $Features{'Path:CLAMDCONF'}. The socket clamd listens on (Unix or TCP) is determined by the LocalSocket / TCPAddr + TCPSocket directives in that config file.

Returns the standard virus-scanner triplet ($code, $category, $action):

(0,   'ok',             'ok')           clean
(1,   'virus',          'quarantine')   virus found
(1,   'not-installed',  'tempfail')     clamdscan binary not found
(999, 'cannot-execute', 'tempfail')     could not spawn / timeout
(999, 'swerr',          'tempfail')     scan error (clamdscan exit >= 2)
md_async_spamc_check(%args)

Async replacement for md_spamc_check from Mail::MIMEDefang::Antispam.

Sends the message to spamd using the raw SPAMC wire protocol over an async socket, without requiring Mail::SpamAssassin::Client.

Args: host (default 127.0.0.1), port (default 783), user (default current user), timeout (default 30s).

Returns the same four-element list as md_spamc_check: ($score, $threshold, $report, $isspam), or undef on failure.

md_async_spam_assassin_check()

Drop-in replacement for spam_assassin_check from Mail::MIMEDefang::Antispam.

Runs SpamAssassin in-process (no spamd required), reading ./INPUTMSG. Returns the same four-element list: ($hits, $required_hits, $tests_list, $full_report), or undef when SpamAssassin is not installed or INPUTMSG cannot be read.

For a network check against a running spamd, use md_async_spamc_check instead.

md_async_rspamd_check($uri)

Async replacement for rspamd_check from Mail::MIMEDefang::Antispam.

POSTs the message to the Rspamd HTTP API at $uri/checkv2 using a raw HTTP/1.0 request over an async TCP socket (no LWP::UserAgent required). Requires JSON::PP (Perl core since 5.14) for response parsing.

$uri defaults to http://127.0.0.1:11333.

Returns the same six-element list as rspamd_check: ($hits, $required_score, $tests, $report, $action, $is_spam), or (0, 0, '', '', 'soft reject', 'false') on connection failure.

SYNOPSIS

use Mail::MIMEDefang::Async;
use Mail::MIMEDefang::Async::Checks qw(...);
use Mail::MIMEDefang::Async::Results qw(...);

md_async_init(max_concurrency => 8, global_timeout => 10);

my $result = md_async_run_checks([
    md_async_check_dnsbl(ip => $client_ip, zone => 'zen.spamhaus.org'),
    md_async_check_rdns(ip => $client_ip),
]);

# Drop-in replacements
my $listed                    = md_async_relay_is_blacklisted($client_ip, 'zen.spamhaus.org');
my $dmarc                     = md_async_dmarc_verify($sender_domain);
my ($code, $cat, $act)        = md_async_message_contains_virus();
my ($score, $thr, $rep, $spam) = md_async_spamc_check();
my ($hits, $req, $sym, $rpt, $action, $spam) = md_async_rspamd_check();

SEE ALSO

Mail::MIMEDefang::Async::Checks, Mail::MIMEDefang::Async::Results, Mail::MIMEDefang::Net, Mail::MIMEDefang::SPF