fix edge cases for previous commit
[infanticide.git] / infanticide.pm
1 #!/usr/bin/perl
2
3 # This is a SpamAssassin module that kills mail from newly-born domains.
4 # Copyright (c) 2022-2023 by Art Cancro
5 # Use, duplication, or disclosure are subject to the terms of the GNU General Public License v3.
6
7
8 package infanticide;
9 use Mail::SpamAssassin::Plugin;
10 use Sys::Syslog;
11 use Sys::Syslog qw(:standard :macros);
12 use HTTP::Date;
13 use POSIX;
14
15
16 our @ISA = qw(Mail::SpamAssassin::Plugin);
17 my $counter = 100;
18 sub new {
19         my ($class, $mailsa) = @_;
20
21         # the usual perlobj boilerplate to create a subclass object
22         $class = ref($class) || $class;
23         my $self = $class->SUPER::new($mailsa);
24         bless ($self, $class);
25  
26         # syslog is fun
27         syslog('info', 'Initializing INFANTICIDE for SpamAssassin (c) 2022-2023 by Art Cancro');
28
29         # Register evaluation rules
30         $self->register_eval_rule ("check_for_disposable_domain");
31
32         # and return the new plugin object
33         return $self;
34 }
35
36
37 sub trim {
38         my $s = shift; $s =~ s/^\s+|\s+$//g; return $s
39 }
40
41
42 sub infanticide_get_creation_date {
43         my $domain_to_test = $_[0];
44         my $cDate = "";
45
46         open my $fh, "-|", "whois", $domain_to_test     or return("");
47         my @lines = <$fh>                               or return("");
48         close $fh                                       or return("");
49         foreach(@lines) {
50                 if (index($_, "Creation Date:") != -1) {
51                         my @arr = split("Creation Date:", $_);
52                         $cDate = trim($arr[1]);
53                         # 2017-06-02T13:15:42Z
54                 }
55         }
56         if ($cDate eq "") {
57                 syslog('debug', "infanticide: no creation date for " . $domain_to_test);
58
59                 # If the FQDN is made up of more than two components (for example, mail.spammers.com)
60                 # try stripping components until it's down to two
61                 $dots = ($domain_to_test =~ tr/.//);
62                 if ($dots > 1) {
63                         return(infanticide_get_creation_date(substr $domain_to_test, index($domain_to_test, ".")+1));
64                 }
65
66         }
67         return($cDate);
68 }
69
70
71 sub infanticide_get_creation_date_recursive {
72         my $domain_to_test = $_[0];
73         my $cDate = infanticide_get_creation_date($domain_to_test);
74         if ($cDate ne "") {
75                 return($cDate);
76         }
77
78         $dots = ($domain_to_test =~ tr/.//);
79         if ($dots < 2) {
80                 return("");
81         }
82
83         return(infanticide_get_creation_date_recursive(substr $domain_to_test, index($domain_to_test, ".")+1));
84 }
85
86
87
88 sub check_for_disposable_domain() {
89         $counter = $counter + 1;
90
91         my ($self, $pms) = @_;
92         my $fromaddr = $pms->get("From:addr", undef);
93         my @fromarr = split('@', $fromaddr);
94         my $domain = $fromarr[1];
95         my $creationDate = infanticide_get_creation_date_recursive($domain);
96         if ($creationDate eq "") {
97                 return(0);
98         }
99         my $dt = str2time($creationDate);
100         my $daysAgo = ceil((time() - $dt) / 86400);
101         syslog("debug", "infanticide: " . $domain . " was created " . $daysAgo . " days ago");
102         if ($daysAgo < 30) {
103                 return(1);
104         }
105         return(0);
106 }