line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Mail::DMARC::Base; |
2
|
|
|
|
|
|
|
our $VERSION = '1.20230215'; |
3
|
16
|
|
|
16
|
|
7626
|
use strict; |
|
16
|
|
|
|
|
40
|
|
|
16
|
|
|
|
|
453
|
|
4
|
16
|
|
|
16
|
|
78
|
use warnings; |
|
16
|
|
|
|
|
63
|
|
|
16
|
|
|
|
|
404
|
|
5
|
16
|
|
|
16
|
|
268
|
use 5.10.0; |
|
16
|
|
|
|
|
53
|
|
6
|
|
|
|
|
|
|
|
7
|
16
|
|
|
16
|
|
114
|
use Carp; |
|
16
|
|
|
|
|
34
|
|
|
16
|
|
|
|
|
972
|
|
8
|
16
|
|
|
16
|
|
8059
|
use Config::Tiny; |
|
16
|
|
|
|
|
18032
|
|
|
16
|
|
|
|
|
548
|
|
9
|
16
|
|
|
16
|
|
4490
|
use File::ShareDir; |
|
16
|
|
|
|
|
232124
|
|
|
16
|
|
|
|
|
804
|
|
10
|
16
|
|
|
16
|
|
11729
|
use HTTP::Tiny; |
|
16
|
|
|
|
|
600039
|
|
|
16
|
|
|
|
|
670
|
|
11
|
16
|
|
|
16
|
|
5868
|
use IO::File; |
|
16
|
|
|
|
|
16787
|
|
|
16
|
|
|
|
|
2162
|
|
12
|
16
|
|
|
16
|
|
8588
|
use Net::DNS::Resolver; |
|
16
|
|
|
|
|
918921
|
|
|
16
|
|
|
|
|
2781
|
|
13
|
16
|
|
|
16
|
|
15621
|
use Net::IDN::Encode qw/domain_to_unicode/; |
|
16
|
|
|
|
|
1932352
|
|
|
16
|
|
|
|
|
1454
|
|
14
|
16
|
|
|
16
|
|
12079
|
use Net::IP; |
|
16
|
|
|
|
|
945015
|
|
|
16
|
|
|
|
|
3156
|
|
15
|
16
|
|
|
16
|
|
10744
|
use Regexp::Common 2013031301 qw /net/; |
|
16
|
|
|
|
|
44684
|
|
|
16
|
|
|
|
|
104
|
|
16
|
16
|
|
|
16
|
|
48937
|
use Socket; |
|
16
|
|
|
|
|
70
|
|
|
16
|
|
|
|
|
10269
|
|
17
|
16
|
|
|
16
|
|
8055
|
use Socket6 qw//; # don't export symbols |
|
16
|
|
|
|
|
20130
|
|
|
16
|
|
|
|
|
13705
|
|
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
sub new { |
20
|
99
|
|
|
99
|
0
|
14149
|
my ( $class, @args ) = @_; |
21
|
99
|
50
|
|
|
|
364
|
croak "invalid args" if scalar @args % 2 != 0; |
22
|
99
|
|
|
|
|
967
|
return bless { |
23
|
|
|
|
|
|
|
config_file => 'mail-dmarc.ini', |
24
|
|
|
|
|
|
|
@args, # this may override config_file |
25
|
|
|
|
|
|
|
}, $class; |
26
|
|
|
|
|
|
|
} |
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
my $_fake_time; |
29
|
|
|
|
|
|
|
sub time { ## no critic |
30
|
|
|
|
|
|
|
# Ability to return a fake time for testing |
31
|
33
|
|
|
33
|
0
|
108
|
my ( $self ) = @_; |
32
|
33
|
100
|
|
|
|
125
|
my $time = defined $Mail::DMARC::Base::_fake_time ? $Mail::DMARC::Base::_fake_time : time; |
33
|
33
|
|
|
|
|
615
|
return $time; |
34
|
|
|
|
|
|
|
} |
35
|
|
|
|
|
|
|
sub set_fake_time { |
36
|
8
|
|
|
8
|
0
|
92
|
my ( $self, $time ) = @_; |
37
|
8
|
|
|
|
|
27
|
$Mail::DMARC::Base::_fake_time = $time; |
38
|
8
|
|
|
|
|
21
|
return; |
39
|
|
|
|
|
|
|
} |
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
sub config { |
42
|
2196
|
|
|
2196
|
0
|
41175
|
my ( $self, $file, @too_many ) = @_; |
43
|
2196
|
50
|
|
|
|
4611
|
croak "invalid args" if scalar @too_many; |
44
|
2196
|
100
|
66
|
|
|
13494
|
return $self->{config} if ref $self->{config} && !$file; |
45
|
47
|
|
|
|
|
280
|
return $self->{config} = $self->get_config($file); |
46
|
|
|
|
|
|
|
} |
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
sub get_prefix { |
49
|
25
|
|
|
25
|
0
|
461
|
my ($self, $subdir) = @_; |
50
|
25
|
100
|
|
|
|
74
|
return map { $_ . ($subdir ? $subdir : '') } qw[ /usr/local/ /opt/local/ / ./ ]; |
|
100
|
|
|
|
|
415
|
|
51
|
|
|
|
|
|
|
} |
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
sub get_sharefile { |
54
|
22
|
|
|
22
|
0
|
678
|
my ($self, $file) = @_; |
55
|
|
|
|
|
|
|
|
56
|
22
|
|
|
|
|
151
|
my $match = File::ShareDir::dist_file( 'Mail-DMARC', $file ); |
57
|
22
|
50
|
|
|
|
4874
|
print "using $match for $file\n" if $self->verbose; |
58
|
22
|
|
|
|
|
229
|
return $match; |
59
|
|
|
|
|
|
|
} |
60
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
sub get_config { |
62
|
47
|
|
|
47
|
0
|
88
|
my $self = shift; |
63
|
47
|
50
|
66
|
|
|
371
|
my $file = shift || $ENV{MAIL_DMARC_CONFIG_FILE} || $self->{config_file} or croak; |
64
|
47
|
100
|
|
|
|
1325
|
return Config::Tiny->read($file) if -r $file; # fully qualified |
65
|
12
|
|
|
|
|
105
|
foreach my $d ($self->get_prefix('etc')) { |
66
|
48
|
100
|
|
|
|
604
|
next if !-d $d; |
67
|
24
|
50
|
|
|
|
328
|
next if !-e "$d/$file"; |
68
|
0
|
0
|
|
|
|
0
|
croak "unreadable file: $d/$file" if !-r "$d/$file"; |
69
|
0
|
|
|
|
|
0
|
my $Config = Config::Tiny->new; |
70
|
0
|
|
|
|
|
0
|
return Config::Tiny->read("$d/$file"); |
71
|
|
|
|
|
|
|
} |
72
|
|
|
|
|
|
|
|
73
|
12
|
100
|
|
|
|
167
|
if ($file ne 'mail-dmarc.ini') { |
74
|
1
|
|
|
|
|
271
|
croak "unable to find requested config file $file\n"; |
75
|
|
|
|
|
|
|
} |
76
|
11
|
|
|
|
|
84
|
return Config::Tiny->read( $self->get_sharefile('mail-dmarc.ini') ); |
77
|
|
|
|
|
|
|
} |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
sub any_inet_ntop { |
80
|
24
|
|
|
24
|
0
|
4817
|
my ( $self, $ip_bin ) = @_; |
81
|
24
|
50
|
|
|
|
68
|
$ip_bin or croak "missing IP in request"; |
82
|
|
|
|
|
|
|
|
83
|
24
|
100
|
|
|
|
70
|
if ( length $ip_bin == 16 ) { |
84
|
6
|
|
|
|
|
65
|
return Socket6::inet_ntop( AF_INET6, $ip_bin ); |
85
|
|
|
|
|
|
|
} |
86
|
|
|
|
|
|
|
|
87
|
18
|
|
|
|
|
154
|
return Socket6::inet_ntop( AF_INET, $ip_bin ); |
88
|
|
|
|
|
|
|
} |
89
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
sub any_inet_pton { |
91
|
14
|
|
|
14
|
0
|
4123
|
my ( $self, $ip_txt ) = @_; |
92
|
14
|
50
|
|
|
|
56
|
$ip_txt or croak "missing IP in request"; |
93
|
|
|
|
|
|
|
|
94
|
14
|
100
|
|
|
|
67
|
if ( $ip_txt =~ /:/ ) { |
95
|
4
|
|
33
|
|
|
40
|
return Socket6::inet_pton( AF_INET6, $ip_txt ) |
96
|
|
|
|
|
|
|
|| croak "invalid IPv6: $ip_txt"; |
97
|
|
|
|
|
|
|
} |
98
|
|
|
|
|
|
|
|
99
|
10
|
|
33
|
|
|
148
|
return Socket6::inet_pton( AF_INET, $ip_txt ) |
100
|
|
|
|
|
|
|
|| croak "invalid IPv4: $ip_txt"; |
101
|
|
|
|
|
|
|
} |
102
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
{ |
104
|
|
|
|
|
|
|
my $public_suffixes; |
105
|
|
|
|
|
|
|
my $public_suffixes_stamp; |
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
sub get_public_suffix_list { |
108
|
341
|
|
|
341
|
0
|
489
|
my ( $self ) = @_; |
109
|
341
|
100
|
|
|
|
677
|
if ( $public_suffixes ) { return $public_suffixes; } |
|
334
|
|
|
|
|
564
|
|
110
|
16
|
|
|
16
|
|
159
|
no warnings 'once'; ## no critic |
|
16
|
|
|
|
|
54
|
|
|
16
|
|
|
|
|
30418
|
|
111
|
7
|
|
|
|
|
14
|
$Mail::DMARC::psl_loads++; |
112
|
7
|
|
|
|
|
39
|
my $file = $self->find_psl_file(); |
113
|
7
|
|
|
|
|
105
|
$public_suffixes_stamp = ( stat( $file ) )[9]; |
114
|
|
|
|
|
|
|
|
115
|
6
|
50
|
|
6
|
|
47
|
open my $fh, '<:encoding(UTF-8)', $file |
|
6
|
|
|
|
|
21
|
|
|
6
|
|
|
|
|
102
|
|
|
7
|
|
|
|
|
470
|
|
116
|
|
|
|
|
|
|
or croak "unable to open $file for read: $!\n"; |
117
|
|
|
|
|
|
|
# load PSL into hash for fast lookups, esp. for long running daemons |
118
|
80458
|
|
|
|
|
157698
|
my %psl = map { $_ => 1 } |
119
|
99666
|
|
|
|
|
143750
|
grep { $_ !~ /^[\/\s]/ } # weed out comments & whitespace |
120
|
7
|
|
|
|
|
8972
|
map { chomp($_); $_ } ## no critic, remove line endings |
|
99666
|
|
|
|
|
161632
|
|
|
99666
|
|
|
|
|
116952
|
|
121
|
|
|
|
|
|
|
<$fh>; |
122
|
7
|
|
|
|
|
12207
|
close $fh; |
123
|
7
|
|
|
|
|
90
|
return $public_suffixes = \%psl; |
124
|
|
|
|
|
|
|
} |
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
sub check_public_suffix_list { |
127
|
2
|
|
|
2
|
0
|
2434
|
my ( $self ) = @_; |
128
|
2
|
|
|
|
|
8
|
my $file = $self->find_psl_file(); |
129
|
2
|
|
|
|
|
30
|
my $new_public_suffixes_stamp = ( stat( $file ) )[9]; |
130
|
2
|
100
|
|
|
|
13
|
if ( $new_public_suffixes_stamp != $public_suffixes_stamp ) { |
131
|
1
|
|
|
|
|
2733
|
$public_suffixes = undef; |
132
|
1
|
|
|
|
|
8
|
$self->get_public_suffix_list(); |
133
|
1
|
|
|
|
|
7
|
return 1; |
134
|
|
|
|
|
|
|
} |
135
|
1
|
|
|
|
|
31
|
return 0; |
136
|
|
|
|
|
|
|
} |
137
|
|
|
|
|
|
|
} |
138
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
sub is_public_suffix { |
140
|
340
|
|
|
340
|
1
|
10466
|
my ( $self, $zone ) = @_; |
141
|
|
|
|
|
|
|
|
142
|
340
|
50
|
|
|
|
604
|
croak "missing zone name!" if !$zone; |
143
|
|
|
|
|
|
|
|
144
|
340
|
|
|
|
|
639
|
my $public_suffixes = $self->get_public_suffix_list(); |
145
|
|
|
|
|
|
|
|
146
|
340
|
100
|
|
|
|
847
|
$zone = domain_to_unicode( $zone ) if $zone =~ /xn--/; |
147
|
|
|
|
|
|
|
|
148
|
340
|
100
|
|
|
|
42303
|
return 1 if $public_suffixes->{$zone}; |
149
|
|
|
|
|
|
|
|
150
|
164
|
|
|
|
|
457
|
my @labels = split /\./, $zone; |
151
|
164
|
|
|
|
|
509
|
$zone = join '.', '*', (@labels)[ 1 .. scalar(@labels) - 1 ]; |
152
|
|
|
|
|
|
|
|
153
|
164
|
100
|
|
|
|
417
|
return 1 if $public_suffixes->{$zone}; |
154
|
163
|
|
|
|
|
581
|
return 0; |
155
|
|
|
|
|
|
|
} |
156
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
sub update_psl_file { |
158
|
0
|
|
|
0
|
1
|
0
|
my ($self, $dryrun) = @_; |
159
|
|
|
|
|
|
|
|
160
|
0
|
|
|
|
|
0
|
my $psl_file = $self->find_psl_file(); |
161
|
|
|
|
|
|
|
|
162
|
0
|
0
|
|
|
|
0
|
die "No Public Suffix List file found\n" if ( ! $psl_file ); |
163
|
0
|
0
|
|
|
|
0
|
die "Public suffix list file $psl_file not found\n" if ( ! -f $psl_file ); |
164
|
0
|
0
|
|
|
|
0
|
die "Cannot write to Public Suffix List file $psl_file\n" if ( ! -w $psl_file ); |
165
|
|
|
|
|
|
|
|
166
|
0
|
|
|
|
|
0
|
my $url = 'https://publicsuffix.org/list/effective_tld_names.dat'; |
167
|
0
|
0
|
|
|
|
0
|
if ( $dryrun ) { |
168
|
0
|
|
|
|
|
0
|
print "Will attempt to update the Public Suffix List file at $psl_file (dryrun mode)\n"; |
169
|
0
|
|
|
|
|
0
|
return; |
170
|
|
|
|
|
|
|
} |
171
|
|
|
|
|
|
|
|
172
|
0
|
|
|
|
|
0
|
my $response = HTTP::Tiny->new->mirror( $url, $psl_file ); |
173
|
0
|
|
|
|
|
0
|
my $content = $response->{'content'}; |
174
|
0
|
0
|
|
|
|
0
|
if ( !$response->{'success'} ) { |
175
|
0
|
|
|
|
|
0
|
my $status = $response->{'status'}; |
176
|
0
|
|
|
|
|
0
|
die "HTTP Request for Public Suffix List file failed with error $status ($content)\n"; |
177
|
|
|
|
|
|
|
} |
178
|
|
|
|
|
|
|
else { |
179
|
0
|
0
|
|
|
|
0
|
if ( $response->{'status'} eq '304' ) { |
180
|
0
|
|
|
|
|
0
|
print "Public Suffix List file $psl_file not modified\n"; |
181
|
|
|
|
|
|
|
} |
182
|
|
|
|
|
|
|
else { |
183
|
0
|
|
|
|
|
0
|
print "Public Suffix List file $psl_file updated\n"; |
184
|
|
|
|
|
|
|
} |
185
|
|
|
|
|
|
|
} |
186
|
0
|
|
|
|
|
0
|
return; |
187
|
|
|
|
|
|
|
} |
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
sub find_psl_file { |
190
|
10
|
|
|
10
|
0
|
1690
|
my ($self) = @_; |
191
|
|
|
|
|
|
|
|
192
|
10
|
|
50
|
|
|
57
|
my $file = $self->config->{dns}{public_suffix_list} || 'share/public_suffix_list'; |
193
|
10
|
0
|
33
|
|
|
4020
|
if ( $file =~ /^\// && -f $file && -r $file ) { |
|
|
|
33
|
|
|
|
|
194
|
0
|
0
|
|
|
|
0
|
print "using $file for Public Suffix List\n" if $self->verbose; |
195
|
0
|
|
|
|
|
0
|
return $file; |
196
|
|
|
|
|
|
|
} |
197
|
10
|
|
|
|
|
20
|
my $path; |
198
|
10
|
|
|
|
|
57
|
foreach $path ($self->get_prefix('share/' . $file)) { ## no critic |
199
|
40
|
50
|
33
|
|
|
559
|
last if ( -f $path && -r $path ); |
200
|
|
|
|
|
|
|
} |
201
|
10
|
50
|
33
|
|
|
65
|
if ($path && -r $path) { |
202
|
0
|
0
|
|
|
|
0
|
print "using $path for Public Suffix List\n" if $self->verbose; |
203
|
0
|
|
|
|
|
0
|
return $path; |
204
|
|
|
|
|
|
|
}; |
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
# Fallback to included suffic list |
207
|
10
|
|
|
|
|
48
|
return $self->get_sharefile('public_suffix_list'); |
208
|
|
|
|
|
|
|
} |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
sub has_dns_rr { |
211
|
73
|
|
|
73
|
1
|
4243
|
my ( $self, $type, $domain ) = @_; |
212
|
|
|
|
|
|
|
|
213
|
73
|
|
|
|
|
115
|
my @matches; |
214
|
73
|
|
|
|
|
222
|
my $res = $self->get_resolver(); |
215
|
73
|
100
|
|
|
|
321
|
my $query = $res->query( $domain, $type ) or do { |
216
|
40
|
50
|
|
|
|
2069732
|
return 0 if ! wantarray; |
217
|
0
|
|
|
|
|
0
|
return @matches; |
218
|
|
|
|
|
|
|
}; |
219
|
33
|
|
|
|
|
687771
|
for my $rr ( $query->answer ) { |
220
|
61
|
100
|
|
|
|
3730
|
next if $rr->type ne $type; |
221
|
57
|
50
|
|
|
|
955
|
push @matches, $rr->type eq 'A' ? $rr->address |
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
222
|
|
|
|
|
|
|
: $rr->type eq 'PTR' ? $rr->ptrdname |
223
|
|
|
|
|
|
|
: $rr->type eq 'NS' ? $rr->nsdname |
224
|
|
|
|
|
|
|
: $rr->type eq 'TXT' ? $rr->txtdata |
225
|
|
|
|
|
|
|
: $rr->type eq 'SPF' ? $rr->txtdata |
226
|
|
|
|
|
|
|
: $rr->type eq 'AAAA' ? $rr->address |
227
|
|
|
|
|
|
|
: $rr->type eq 'MX' ? $rr->exchange |
228
|
|
|
|
|
|
|
: $rr->answer; |
229
|
|
|
|
|
|
|
} |
230
|
33
|
50
|
|
|
|
3606
|
return scalar @matches if ! wantarray; |
231
|
0
|
|
|
|
|
0
|
return @matches; |
232
|
|
|
|
|
|
|
} |
233
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
sub epoch_to_iso { |
235
|
37
|
|
|
37
|
0
|
406
|
my ($self, $epoch) = @_; |
236
|
|
|
|
|
|
|
|
237
|
37
|
|
|
|
|
1036
|
my @fields = localtime( $epoch ); |
238
|
|
|
|
|
|
|
|
239
|
37
|
|
|
|
|
195
|
my $ss = sprintf( "%02i", $fields[0] ); # seconds |
240
|
37
|
|
|
|
|
81
|
my $mn = sprintf( "%02i", $fields[1] ); # minutes |
241
|
37
|
|
|
|
|
87
|
my $hh = sprintf( "%02i", $fields[2] ); # hours (24 hour clock) |
242
|
|
|
|
|
|
|
|
243
|
37
|
|
|
|
|
64
|
my $dd = sprintf( "%02i", $fields[3] ); # day of month |
244
|
37
|
|
|
|
|
87
|
my $mm = sprintf( "%02i", $fields[4] + 1 ); # month |
245
|
37
|
|
|
|
|
70
|
my $yy = ( $fields[5] + 1900 ); # year |
246
|
|
|
|
|
|
|
|
247
|
37
|
|
|
|
|
362
|
return "$yy-$mm-$dd" .'T'."$hh:$mn:$ss"; |
248
|
|
|
|
|
|
|
} |
249
|
|
|
|
|
|
|
|
250
|
|
|
|
|
|
|
sub get_resolver { |
251
|
97
|
|
|
97
|
1
|
1712
|
my $self = shift; |
252
|
97
|
|
50
|
|
|
449
|
my $timeout = shift || $self->config->{dns}{timeout} || 5; |
253
|
97
|
100
|
|
|
|
4211
|
return $self->{resolver} if defined $self->{resolver}; |
254
|
11
|
|
|
|
|
191
|
$self->{resolver} = Net::DNS::Resolver->new( dnsrch => 0 ); |
255
|
11
|
|
|
|
|
5814
|
$self->{resolver}->tcp_timeout($timeout); |
256
|
11
|
|
|
|
|
281
|
$self->{resolver}->udp_timeout($timeout); |
257
|
11
|
|
|
|
|
239
|
return $self->{resolver}; |
258
|
|
|
|
|
|
|
} |
259
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
sub set_resolver { |
261
|
0
|
|
|
0
|
1
|
0
|
my ($self,$resolver) = @_; |
262
|
0
|
|
|
|
|
0
|
$self->{resolver} = $resolver; |
263
|
0
|
|
|
|
|
0
|
return; |
264
|
|
|
|
|
|
|
} |
265
|
|
|
|
|
|
|
|
266
|
|
|
|
|
|
|
sub is_valid_ip { |
267
|
21
|
|
|
21
|
1
|
13753
|
my ( $self, $ip ) = @_; |
268
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
# Using Regexp::Common removes perl 5.8 compat |
270
|
|
|
|
|
|
|
# Perl 5.008009 does not support the pattern $RE{net}{IPv6}. |
271
|
|
|
|
|
|
|
# You need Perl 5.01 or later |
272
|
|
|
|
|
|
|
|
273
|
21
|
100
|
|
|
|
96
|
if ( $ip =~ /:/ ) { |
274
|
1
|
|
|
|
|
7
|
return Net::IP->new( $ip, 6 ); |
275
|
|
|
|
|
|
|
} |
276
|
|
|
|
|
|
|
|
277
|
20
|
|
|
|
|
174
|
return Net::IP->new( $ip, 4 ); |
278
|
|
|
|
|
|
|
} |
279
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
sub is_valid_domain { |
281
|
68
|
|
|
68
|
1
|
2699
|
my ( $self, $domain ) = @_; |
282
|
68
|
100
|
|
|
|
591
|
return 0 if $domain !~ /^$RE{net}{domain}{-rfc1101}{-nospace}$/x; |
283
|
65
|
|
|
|
|
20287
|
my $tld = ( split /\./, lc $domain )[-1]; |
284
|
65
|
100
|
|
|
|
664
|
return 1 if $self->is_public_suffix($tld); |
285
|
5
|
|
|
|
|
30
|
$tld = join( '.', ( split /\./, $domain )[ -2, -1 ] ); |
286
|
5
|
50
|
|
|
|
15
|
return 1 if $self->is_public_suffix($tld); |
287
|
5
|
|
|
|
|
367
|
return 0; |
288
|
|
|
|
|
|
|
} |
289
|
|
|
|
|
|
|
|
290
|
|
|
|
|
|
|
sub is_valid_spf_scope { |
291
|
53
|
|
|
53
|
0
|
115
|
my ($self, $scope ) = @_; |
292
|
53
|
50
|
|
|
|
90
|
return lc $scope if grep { lc $scope eq $_ } qw/ mfrom helo /; |
|
106
|
|
|
|
|
429
|
|
293
|
0
|
|
|
|
|
0
|
carp "$scope is not a valid SPF scope"; |
294
|
0
|
|
|
|
|
0
|
return; |
295
|
|
|
|
|
|
|
} |
296
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
sub is_valid_spf_result { |
298
|
53
|
|
|
53
|
0
|
111
|
my ($self, $result ) = @_; |
299
|
53
|
50
|
|
|
|
107
|
return 1 if grep { lc $result eq $_ } |
|
371
|
|
|
|
|
717
|
|
300
|
|
|
|
|
|
|
qw/ fail neutral none pass permerror softfail temperror /; |
301
|
0
|
|
|
|
|
0
|
carp "$result is not a valid SPF result"; |
302
|
0
|
|
|
|
|
0
|
return; |
303
|
|
|
|
|
|
|
} |
304
|
|
|
|
|
|
|
|
305
|
|
|
|
|
|
|
sub slurp { |
306
|
11
|
|
|
11
|
0
|
44
|
my ( $self, $file ) = @_; |
307
|
11
|
50
|
|
|
|
524
|
open my $FH, '<', $file or croak "unable to read $file: $!"; |
308
|
11
|
|
|
|
|
37
|
my $contents = do { local $/; <$FH> }; ## no critic (Local) |
|
11
|
|
|
|
|
71
|
|
|
11
|
|
|
|
|
500
|
|
309
|
11
|
|
|
|
|
167
|
close $FH; |
310
|
11
|
|
|
|
|
127
|
return $contents; |
311
|
|
|
|
|
|
|
} |
312
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
sub verbose { |
314
|
232
|
100
|
|
232
|
0
|
1167
|
return $_[0]->{verbose} if 1 == scalar @_; |
315
|
4
|
|
|
|
|
10
|
return $_[0]->{verbose} = $_[1]; |
316
|
|
|
|
|
|
|
} |
317
|
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
1; |
319
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
__END__ |