blob: 55f1c892d270eb5b06b6219b5d424fd4c6dc7aa4 [file]
#!/usr/bin/perl -T
use lib '.'; use lib 't';
use SATest; sa_t_init("handler_ics");
use Test::More;
# ---------------------------------------------------------------------------
# End-to-end test of Mail::SpamAssassin::Handler::ICS.
#
# handler_ics attaches the same invite twice -- once inline as text/calendar and
# once as a base64 application/ics file (as Google Calendar does) -- carrying a
# docusign phishing lure in the event SUMMARY/DESCRIPTION, phishing links in
# URL/LOCATION, two ATTENDEE properties, and a DTSTART with non-zero seconds. We
# confirm:
# * icstext rules match the event text (and only the ICS, not the plain body);
# * a plain body rule matches the event text too -- the handler renders it into
# the body via set_rendered (SA otherwise skips text/calendar for bug 4843);
# * check_ics_attendee_count() sees exactly two attendees -- the duplicate copy
# is deduped by UID, so the count is 2 and not 4;
# * check_ics_random_start_time() fires on the odd (non-zero seconds) DTSTART;
# * URIs from the invite reach the URI detail list under type 'ics';
# * the original text/plain body is preserved.
#
# A second message (handler_ics_html) checks that a link hidden in an HTML-typed
# property (X-ALT-DESC;FMTTYPE=text/html) -- with no plain URL: property -- reaches
# the URI detail list: the ICS handler hands the HTML to the HTML handler as a
# child part, and that synthetic part's links are harvested.
#
# The handler is pure Perl (no external binary), so this test runs everywhere.
plan tests => 15;
tstpre ("
loadhandler Mail::SpamAssassin::Handler::ICS
loadhandler Mail::SpamAssassin::Handler::HTML
");
tstlocalrules ('
icstext ICS_SENTINEL /ICSSENTINEL/
score ICS_SENTINEL 1.0
describe ICS_SENTINEL ICS event text reached the ICS handler
icstext ICS_SUSP /\b(docusign|secure link)\b/i
score ICS_SUSP 1.0
describe ICS_SUSP suspicious phishing terms in ICS event text
body ICS_ATTENDEES eval:check_ics_attendee_count(\'2\',\'2\')
score ICS_ATTENDEES 1.0
describe ICS_ATTENDEES invite has exactly two attendees (duplicate deduped by UID)
body ICS_DTSEC eval:check_ics_random_start_time()
score ICS_DTSEC 1.0
describe ICS_DTSEC DTSTART has a non-zero seconds component
uri-detail ICS_LINK type =~ /^ics$/ raw =~ /phish\.example/
score ICS_LINK 1.0
describe ICS_LINK a link inside the invite reached the URI list
body ICS_ORIG /ORIGINAL_BODY_MARKER/
score ICS_ORIG 1.0
describe ICS_ORIG original body preserved
body ICS_RENDERED /confirm your docusign invoice/
score ICS_RENDERED 1.0
describe ICS_RENDERED event text rendered into the body via set_rendered
body ICS_INLINE_IMG eval:check_ics_event_prop(\'ATTACH\',\'ENCODING=BASE64\')
score ICS_INLINE_IMG 1.0
describe ICS_INLINE_IMG invite carries an inline base64 attachment
body ICS_ATTACH_DELIM eval:check_ics_event_prop(\'ATTACH\',\'/ENCODING=BASE64/\')
score ICS_ATTACH_DELIM 1.0
describe ICS_ATTACH_DELIM delimited regex form matches (delimiters stripped)
uri-detail ICS_HTML_URI raw =~ /hidden-in-html/
score ICS_HTML_URI 1.0
describe ICS_HTML_URI a link hidden in an HTML property reached the URI list
body ICS_HTML_TEXT /ICSHTMLBODY/
score ICS_HTML_TEXT 1.0
describe ICS_HTML_TEXT the HTML property visible text was rendered into the body
body ICS_HTML_RAWTAG /<p>/
score ICS_HTML_RAWTAG 1.0
describe ICS_HTML_RAWTAG raw HTML markup leaked into body rules (should NOT fire)
icstext ICS_HTML_ICSTEXT /ICSHTMLBODY/
score ICS_HTML_ICSTEXT 1.0
describe ICS_HTML_ICSTEXT HTML property rendered text is visible to icstext rules
');
%patterns = (
' 1.0 ICS_SENTINEL ', 'ics_text',
' 1.0 ICS_SUSP ', 'ics_suspicious_text',
' 1.0 ICS_ATTENDEES ', 'ics_attendee_count',
' 1.0 ICS_DTSEC ', 'ics_dtstart_nonzero_seconds',
' 1.0 ICS_LINK ', 'ics_uri_detail',
' 1.0 ICS_ORIG ', 'original_body_preserved',
' 1.0 ICS_RENDERED ', 'ics_rendered_into_body',
' 1.0 ICS_INLINE_IMG ','ics_event_prop_matches_raw_param',
' 1.0 ICS_ATTACH_DELIM ','ics_event_prop_delimited_regex',
);
ok (sarun ("-L -t < data/nice/handler_ics", \&patterns_run_cb));
ok_all_patterns();
# handler_ics_html carries the same HTML in two properties -- a bare DESCRIPTION
# (no FMTTYPE, detected as HTML by its markup) and a duplicate X-ALT-DESC;
# FMTTYPE=text/html -- with the payload link only in an <a href>, no plain URL:
# property. The ICS handler hands the HTML to the HTML handler as a child part
# (the duplicate collapses via the framework's content fingerprint). We confirm:
# * the link reaches the URI detail list (synthetic-part URI extraction);
# * the visible text is rendered into the body -- ICS_HTML_TEXT fires;
# * the raw markup does NOT leak into body rules -- ICS_HTML_RAWTAG must not fire.
# Requires the HTML handler (tstpre above).
%patterns = (
' 1.0 ICS_HTML_URI ', 'ics_html_property_uri',
' 1.0 ICS_HTML_TEXT ', 'ics_html_rendered_text',
' 1.0 ICS_HTML_ICSTEXT ', 'ics_html_rendered_text_icstext',
);
%anti_patterns = (
' 1.0 ICS_HTML_RAWTAG ', 'ics_html_raw_markup_leaked',
);
ok (sarun ("-L -t < data/nice/handler_ics_html", \&patterns_run_cb));
ok_all_patterns();