line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Mail::DMARC::Policy; |
2
|
12
|
|
|
12
|
|
273165
|
use strict; |
|
12
|
|
|
|
|
63
|
|
|
12
|
|
|
|
|
388
|
|
3
|
12
|
|
|
12
|
|
64
|
use warnings; |
|
12
|
|
|
|
|
36
|
|
|
12
|
|
|
|
|
520
|
|
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
our $VERSION = '1.20230215'; |
6
|
|
|
|
|
|
|
|
7
|
12
|
|
|
12
|
|
74
|
use Carp; |
|
12
|
|
|
|
|
36
|
|
|
12
|
|
|
|
|
747
|
|
8
|
|
|
|
|
|
|
|
9
|
12
|
|
|
12
|
|
4887
|
use Mail::DMARC::Report::URI; |
|
12
|
|
|
|
|
30
|
|
|
12
|
|
|
|
|
22400
|
|
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
sub new { |
12
|
82
|
|
|
82
|
1
|
19155
|
my ( $class, @args ) = @_; |
13
|
82
|
50
|
|
|
|
234
|
my $package = ref $class ? ref $class : $class; |
14
|
82
|
|
|
|
|
167
|
my $self = bless {}, $package; |
15
|
|
|
|
|
|
|
|
16
|
82
|
100
|
|
|
|
300
|
return $self if 0 == scalar @args; # no args, empty pol |
17
|
71
|
100
|
|
|
|
177
|
if (1 == @args) { # a string |
18
|
55
|
|
|
|
|
167
|
my $policy = $self->parse( $args[0] ); |
19
|
55
|
|
|
|
|
191
|
$self->is_valid($policy); |
20
|
53
|
|
|
|
|
319
|
return $policy; |
21
|
|
|
|
|
|
|
} |
22
|
|
|
|
|
|
|
|
23
|
16
|
50
|
|
|
|
57
|
croak "invalid arguments" if @args % 2 != 0; |
24
|
16
|
|
|
|
|
81
|
my $policy = {@args}; |
25
|
16
|
|
|
|
|
31
|
bless $policy, $package; |
26
|
16
|
50
|
|
|
|
48
|
croak "invalid policy" if !$self->is_valid($policy); |
27
|
14
|
|
|
|
|
99
|
return bless $policy, $package; |
28
|
|
|
|
|
|
|
} |
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
sub parse { |
31
|
75
|
|
|
75
|
1
|
2746
|
my ( $self, $str, @junk ) = @_; |
32
|
75
|
50
|
|
|
|
186
|
croak "invalid parse request" if 0 != scalar @junk; |
33
|
75
|
|
|
|
|
137
|
my $cleaned = $str; |
34
|
75
|
|
|
|
|
398
|
$cleaned =~ s/\s//g; # remove whitespace |
35
|
75
|
|
|
|
|
185
|
$cleaned =~ s/\\;/;/g; # replace \; with ; |
36
|
75
|
|
|
|
|
139
|
$cleaned =~ s/;;/;/g; # replace ;; with ; |
37
|
75
|
|
|
|
|
162
|
$cleaned =~ s/;0;/;/g; # replace ;0; with ; |
38
|
75
|
100
|
|
|
|
229
|
chop $cleaned if ';' eq substr $cleaned, -1, 1; # remove a trailing ; |
39
|
75
|
|
|
|
|
300
|
my @tag_vals = split /;/, $cleaned; |
40
|
75
|
|
|
|
|
112
|
my %policy; |
41
|
75
|
|
|
|
|
106
|
my $warned = 0; |
42
|
75
|
|
|
|
|
148
|
foreach my $tv (@tag_vals) { |
43
|
482
|
|
|
|
|
1619
|
my ($tag, $value) = split /=|:|-/, $tv, 2; |
44
|
482
|
100
|
33
|
|
|
1940
|
if ( !defined $tag || !defined $value || $value eq '') { |
|
|
|
66
|
|
|
|
|
45
|
2
|
50
|
|
|
|
4
|
if (!$warned) { |
46
|
|
|
|
|
|
|
#warn "tv: $tv\n"; |
47
|
2
|
|
|
|
|
98
|
warn "invalid DMARC record, please post this message to\n" . |
48
|
|
|
|
|
|
|
"\thttps://github.com/msimerson/mail-dmarc/issues/39\n" . |
49
|
|
|
|
|
|
|
"\t$str\n"; |
50
|
|
|
|
|
|
|
} |
51
|
2
|
|
|
|
|
7
|
$warned++; |
52
|
2
|
|
|
|
|
5
|
next; |
53
|
|
|
|
|
|
|
} |
54
|
480
|
|
|
|
|
1057
|
$policy{lc $tag} = $value; |
55
|
|
|
|
|
|
|
} |
56
|
75
|
|
|
|
|
346
|
return bless \%policy, ref $self; # inherited defaults + overrides |
57
|
|
|
|
|
|
|
} |
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
sub apply_defaults { |
60
|
7
|
|
|
7
|
1
|
439
|
my $self = shift; |
61
|
|
|
|
|
|
|
|
62
|
7
|
100
|
|
|
|
46
|
$self->adkim('r') if !defined $self->adkim; |
63
|
7
|
100
|
|
|
|
24
|
$self->aspf('r') if !defined $self->aspf; |
64
|
7
|
50
|
|
|
|
29
|
$self->fo(0) if !defined $self->fo; |
65
|
7
|
50
|
|
|
|
31
|
$self->ri(86400) if !defined $self->ri; |
66
|
7
|
50
|
|
|
|
25
|
$self->rf('afrf') if !defined $self->rf; |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
# pct # default is 100%, but 100% -vs- not defined is different |
69
|
7
|
|
|
|
|
31
|
return 1; |
70
|
|
|
|
|
|
|
} |
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
sub v { |
73
|
6
|
100
|
|
6
|
1
|
1405
|
return $_[0]->{v} if 1 == scalar @_; |
74
|
4
|
100
|
|
|
|
85
|
croak "unsupported DMARC version" if 'DMARC1' ne uc $_[1]; |
75
|
3
|
|
|
|
|
12
|
return $_[0]->{v} = $_[1]; |
76
|
|
|
|
|
|
|
} |
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
sub p { |
79
|
37
|
100
|
|
37
|
1
|
3393
|
return $_[0]->{p} if 1 == scalar @_; |
80
|
9
|
100
|
|
|
|
19
|
croak "invalid p" if !$_[0]->is_valid_p( $_[1] ); |
81
|
6
|
|
|
|
|
25
|
return $_[0]->{p} = $_[1]; |
82
|
|
|
|
|
|
|
} |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
sub sp { |
85
|
24
|
100
|
|
24
|
1
|
2955
|
return $_[0]->{sp} if 1 == scalar @_; |
86
|
9
|
100
|
|
|
|
23
|
croak "invalid sp ($_[1])" if !$_[0]->is_valid_p( $_[1] ); |
87
|
6
|
|
|
|
|
26
|
return $_[0]->{sp} = $_[1]; |
88
|
|
|
|
|
|
|
} |
89
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
sub adkim { |
91
|
42
|
100
|
|
42
|
1
|
2082
|
return $_[0]->{adkim} if 1 == scalar @_; |
92
|
12
|
100
|
|
|
|
22
|
croak "invalid adkim" if 0 == grep {/^\Q$_[1]\E$/ix} qw/ r s /; |
|
24
|
|
|
|
|
332
|
|
93
|
10
|
|
|
|
|
35
|
return $_[0]->{adkim} = $_[1]; |
94
|
|
|
|
|
|
|
} |
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
sub aspf { |
97
|
46
|
100
|
|
46
|
1
|
2171
|
return $_[0]->{aspf} if 1 == scalar @_; |
98
|
13
|
100
|
|
|
|
24
|
croak "invalid aspf" if 0 == grep {/^\Q$_[1]\E$/ix} qw/ r s /; |
|
26
|
|
|
|
|
473
|
|
99
|
11
|
|
|
|
|
50
|
return $_[0]->{aspf} = $_[1]; |
100
|
|
|
|
|
|
|
} |
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
sub fo { |
103
|
26
|
100
|
|
26
|
1
|
3829
|
return $_[0]->{fo} if 1 == scalar @_; |
104
|
19
|
100
|
|
|
|
411
|
croak "invalid fo: $_[1]" if $_[1] !~ /^[01ds](:[01ds])*$/ix; |
105
|
15
|
|
|
|
|
51
|
return $_[0]->{fo} = $_[1]; |
106
|
|
|
|
|
|
|
} |
107
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
sub rua { |
109
|
36
|
100
|
|
36
|
1
|
1472
|
return $_[0]->{rua} if 1 == scalar @_; |
110
|
17
|
100
|
|
|
|
67
|
croak "invalid rua" if !$_[0]->is_valid_uri_list( $_[1] ); |
111
|
15
|
|
|
|
|
99
|
return $_[0]->{rua} = $_[1]; |
112
|
|
|
|
|
|
|
} |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
sub ruf { |
115
|
2
|
50
|
|
2
|
1
|
352
|
return $_[0]->{ruf} if 1 == scalar @_; |
116
|
2
|
100
|
|
|
|
5
|
croak "invalid rua" if !$_[0]->is_valid_uri_list( $_[1] ); |
117
|
1
|
|
|
|
|
6
|
return $_[0]->{ruf} = $_[1]; |
118
|
|
|
|
|
|
|
} |
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
sub rf { |
121
|
21
|
100
|
|
21
|
1
|
2400
|
return $_[0]->{rf} if 1 == scalar @_; |
122
|
14
|
|
|
|
|
73
|
foreach my $f ( split /,/, $_[1] ) { |
123
|
14
|
100
|
|
|
|
51
|
croak "invalid format: $f" if !$_[0]->is_valid_rf($f); |
124
|
|
|
|
|
|
|
} |
125
|
11
|
|
|
|
|
45
|
return $_[0]->{rf} = $_[1]; |
126
|
|
|
|
|
|
|
} |
127
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
sub ri { |
129
|
26
|
100
|
|
26
|
1
|
2347
|
return $_[0]->{ri} if 1 == scalar @_; |
130
|
14
|
100
|
|
|
|
219
|
croak "not numeric ($_[1])!" if $_[1] =~ /\D/; |
131
|
12
|
50
|
|
|
|
34
|
croak "not an integer!" if $_[1] != int $_[1]; |
132
|
12
|
100
|
66
|
|
|
146
|
croak "out of range" if ( $_[1] < 0 || $_[1] > 4294967295 ); |
133
|
11
|
|
|
|
|
41
|
return $_[0]->{ri} = $_[1]; |
134
|
|
|
|
|
|
|
} |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
sub pct { |
137
|
20
|
100
|
|
20
|
1
|
3568
|
return $_[0]->{pct} if 1 == scalar @_; |
138
|
11
|
100
|
|
|
|
406
|
croak "not numeric ($_[1])!" if $_[1] =~ /\D/; |
139
|
6
|
50
|
|
|
|
12
|
croak "not an integer!" if $_[1] != int $_[1]; |
140
|
6
|
100
|
66
|
|
|
95
|
croak "out of range" if $_[1] < 0 || $_[1] > 100; |
141
|
5
|
|
|
|
|
20
|
return $_[0]->{pct} = $_[1]; |
142
|
|
|
|
|
|
|
} |
143
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
sub domain { |
145
|
62
|
100
|
|
62
|
0
|
442
|
return $_[0]->{domain} if 1 == scalar @_; |
146
|
7
|
|
|
|
|
72
|
return $_[0]->{domain} = $_[1]; |
147
|
|
|
|
|
|
|
} |
148
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
sub is_valid_rf { |
150
|
19
|
|
|
19
|
0
|
1241
|
my ( $self, $f ) = @_; |
151
|
19
|
100
|
|
|
|
36
|
return ( grep {/^\Q$f\E$/i} qw/ iodef afrf / ) ? 1 : 0; |
|
38
|
|
|
|
|
569
|
|
152
|
|
|
|
|
|
|
} |
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
sub is_valid_p { |
155
|
140
|
|
|
140
|
0
|
6561
|
my ( $self, $p ) = @_; |
156
|
140
|
50
|
|
|
|
253
|
croak "unspecified p" if !defined $p; |
157
|
140
|
100
|
|
|
|
221
|
return ( grep {/^\Q$p\E$/i} qw/ none reject quarantine / ) ? 1 : 0; |
|
420
|
|
|
|
|
2700
|
|
158
|
|
|
|
|
|
|
} |
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
sub is_valid_uri_list { |
161
|
21
|
|
|
21
|
0
|
59
|
my ( $self, $str ) = @_; |
162
|
21
|
|
66
|
|
|
164
|
$self->{uri} ||= Mail::DMARC::Report::URI->new; |
163
|
21
|
|
|
|
|
78
|
my $uris = $self->{uri}->parse($str); |
164
|
21
|
|
|
|
|
341
|
return scalar @$uris; |
165
|
|
|
|
|
|
|
} |
166
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
sub is_valid { |
168
|
109
|
|
|
109
|
0
|
284
|
my ( $self, $obj ) = @_; |
169
|
109
|
100
|
|
|
|
246
|
$obj = $self if !$obj; |
170
|
109
|
100
|
|
|
|
327
|
croak "missing version specifier" if !$obj->{v}; |
171
|
108
|
50
|
|
|
|
270
|
croak "invalid version" if 'DMARC1' ne uc $obj->{v}; |
172
|
108
|
100
|
|
|
|
213
|
if ( !$obj->{p} ) { |
173
|
4
|
100
|
100
|
|
|
14
|
if ( $obj->{rua} && $self->is_valid_uri_list( $obj->{rua} ) ) { |
174
|
1
|
|
|
|
|
3
|
$obj->{p} = 'none'; |
175
|
|
|
|
|
|
|
} |
176
|
|
|
|
|
|
|
else { |
177
|
3
|
|
|
|
|
298
|
croak "missing policy action (p=)"; |
178
|
|
|
|
|
|
|
} |
179
|
|
|
|
|
|
|
} |
180
|
105
|
100
|
|
|
|
238
|
croak "invalid policy action" if !$self->is_valid_p( $obj->{p} ); |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
# everything else is optional |
183
|
104
|
|
|
|
|
273
|
return 1; |
184
|
|
|
|
|
|
|
} |
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
1; |
187
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
__END__ |