Email-Abuse-Investigator
view release on metacpan or search on metacpan
Use `report()` for human review or ticketing systems. Use
`abuse_report_text()` for sending to ISP abuse desks.
### Usage
print $analyser->report();
open my $fh, '>', 'report.txt' or croak "Cannot open: $!";
print $fh $analyser->report();
close $fh;
### Arguments
None. `parse_email()` must have been called first.
### Returns
A plain scalar string, newline-terminated, Unix line endings. Never empty
or undef.
### Side Effects
Triggers all analysis methods if not already cached.
### Notes
The report is idempotent: calling it multiple times on the same object
always returns an identical string. All user-derived content is sanitised
before output.
### API Specification
#### Input
[]
#### Output
{ type => 'string' }
## header\_value( $name )
Returns the value of the first occurrence of a named header field, or
`undef` if the header is absent. The name comparison is case-insensitive.
### Usage
my $subj = $analyser->header_value('Subject');
my $from = $analyser->header_value('From');
my $msgid = $analyser->header_value('Message-ID');
### Arguments
- `$name` (string, required)
The header field name, e.g. `'Subject'`, `'From'`, `'X-Mailer'`.
Comparison is case-insensitive.
### Returns
The raw header value string (not decoded), or `undef` if the named header
is not present. When the same header appears more than once, only the value
of the first occurrence is returned.
### Side Effects
None. Header data is pre-parsed during `parse_email()`.
### Notes
Header values are returned verbatim, including any MIME encoded-word sequences
(`=?charset?B/Q?...?=`). Pass the result through `_decode_mime_words()`
internally if human-readable output is needed.
### API Specification
#### Input
{
name => { type => 'string', required => 1 },
}
#### Output
{ type => [ 'string', 'undef' ] }
### Messages
None -- returns `undef` on a missing header, never throws.
# ALGORITHM: DOMAIN INTELLIGENCE PIPELINE
For each unique non-infrastructure domain found in the email, the module
runs the following pipeline:
Domain name
|
+-- A/AAAA record --> web hosting IP --> RDAP --> org + abuse contact
|
+-- MX record --> mail server hostname --> A --> RDAP --> org + abuse
|
+-- NS record --> nameserver hostname --> A --> RDAP --> org + abuse
|
+-- WHOIS (TLD whois server via IANA referral)
+-- Registrar name + abuse contact
+-- Creation date (-> recently-registered flag if < 180 days)
+-- Expiry date (-> expires-soon or expired flags)
Domains are collected from:
From:/Reply-To:/Sender:/Return-Path: headers
DKIM-Signature: d= (signing domain)
List-Unsubscribe: (ESP / bulk sender domain)
Message-ID: (often reveals real sending platform)
mailto: links and bare addresses in the body
# CACHING
Two levels of caching are used:
- Per-message cache (`$self->{_domain_info}`)
# SUPPORT
This module is provided as-is without any warranty.
Please report any bugs or feature requests to `bug-email-abuse-investigator at rt.cpan.org`,
or through the web interface at
[http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Email-Abuse-Investigator](http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Email-Abuse-Investigator).
I will be notified, and then you'll
automatically be notified of progress on your bug as I make changes.
You can find documentation for this module with the perldoc command.
perldoc Email::Abuse::Investigator
You can also look for information at:
- MetaCPAN
[https://metacpan.org/dist/Email-Abuse-Investigator](https://metacpan.org/dist/Email-Abuse-Investigator)
- RT: CPAN's request tracker
[https://rt.cpan.org/NoAuth/Bugs.html?Dist=Email-Abuse-Investigator](https://rt.cpan.org/NoAuth/Bugs.html?Dist=Email-Abuse-Investigator)
- CPAN Testers' Matrix
[http://matrix.cpantesters.org/?dist=Email-Abuse-Investigator](http://matrix.cpantesters.org/?dist=Email-Abuse-Investigator)
- CPAN Testers Dependencies
[http://deps.cpantesters.org/?module=Email-Abuse-Investigator](http://deps.cpantesters.org/?module=Email-Abuse-Investigator)
# REQUIRED MODULES
The following modules are mandatory:
Readonly::Values::Months
Socket (core since Perl 5)
IO::Socket::INET (core since Perl 5)
MIME::QuotedPrint (core since Perl 5.8)
MIME::Base64 (core since Perl 5.8)
The following are optional but strongly recommended:
Net::DNS -- enables MX, NS, AAAA record lookups
LWP::UserAgent -- enables RDAP (faster and richer than raw WHOIS)
and following redirect chains for URL shorteners
and cloud-storage redirect cloakers
LWP::ConnCache -- enables HTTP connection reuse (used with LWP::UserAgent)
HTML::LinkExtor -- enables structural HTML link extraction with entity decoding
CHI -- enables cross-message IP/domain result caching
IO::Socket::IP -- enables IPv6 WHOIS connections
Domain::PublicSuffix -- enables accurate eTLD+1 domain normalisation
AnyEvent::DNS -- enables parallel DNS resolution for multiple URL hosts
# LIMITATIONS
- No charset conversion
Body text is stored as raw bytes. Non-ASCII content (UTF-8, Latin-1,
ISO-2022-JP, etc.) is not decoded to Perl's internal Unicode representation.
URL and domain extraction from non-ASCII bodies may miss or misparse content.
Use `Email::MIME` if full charset support is needed.
- Hand-rolled MIME parser
The built-in MIME parser handles common cases but is not a conforming
implementation of RFC 2045/2046. It silently drops parts it cannot decode,
does not handle `message/rfc822` attachments, and does not parse
`Content-Disposition` filenames. Replace with `Email::MIME` or
`MIME::Entity` for production use with untrusted input.
- IPv4-only CIDR matching for trusted\_relays
`_ip_in_cidr()` and the `trusted_relays` constructor argument only support
IPv4 CIDR notation. IPv6 trusted relay entries are accepted but silently
never match.
- WHOIS rate-limiting not handled
`_raw_whois()` does not retry on rate-limit responses (typically a
"quota exceeded" reply). Under high-volume processing the module will
silently return empty enrichment data for affected IPs and domains.
- Not thread-safe
The class-level `$_cache` variable and the optional-module `$HAS_*` flags
are shared across all threads. Create a separate object per thread and do
not share objects across threads.
- DMARC policy not fetched
The module reads the `Authentication-Results: dmarc=` result from the
message headers but does not perform live `_dmarc.domain` TXT record
lookups. A missing DMARC result in the headers is not independently flagged.
- `abuse_contacts()` routes duplicated in `form_contacts()`
Both methods iterate the same six discovery routes independently. Any new
discovery route must be added to both. A future refactor should share a
single routing pass.
- CHI cache is a class-level mutable global
The cross-message cache is shared across all instances in the process.
Tests that populate the cache will affect subsequent tests. Pass the cache
in via `new()` (not currently supported) to enable proper isolation.
# FORMAL SPECIFICATION
## new
-- Z notation (simplified)
new == [
timeout : N;
trusted_relays : seq STRING;
verbose : BOOL;
_raw : STRING;
_headers : seq (STRING x STRING);
_origin? : IP_INFO | undefined;
_urls? : seq URL_INFO | undefined;
( run in 0.738 second using v1.01-cache-2.11-cpan-9169edd2b0e )