Mail::MIMEDefang::Async(3)

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