Filename | /usr/local/lib/perl5/site_perl/Mail/SpamAssassin/Plugin/URIDetail.pm |
Statements | Executed 31 statements in 3.01ms |
Calls | P | F | Exclusive Time |
Inclusive Time |
Subroutine |
---|---|---|---|---|---|
1 | 1 | 1 | 70µs | 305µs | new | Mail::SpamAssassin::Plugin::URIDetail::
1 | 1 | 1 | 43µs | 153µs | set_config | Mail::SpamAssassin::Plugin::URIDetail::
1 | 1 | 1 | 40µs | 40µs | BEGIN@69 | Mail::SpamAssassin::Plugin::URIDetail::
1 | 1 | 1 | 30µs | 118µs | BEGIN@78 | Mail::SpamAssassin::Plugin::URIDetail::
1 | 1 | 1 | 27µs | 119µs | BEGIN@76 | Mail::SpamAssassin::Plugin::URIDetail::
1 | 1 | 1 | 27µs | 248µs | BEGIN@70 | Mail::SpamAssassin::Plugin::URIDetail::
1 | 1 | 1 | 27µs | 43µs | BEGIN@75 | Mail::SpamAssassin::Plugin::URIDetail::
1 | 1 | 1 | 25µs | 143µs | BEGIN@71 | Mail::SpamAssassin::Plugin::URIDetail::
1 | 1 | 1 | 21µs | 51µs | BEGIN@74 | Mail::SpamAssassin::Plugin::URIDetail::
1 | 1 | 1 | 20µs | 27µs | BEGIN@73 | Mail::SpamAssassin::Plugin::URIDetail::
0 | 0 | 0 | 0s | 0s | __ANON__[:147] | Mail::SpamAssassin::Plugin::URIDetail::
0 | 0 | 0 | 0s | 0s | check_uri_detail | Mail::SpamAssassin::Plugin::URIDetail::
0 | 0 | 0 | 0s | 0s | make_qr | Mail::SpamAssassin::Plugin::URIDetail::
Line | State ments |
Time on line |
Calls | Time in subs |
Code |
---|---|---|---|---|---|
1 | # <@LICENSE> | ||||
2 | # Licensed to the Apache Software Foundation (ASF) under one or more | ||||
3 | # contributor license agreements. See the NOTICE file distributed with | ||||
4 | # this work for additional information regarding copyright ownership. | ||||
5 | # The ASF licenses this file to you under the Apache License, Version 2.0 | ||||
6 | # (the "License"); you may not use this file except in compliance with | ||||
7 | # the License. You may obtain a copy of the License at: | ||||
8 | # | ||||
9 | # http://www.apache.org/licenses/LICENSE-2.0 | ||||
10 | # | ||||
11 | # Unless required by applicable law or agreed to in writing, software | ||||
12 | # distributed under the License is distributed on an "AS IS" BASIS, | ||||
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
14 | # See the License for the specific language governing permissions and | ||||
15 | # limitations under the License. | ||||
16 | # </@LICENSE> | ||||
17 | # | ||||
18 | # TODO: where are the tests? | ||||
19 | |||||
20 | =head1 NAME | ||||
21 | |||||
22 | URIDetail - test URIs using detailed URI information | ||||
23 | |||||
24 | =head1 SYNOPSIS | ||||
25 | |||||
26 | This plugin creates a new rule test type, known as "uri_detail". These | ||||
27 | rules apply to all URIs found in the message. | ||||
28 | |||||
29 | loadplugin Mail::SpamAssassin::Plugin::URIDetail | ||||
30 | |||||
31 | =head1 RULE DEFINITIONS AND PRIVILEGED SETTINGS | ||||
32 | |||||
33 | The format for defining a rule is as follows: | ||||
34 | |||||
35 | uri_detail SYMBOLIC_TEST_NAME key1 =~ /value1/ key2 !~ /value2/ ... | ||||
36 | |||||
37 | Supported keys are: | ||||
38 | |||||
39 | C<raw> is the raw URI prior to any cleaning | ||||
40 | (e.g. "http://spamassassin.apache%2Eorg/"). | ||||
41 | |||||
42 | C<type> is the tag(s) which referenced the raw_uri. I<parsed> is a | ||||
43 | faked type which specifies that the raw_uri was parsed from the | ||||
44 | rendered text. | ||||
45 | |||||
46 | C<cleaned> is a list including the raw URI and various cleaned | ||||
47 | versions of the raw URI (http://spamassassin.apache%2Eorg/, | ||||
48 | http://spamassassin.apache.org/). | ||||
49 | |||||
50 | C<text> is the anchor text(s) (text between <a> and </a>) that | ||||
51 | linked to the raw URI. | ||||
52 | |||||
53 | C<domain> is the domain(s) found in the cleaned URIs. | ||||
54 | |||||
55 | Example rule for matching a URI where the raw URI matches "%2Ebar", | ||||
56 | the domain "bar.com" is found, and the type is "a" (an anchor tag). | ||||
57 | |||||
58 | uri_detail TEST1 raw =~ /%2Ebar/ domain =~ /^bar\.com$/ type =~ /^a$/ | ||||
59 | |||||
60 | Example rule to look for suspicious "https" links: | ||||
61 | |||||
62 | uri_detail FAKE_HTTPS text =~ /\bhttps:/ cleaned !~ /\bhttps:/ | ||||
63 | |||||
64 | Regular expressions should be delimited by slashes. | ||||
65 | |||||
66 | =cut | ||||
67 | |||||
68 | package Mail::SpamAssassin::Plugin::URIDetail; | ||||
69 | 2 | 73µs | 1 | 40µs | # spent 40µs within Mail::SpamAssassin::Plugin::URIDetail::BEGIN@69 which was called:
# once (40µs+0s) by Mail::SpamAssassin::PluginHandler::load_plugin at line 69 # spent 40µs making 1 call to Mail::SpamAssassin::Plugin::URIDetail::BEGIN@69 |
70 | 2 | 76µs | 2 | 469µs | # spent 248µs (27+221) within Mail::SpamAssassin::Plugin::URIDetail::BEGIN@70 which was called:
# once (27µs+221µs) by Mail::SpamAssassin::PluginHandler::load_plugin at line 70 # spent 248µs making 1 call to Mail::SpamAssassin::Plugin::URIDetail::BEGIN@70
# spent 221µs making 1 call to Exporter::import |
71 | 2 | 84µs | 2 | 261µs | # spent 143µs (25+118) within Mail::SpamAssassin::Plugin::URIDetail::BEGIN@71 which was called:
# once (25µs+118µs) by Mail::SpamAssassin::PluginHandler::load_plugin at line 71 # spent 143µs making 1 call to Mail::SpamAssassin::Plugin::URIDetail::BEGIN@71
# spent 118µs making 1 call to Exporter::import |
72 | |||||
73 | 2 | 62µs | 2 | 35µs | # spent 27µs (20+8) within Mail::SpamAssassin::Plugin::URIDetail::BEGIN@73 which was called:
# once (20µs+8µs) by Mail::SpamAssassin::PluginHandler::load_plugin at line 73 # spent 27µs making 1 call to Mail::SpamAssassin::Plugin::URIDetail::BEGIN@73
# spent 8µs making 1 call to strict::import |
74 | 2 | 59µs | 2 | 81µs | # spent 51µs (21+30) within Mail::SpamAssassin::Plugin::URIDetail::BEGIN@74 which was called:
# once (21µs+30µs) by Mail::SpamAssassin::PluginHandler::load_plugin at line 74 # spent 51µs making 1 call to Mail::SpamAssassin::Plugin::URIDetail::BEGIN@74
# spent 30µs making 1 call to warnings::import |
75 | 2 | 68µs | 2 | 60µs | # spent 43µs (27+16) within Mail::SpamAssassin::Plugin::URIDetail::BEGIN@75 which was called:
# once (27µs+16µs) by Mail::SpamAssassin::PluginHandler::load_plugin at line 75 # spent 43µs making 1 call to Mail::SpamAssassin::Plugin::URIDetail::BEGIN@75
# spent 16µs making 1 call to bytes::import |
76 | 2 | 72µs | 2 | 211µs | # spent 119µs (27+92) within Mail::SpamAssassin::Plugin::URIDetail::BEGIN@76 which was called:
# once (27µs+92µs) by Mail::SpamAssassin::PluginHandler::load_plugin at line 76 # spent 119µs making 1 call to Mail::SpamAssassin::Plugin::URIDetail::BEGIN@76
# spent 92µs making 1 call to re::import |
77 | |||||
78 | 2 | 2.38ms | 2 | 207µs | # spent 118µs (30+89) within Mail::SpamAssassin::Plugin::URIDetail::BEGIN@78 which was called:
# once (30µs+89µs) by Mail::SpamAssassin::PluginHandler::load_plugin at line 78 # spent 118µs making 1 call to Mail::SpamAssassin::Plugin::URIDetail::BEGIN@78
# spent 89µs making 1 call to vars::import |
79 | 1 | 13µs | @ISA = qw(Mail::SpamAssassin::Plugin); | ||
80 | |||||
81 | # constructor | ||||
82 | # spent 305µs (70+235) within Mail::SpamAssassin::Plugin::URIDetail::new which was called:
# once (70µs+235µs) by Mail::SpamAssassin::PluginHandler::load_plugin at line 1 of (eval 87)[Mail/SpamAssassin/PluginHandler.pm:129] | ||||
83 | 1 | 2µs | my $class = shift; | ||
84 | 1 | 2µs | my $mailsaobject = shift; | ||
85 | |||||
86 | # some boilerplate... | ||||
87 | 1 | 2µs | $class = ref($class) || $class; | ||
88 | 1 | 14µs | 1 | 26µs | my $self = $class->SUPER::new($mailsaobject); # spent 26µs making 1 call to Mail::SpamAssassin::Plugin::new |
89 | 1 | 2µs | bless ($self, $class); | ||
90 | |||||
91 | 1 | 26µs | 1 | 57µs | $self->register_eval_rule("check_uri_detail"); # spent 57µs making 1 call to Mail::SpamAssassin::Plugin::register_eval_rule |
92 | |||||
93 | 1 | 7µs | 1 | 153µs | $self->set_config($mailsaobject->{conf}); # spent 153µs making 1 call to Mail::SpamAssassin::Plugin::URIDetail::set_config |
94 | |||||
95 | 1 | 16µs | return $self; | ||
96 | } | ||||
97 | |||||
98 | # spent 153µs (43+110) within Mail::SpamAssassin::Plugin::URIDetail::set_config which was called:
# once (43µs+110µs) by Mail::SpamAssassin::Plugin::URIDetail::new at line 93 | ||||
99 | 1 | 2µs | my ($self, $conf) = @_; | ||
100 | 1 | 2µs | my @cmds; | ||
101 | |||||
102 | 1 | 1µs | my $pluginobj = $self; # allow use inside the closure below | ||
103 | |||||
104 | push (@cmds, { | ||||
105 | setting => 'uri_detail', | ||||
106 | is_priv => 1, | ||||
107 | code => sub { | ||||
108 | my ($self, $key, $value, $line) = @_; | ||||
109 | |||||
110 | if ($value !~ /^(\S+)\s+(.+)$/) { | ||||
111 | return $Mail::SpamAssassin::Conf::INVALID_VALUE; | ||||
112 | } | ||||
113 | my $name = $1; | ||||
114 | my $def = $2; | ||||
115 | my $added_criteria = 0; | ||||
116 | |||||
117 | # if this matches a regex, it strips slashes | ||||
118 | while ($def =~ m{\b(\w+)\b\s*([\=\!]\~)\s*((?:/.*?/|m(\W).*?\4)[imsx]*)(?=\s|$)}g) { | ||||
119 | my $target = $1; | ||||
120 | my $op = $2; | ||||
121 | my $pattern = $3; | ||||
122 | |||||
123 | if ($target !~ /^(?:raw|type|cleaned|text|domain)$/) { | ||||
124 | return $Mail::SpamAssassin::Conf::INVALID_VALUE; | ||||
125 | } | ||||
126 | if ($conf->{parser}->is_delimited_regexp_valid($name, $pattern)) { | ||||
127 | $pattern = $pluginobj->make_qr($pattern); | ||||
128 | } | ||||
129 | else { | ||||
130 | return $Mail::SpamAssassin::Conf::INVALID_VALUE; | ||||
131 | } | ||||
132 | |||||
133 | dbg("config: uri_detail adding ($target $op /$pattern/) to $name"); | ||||
134 | $conf->{parser}->{conf}->{uri_detail}->{$name}->{$target} = | ||||
135 | [$op, $pattern]; | ||||
136 | $added_criteria = 1; | ||||
137 | } | ||||
138 | |||||
139 | if ($added_criteria) { | ||||
140 | dbg("config: uri_detail added $name\n"); | ||||
141 | $conf->{parser}->add_test($name, 'check_uri_detail()', $Mail::SpamAssassin::Conf::TYPE_BODY_EVALS); | ||||
142 | } | ||||
143 | else { | ||||
144 | warn "config: failed to add invalid rule $name"; | ||||
145 | return $Mail::SpamAssassin::Conf::INVALID_VALUE; | ||||
146 | } | ||||
147 | } | ||||
148 | 1 | 22µs | }); | ||
149 | |||||
150 | 1 | 17µs | 1 | 110µs | $conf->{parser}->register_commands(\@cmds); # spent 110µs making 1 call to Mail::SpamAssassin::Conf::Parser::register_commands |
151 | } | ||||
152 | |||||
153 | sub check_uri_detail { | ||||
154 | my ($self, $permsg) = @_; | ||||
155 | |||||
156 | my %uri_detail = %{ $permsg->get_uri_detail_list() }; | ||||
157 | |||||
158 | while (my ($raw, $info) = each %uri_detail) { | ||||
159 | my $test = $permsg->{current_rule_name}; | ||||
160 | |||||
161 | dbg("uri: running $test\n"); | ||||
162 | |||||
163 | my $rule = $permsg->{conf}->{uri_detail}->{$test}; | ||||
164 | |||||
165 | if (exists $rule->{raw}) { | ||||
166 | my($op,$patt) = @{$rule->{raw}}; | ||||
167 | if ( ($op eq '=~' && $raw =~ /$patt/) || | ||||
168 | ($op eq '!~' && $raw !~ /$patt/) ) { | ||||
169 | dbg("uri: raw matched: '%s' %s /%s/", $raw,$op,$patt); | ||||
170 | } else { | ||||
171 | next; | ||||
172 | } | ||||
173 | } | ||||
174 | |||||
175 | if (exists $rule->{type}) { | ||||
176 | next unless $info->{types}; | ||||
177 | my($op,$patt) = @{$rule->{type}}; | ||||
178 | my $match; | ||||
179 | for my $text (keys %{ $info->{types} }) { | ||||
180 | if ( ($op eq '=~' && $text =~ /$patt/) || | ||||
181 | ($op eq '!~' && $text !~ /$patt/) ) { $match = $text; last } | ||||
182 | } | ||||
183 | next unless defined $match; | ||||
184 | dbg("uri: type matched: '%s' %s /%s/", $match,$op,$patt); | ||||
185 | } | ||||
186 | |||||
187 | if (exists $rule->{cleaned}) { | ||||
188 | next unless $info->{cleaned}; | ||||
189 | my($op,$patt) = @{$rule->{cleaned}}; | ||||
190 | my $match; | ||||
191 | for my $text (@{ $info->{cleaned} }) { | ||||
192 | if ( ($op eq '=~' && $text =~ /$patt/) || | ||||
193 | ($op eq '!~' && $text !~ /$patt/) ) { $match = $text; last } | ||||
194 | } | ||||
195 | next unless defined $match; | ||||
196 | dbg("uri: cleaned matched: '%s' %s /%s/", $match,$op,$patt); | ||||
197 | } | ||||
198 | |||||
199 | if (exists $rule->{text}) { | ||||
200 | next unless $info->{anchor_text}; | ||||
201 | my($op,$patt) = @{$rule->{text}}; | ||||
202 | my $match; | ||||
203 | for my $text (@{ $info->{anchor_text} }) { | ||||
204 | if ( ($op eq '=~' && $text =~ /$patt/) || | ||||
205 | ($op eq '!~' && $text !~ /$patt/) ) { $match = $text; last } | ||||
206 | } | ||||
207 | next unless defined $match; | ||||
208 | dbg("uri: text matched: '%s' %s /%s/", $match,$op,$patt); | ||||
209 | } | ||||
210 | |||||
211 | if (exists $rule->{domain}) { | ||||
212 | next unless $info->{domains}; | ||||
213 | my($op,$patt) = @{$rule->{domain}}; | ||||
214 | my $match; | ||||
215 | for my $text (keys %{ $info->{domains} }) { | ||||
216 | if ( ($op eq '=~' && $text =~ /$patt/) || | ||||
217 | ($op eq '!~' && $text !~ /$patt/) ) { $match = $text; last } | ||||
218 | } | ||||
219 | next unless defined $match; | ||||
220 | dbg("uri: domain matched: '%s' %s /%s/", $match,$op,$patt); | ||||
221 | } | ||||
222 | |||||
223 | if (would_log('dbg', 'rules') > 1) { | ||||
224 | dbg("uri: criteria for $test met"); | ||||
225 | } | ||||
226 | |||||
227 | $permsg->got_hit($test); | ||||
228 | |||||
229 | # reset hash | ||||
230 | keys %uri_detail; | ||||
231 | |||||
232 | return 0; | ||||
233 | } | ||||
234 | |||||
235 | return 0; | ||||
236 | } | ||||
237 | |||||
238 | # --------------------------------------------------------------------------- | ||||
239 | |||||
240 | # turn "/foobar/i" into qr/(?i)foobar/ | ||||
241 | sub make_qr { | ||||
242 | my ($self, $pattern) = @_; | ||||
243 | |||||
244 | my $re_delim; | ||||
245 | if ($pattern =~ s/^m(\W)//) { # m!foo/bar! | ||||
246 | $re_delim = $1; | ||||
247 | } else { # /foo\/bar/ or !foo/bar! | ||||
248 | $pattern =~ s/^(\W)//; $re_delim = $1; | ||||
249 | } | ||||
250 | if (!$re_delim) { | ||||
251 | return; | ||||
252 | } | ||||
253 | |||||
254 | $pattern =~ s/${re_delim}([imsx]*)$//; | ||||
255 | |||||
256 | my $mods = $1; | ||||
257 | if ($mods) { $pattern = "(?".$mods.")".$pattern; } | ||||
258 | |||||
259 | return qr/$pattern/; | ||||
260 | } | ||||
261 | |||||
262 | # --------------------------------------------------------------------------- | ||||
263 | |||||
264 | 1 | 15µs | 1; |