line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
# SpamAssassin - ASN Lookup Plugin |
2
|
|
|
|
|
|
|
# |
3
|
|
|
|
|
|
|
# <@LICENSE> |
4
|
|
|
|
|
|
|
# Licensed to the Apache Software Foundation (ASF) under one or more |
5
|
|
|
|
|
|
|
# contributor license agreements. See the NOTICE file distributed with |
6
|
|
|
|
|
|
|
# this work for additional information regarding copyright ownership. |
7
|
|
|
|
|
|
|
# The ASF licenses this file to you under the Apache License, Version 2.0 |
8
|
|
|
|
|
|
|
# (the "License"); you may not use this file except in compliance with |
9
|
|
|
|
|
|
|
# the License. You may obtain a copy of the License at: |
10
|
|
|
|
|
|
|
# |
11
|
|
|
|
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0 |
12
|
|
|
|
|
|
|
# |
13
|
|
|
|
|
|
|
# Unless required by applicable law or agreed to in writing, software |
14
|
|
|
|
|
|
|
# distributed under the License is distributed on an "AS IS" BASIS, |
15
|
|
|
|
|
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
16
|
|
|
|
|
|
|
# See the License for the specific language governing permissions and |
17
|
|
|
|
|
|
|
# limitations under the License. |
18
|
|
|
|
|
|
|
# </@LICENSE> |
19
|
|
|
|
|
|
|
# |
20
|
|
|
|
|
|
|
########################################################################### |
21
|
|
|
|
|
|
|
# |
22
|
|
|
|
|
|
|
# Originated by Matthias Leisi, 2006-12-15 (SpamAssassin enhancement #4770). |
23
|
|
|
|
|
|
|
# Modifications by D. Stussy, 2010-12-15 (SpamAssassin enhancement #6484): |
24
|
|
|
|
|
|
|
# |
25
|
|
|
|
|
|
|
# Since SA 3.4.0 a fixed text prefix (such as AS) to each ASN is configurable |
26
|
|
|
|
|
|
|
# through an asn_prefix directive. Its value is 'AS' by default for backward |
27
|
|
|
|
|
|
|
# compatibility with SA 3.3.*, but is rather redundant and can be set to an |
28
|
|
|
|
|
|
|
# empty string for clarity if desired. |
29
|
|
|
|
|
|
|
# |
30
|
|
|
|
|
|
|
# Enhanced TXT-RR decoding for alternative formats from other DNS zones. |
31
|
|
|
|
|
|
|
# Some of the supported formats of TXT RR are (quoted strings here represent |
32
|
|
|
|
|
|
|
# individual string fields in a TXT RR): |
33
|
|
|
|
|
|
|
# "1103" "192.88.99.0" "24" |
34
|
|
|
|
|
|
|
# "559 1103 1239 1257 1299 | 192.88.99.0/24 | US | iana | 2001-06-01" |
35
|
|
|
|
|
|
|
# "192.88.99.0/24 | AS1103 | SURFnet, The Netherlands | 2002-10-15 | EU" |
36
|
|
|
|
|
|
|
# "15169 | 2a00:1450::/32 | IE | ripencc | 2009-10-05" |
37
|
|
|
|
|
|
|
# "as1103" |
38
|
|
|
|
|
|
|
# Multiple routes are sometimes provided by returning multiple TXT records |
39
|
|
|
|
|
|
|
# (e.g. from cymru.com). This form of a response is handled as well. |
40
|
|
|
|
|
|
|
# |
41
|
|
|
|
|
|
|
# Some zones also support IPv6 lookups, for example: |
42
|
|
|
|
|
|
|
# asn_lookup_ipv6 origin6.asn.cymru.com [_ASN_ _ASNCIDR_] |
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
=head1 NAME |
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
Mail::SpamAssassin::Plugin::ASN - SpamAssassin plugin to look up the |
47
|
|
|
|
|
|
|
Autonomous System Number (ASN) of the connecting IP address. |
48
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
=head1 SYNOPSIS |
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
loadplugin Mail::SpamAssassin::Plugin::ASN |
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
asn_lookup asn.routeviews.org _ASN_ _ASNCIDR_ |
54
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
asn_lookup_ipv6 origin6.asn.cymru.com _ASN_ _ASNCIDR_ |
56
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
add_header all ASN _ASN_ _ASNCIDR_ |
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
header TEST_AS1234 X-ASN =~ /^1234$/ |
60
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
=head1 DESCRIPTION |
62
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
This plugin uses DNS lookups to the services of an external DNS zone such |
64
|
|
|
|
|
|
|
as at C<http://www.routeviews.org/> to do the actual work. Please make |
65
|
|
|
|
|
|
|
sure that your use of the plugin does not overload their infrastructure - |
66
|
|
|
|
|
|
|
this generally means that B<you should not use this plugin in a |
67
|
|
|
|
|
|
|
high-volume environment> or that you should use a local mirror of the |
68
|
|
|
|
|
|
|
zone (see C<ftp://ftp.routeviews.org/dnszones/>). Other similar zones |
69
|
|
|
|
|
|
|
may also be used. |
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
=head1 TEMPLATE TAGS |
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
This plugin allows you to create template tags containing the connecting |
74
|
|
|
|
|
|
|
IP's AS number and route info for that AS number. |
75
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
The default config will add a header field that looks like this: |
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
X-Spam-ASN: AS24940 213.239.192.0/18 |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
where "24940" is the ASN and "213.239.192.0/18" is the route |
81
|
|
|
|
|
|
|
announced by that ASN where the connecting IP address came from. |
82
|
|
|
|
|
|
|
If the AS announces multiple networks (more/less specific), they will |
83
|
|
|
|
|
|
|
all be added to the C<_ASNCIDR_> tag, separated by spaces, eg: |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
X-Spam-ASN: AS1680 89.138.0.0/15 89.139.0.0/16 |
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
Note that the literal "AS" before the ASN in the _ASN_ tag is configurable |
88
|
|
|
|
|
|
|
through the I<asn_prefix> directive and may be set to an empty string. |
89
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
=head1 CONFIGURATION |
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
The standard ruleset contains a configuration that will add a header field |
93
|
|
|
|
|
|
|
containing ASN data to scanned messages. The bayes tokenizer will use the |
94
|
|
|
|
|
|
|
added header field for bayes calculations, and thus affect which BAYES_* rule |
95
|
|
|
|
|
|
|
will trigger for a particular message. |
96
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
B<Note> that in most cases you should not score on the ASN data directly. |
98
|
|
|
|
|
|
|
Bayes learning will probably trigger on the _ASNCIDR_ tag, but probably not |
99
|
|
|
|
|
|
|
very well on the _ASN_ tag alone. |
100
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
=head1 SEE ALSO |
102
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
http://www.routeviews.org/ - all data regarding routing, ASNs, etc.... |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
http://issues.apache.org/SpamAssassin/show_bug.cgi?id=4770 - |
106
|
|
|
|
|
|
|
SpamAssassin Issue #4770 concerning this plugin |
107
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
=head1 STATUS |
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
No in-depth analysis of the usefulness of bayes tokenization of ASN data has |
111
|
|
|
|
|
|
|
been performed. |
112
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
=cut |
114
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
package Mail::SpamAssassin::Plugin::ASN; |
116
|
|
|
|
|
|
|
|
117
|
19
|
|
|
19
|
|
141
|
use strict; |
|
19
|
|
|
|
|
43
|
|
|
19
|
|
|
|
|
664
|
|
118
|
19
|
|
|
19
|
|
118
|
use warnings; |
|
19
|
|
|
|
|
54
|
|
|
19
|
|
|
|
|
749
|
|
119
|
19
|
|
|
19
|
|
140
|
use re 'taint'; |
|
19
|
|
|
|
|
60
|
|
|
19
|
|
|
|
|
694
|
|
120
|
|
|
|
|
|
|
|
121
|
19
|
|
|
19
|
|
121
|
use Mail::SpamAssassin::Plugin; |
|
19
|
|
|
|
|
39
|
|
|
19
|
|
|
|
|
508
|
|
122
|
19
|
|
|
19
|
|
121
|
use Mail::SpamAssassin::Logger; |
|
19
|
|
|
|
|
59
|
|
|
19
|
|
|
|
|
1163
|
|
123
|
19
|
|
|
19
|
|
141
|
use Mail::SpamAssassin::Util qw(reverse_ip_address); |
|
19
|
|
|
|
|
46
|
|
|
19
|
|
|
|
|
957
|
|
124
|
19
|
|
|
19
|
|
130
|
use Mail::SpamAssassin::Dns; |
|
19
|
|
|
|
|
38
|
|
|
19
|
|
|
|
|
720
|
|
125
|
19
|
|
|
19
|
|
122
|
use Mail::SpamAssassin::Constants qw(:ip); |
|
19
|
|
|
|
|
67
|
|
|
19
|
|
|
|
|
44429
|
|
126
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
our @ISA = qw(Mail::SpamAssassin::Plugin); |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
our $txtdata_can_provide_a_list; |
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
my $IPV4_ADDRESS = IPV4_ADDRESS; |
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
sub new { |
134
|
60
|
|
|
60
|
1
|
262
|
my ($class, $mailsa) = @_; |
135
|
60
|
|
33
|
|
|
448
|
$class = ref($class) || $class; |
136
|
60
|
|
|
|
|
399
|
my $self = $class->SUPER::new($mailsa); |
137
|
60
|
|
|
|
|
173
|
bless ($self, $class); |
138
|
|
|
|
|
|
|
|
139
|
60
|
|
|
|
|
317
|
$self->set_config($mailsa->{conf}); |
140
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
#$txtdata_can_provide_a_list = Net::DNS->VERSION >= 0.69; |
142
|
|
|
|
|
|
|
#more robust version check from Damyan Ivanov - Bug 7095 |
143
|
60
|
|
|
|
|
1689
|
$txtdata_can_provide_a_list = version->parse(Net::DNS->VERSION) >= version->parse('0.69'); |
144
|
|
|
|
|
|
|
|
145
|
60
|
|
|
|
|
856
|
return $self; |
146
|
|
|
|
|
|
|
} |
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
########################################################################### |
149
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
sub set_config { |
151
|
60
|
|
|
60
|
0
|
209
|
my ($self, $conf) = @_; |
152
|
60
|
|
|
|
|
146
|
my @cmds; |
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
=head1 ADMINISTRATOR SETTINGS |
155
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
=over 4 |
157
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
=item asn_lookup asn-zone.example.com [ _ASNTAG_ _ASNCIDRTAG_ ] |
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
Use this to lookup the ASN info in the specified zone for the first external |
161
|
|
|
|
|
|
|
IPv4 address and add the AS number to the first specified tag and routing info |
162
|
|
|
|
|
|
|
to the second specified tag. |
163
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
If no tags are specified the AS number will be added to the _ASN_ tag and the |
165
|
|
|
|
|
|
|
routing info will be added to the _ASNCIDR_ tag. You must specify either none |
166
|
|
|
|
|
|
|
or both of the tag names. Tag names must start and end with an underscore. |
167
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
If two or more I<asn_lookup>s use the same set of template tags, the results of |
169
|
|
|
|
|
|
|
their lookups will be appended to each other in the template tag values in no |
170
|
|
|
|
|
|
|
particular order. Duplicate results will be omitted when combining results. |
171
|
|
|
|
|
|
|
In a similar fashion, you can also use the same template tag for both the AS |
172
|
|
|
|
|
|
|
number tag and the routing info tag. |
173
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
Examples: |
175
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
asn_lookup asn.routeviews.org |
177
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
asn_lookup asn.routeviews.org _ASN_ _ASNCIDR_ |
179
|
|
|
|
|
|
|
asn_lookup myview.example.com _MYASN_ _MYASNCIDR_ |
180
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
asn_lookup asn.routeviews.org _COMBINEDASN_ _COMBINEDASNCIDR_ |
182
|
|
|
|
|
|
|
asn_lookup myview.example.com _COMBINEDASN_ _COMBINEDASNCIDR_ |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
asn_lookup in1tag.example.net _ASNDATA_ _ASNDATA_ |
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
=item asn_lookup_ipv6 asn-zone6.example.com [_ASN_ _ASNCIDR_] |
187
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
Use specified zone for lookups of IPv6 addresses. If zone supports both |
189
|
|
|
|
|
|
|
IPv4 and IPv6 queries, use both asn_lookup and asn_lookup_ipv6 for the same |
190
|
|
|
|
|
|
|
zone. |
191
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
=item clear_asn_lookups |
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
Removes any previously declared I<asn_lookup> entries from a list of queries. |
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
=item asn_prefix 'prefix_string' (default: 'AS') |
197
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
The string specified in the argument is prepended to each ASN when storing |
199
|
|
|
|
|
|
|
it as a tag. This prefix is rather redundant, but its default value 'AS' |
200
|
|
|
|
|
|
|
is kept for backward compatibility with versions of SpamAssassin earlier |
201
|
|
|
|
|
|
|
than 3.4.0. A sensible setting is an empty string. The argument may be (but |
202
|
|
|
|
|
|
|
need not be) enclosed in single or double quotes for clarity. |
203
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
=back |
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
=cut |
207
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
push (@cmds, { |
209
|
|
|
|
|
|
|
setting => 'asn_lookup', |
210
|
|
|
|
|
|
|
is_admin => 1, |
211
|
|
|
|
|
|
|
code => sub { |
212
|
0
|
|
|
0
|
|
0
|
my ($conf, $key, $value, $line) = @_; |
213
|
0
|
0
|
0
|
|
|
0
|
unless (defined $value && $value !~ /^$/) { |
214
|
0
|
|
|
|
|
0
|
return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE; |
215
|
|
|
|
|
|
|
} |
216
|
0
|
|
|
|
|
0
|
local($1,$2,$3); |
217
|
0
|
0
|
|
|
|
0
|
unless ($value =~ /^(\S+?)\.?(?:\s+_(\S+)_\s+_(\S+)_)?$/) { |
218
|
0
|
|
|
|
|
0
|
return $Mail::SpamAssassin::Conf::INVALID_VALUE; |
219
|
|
|
|
|
|
|
} |
220
|
0
|
|
|
|
|
0
|
my ($zone, $asn_tag, $route_tag) = ($1, $2, $3); |
221
|
0
|
0
|
|
|
|
0
|
$asn_tag = 'ASN' if !defined $asn_tag; |
222
|
0
|
0
|
|
|
|
0
|
$route_tag = 'ASNCIDR' if !defined $route_tag; |
223
|
0
|
|
|
|
|
0
|
push @{$conf->{asnlookups}}, |
|
0
|
|
|
|
|
0
|
|
224
|
|
|
|
|
|
|
{ zone=>$zone, asn_tag=>$asn_tag, route_tag=>$route_tag }; |
225
|
|
|
|
|
|
|
} |
226
|
60
|
|
|
|
|
689
|
}); |
227
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
push (@cmds, { |
229
|
|
|
|
|
|
|
setting => 'asn_lookup_ipv6', |
230
|
|
|
|
|
|
|
is_admin => 1, |
231
|
|
|
|
|
|
|
code => sub { |
232
|
0
|
|
|
0
|
|
0
|
my ($conf, $key, $value, $line) = @_; |
233
|
0
|
0
|
0
|
|
|
0
|
unless (defined $value && $value !~ /^$/) { |
234
|
0
|
|
|
|
|
0
|
return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE; |
235
|
|
|
|
|
|
|
} |
236
|
0
|
|
|
|
|
0
|
local($1,$2,$3); |
237
|
0
|
0
|
|
|
|
0
|
unless ($value =~ /^(\S+?)\.?(?:\s+_(\S+)_\s+_(\S+)_)?$/) { |
238
|
0
|
|
|
|
|
0
|
return $Mail::SpamAssassin::Conf::INVALID_VALUE; |
239
|
|
|
|
|
|
|
} |
240
|
0
|
|
|
|
|
0
|
my ($zone, $asn_tag, $route_tag) = ($1, $2, $3); |
241
|
0
|
0
|
|
|
|
0
|
$asn_tag = 'ASN' if !defined $asn_tag; |
242
|
0
|
0
|
|
|
|
0
|
$route_tag = 'ASNCIDR' if !defined $route_tag; |
243
|
0
|
|
|
|
|
0
|
push @{$conf->{asnlookups_ipv6}}, |
|
0
|
|
|
|
|
0
|
|
244
|
|
|
|
|
|
|
{ zone=>$zone, asn_tag=>$asn_tag, route_tag=>$route_tag }; |
245
|
|
|
|
|
|
|
} |
246
|
60
|
|
|
|
|
527
|
}); |
247
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
push (@cmds, { |
249
|
|
|
|
|
|
|
setting => 'clear_asn_lookups', |
250
|
|
|
|
|
|
|
is_admin => 1, |
251
|
|
|
|
|
|
|
type => $Mail::SpamAssassin::Conf::CONF_TYPE_NOARGS, |
252
|
|
|
|
|
|
|
code => sub { |
253
|
0
|
|
|
0
|
|
0
|
my ($conf, $key, $value, $line) = @_; |
254
|
0
|
0
|
0
|
|
|
0
|
if (defined $value && $value ne '') { |
255
|
0
|
|
|
|
|
0
|
return $Mail::SpamAssassin::Conf::INVALID_VALUE; |
256
|
|
|
|
|
|
|
} |
257
|
0
|
|
|
|
|
0
|
delete $conf->{asnlookups}; |
258
|
0
|
|
|
|
|
0
|
delete $conf->{asnlookups_ipv6}; |
259
|
|
|
|
|
|
|
} |
260
|
60
|
|
|
|
|
498
|
}); |
261
|
|
|
|
|
|
|
|
262
|
|
|
|
|
|
|
push (@cmds, { |
263
|
|
|
|
|
|
|
setting => 'asn_prefix', |
264
|
|
|
|
|
|
|
type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING, |
265
|
|
|
|
|
|
|
default => 'AS', |
266
|
|
|
|
|
|
|
code => sub { |
267
|
0
|
|
|
0
|
|
0
|
my ($conf, $key, $value, $line) = @_; |
268
|
0
|
0
|
|
|
|
0
|
$value = '' if !defined $value; |
269
|
0
|
|
|
|
|
0
|
local($1,$2); |
270
|
0
|
0
|
|
|
|
0
|
$value = $2 if $value =~ /^(['"])(.*)\1\z/; # strip quotes if any |
271
|
0
|
|
|
|
|
0
|
$conf->{$key} = $value; # keep tainted |
272
|
|
|
|
|
|
|
} |
273
|
60
|
|
|
|
|
527
|
}); |
274
|
|
|
|
|
|
|
|
275
|
60
|
|
|
|
|
328
|
$conf->{parser}->register_commands(\@cmds); |
276
|
|
|
|
|
|
|
} |
277
|
|
|
|
|
|
|
|
278
|
|
|
|
|
|
|
# --------------------------------------------------------------------------- |
279
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
sub parsed_metadata { |
281
|
81
|
|
|
81
|
1
|
245
|
my ($self, $opts) = @_; |
282
|
|
|
|
|
|
|
|
283
|
81
|
|
|
|
|
194
|
my $pms = $opts->{permsgstatus}; |
284
|
81
|
|
|
|
|
208
|
my $conf = $self->{main}->{conf}; |
285
|
|
|
|
|
|
|
|
286
|
81
|
100
|
|
|
|
277
|
if (!$pms->is_dns_available()) { |
287
|
77
|
|
|
|
|
320
|
dbg("asn: DNS is not available, skipping ASN checks"); |
288
|
77
|
|
|
|
|
263
|
return; |
289
|
|
|
|
|
|
|
} |
290
|
|
|
|
|
|
|
|
291
|
4
|
50
|
33
|
|
|
31
|
if (!$conf->{asnlookups} && !$conf->{asnlookups_ipv6}) { |
292
|
4
|
|
|
|
|
17
|
dbg("asn: no asn_lookups configured, skipping ASN lookups"); |
293
|
4
|
|
|
|
|
13
|
return; |
294
|
|
|
|
|
|
|
} |
295
|
|
|
|
|
|
|
|
296
|
|
|
|
|
|
|
# initialize the tag data so that if no result is returned from the DNS |
297
|
|
|
|
|
|
|
# query we won't end up with a missing tag. Don't use $pms->set_tag() |
298
|
|
|
|
|
|
|
# here to avoid triggering any tag-dependent action unnecessarily |
299
|
0
|
0
|
|
|
|
|
if ($conf->{asnlookups}) { |
300
|
0
|
|
|
|
|
|
foreach my $entry (@{$conf->{asnlookups}}) { |
|
0
|
|
|
|
|
|
|
301
|
0
|
|
0
|
|
|
|
$pms->{tag_data}->{$entry->{asn_tag}} ||= ''; |
302
|
0
|
|
0
|
|
|
|
$pms->{tag_data}->{$entry->{route_tag}} ||= ''; |
303
|
|
|
|
|
|
|
} |
304
|
|
|
|
|
|
|
} |
305
|
0
|
0
|
|
|
|
|
if ($conf->{asnlookups_ipv6}) { |
306
|
0
|
|
|
|
|
|
foreach my $entry (@{$conf->{asnlookups_ipv6}}) { |
|
0
|
|
|
|
|
|
|
307
|
0
|
|
0
|
|
|
|
$pms->{tag_data}->{$entry->{asn_tag}} ||= ''; |
308
|
0
|
|
0
|
|
|
|
$pms->{tag_data}->{$entry->{route_tag}} ||= ''; |
309
|
|
|
|
|
|
|
} |
310
|
|
|
|
|
|
|
} |
311
|
|
|
|
|
|
|
|
312
|
|
|
|
|
|
|
# get reversed IP address of last external relay to lookup |
313
|
|
|
|
|
|
|
# don't return until we've initialized the template tags |
314
|
0
|
|
|
|
|
|
my $relay = $pms->{relays_external}->[0]; |
315
|
0
|
0
|
|
|
|
|
if (!defined $relay) { |
|
|
0
|
|
|
|
|
|
316
|
0
|
|
|
|
|
|
dbg("asn: no first external relay IP available, skipping ASN check"); |
317
|
0
|
|
|
|
|
|
return; |
318
|
|
|
|
|
|
|
} elsif ($relay->{ip_private}) { |
319
|
0
|
|
|
|
|
|
dbg("asn: first external relay is a private IP, skipping ASN check"); |
320
|
0
|
|
|
|
|
|
return; |
321
|
|
|
|
|
|
|
} |
322
|
|
|
|
|
|
|
|
323
|
0
|
|
|
|
|
|
my $ip = $relay->{ip}; |
324
|
0
|
|
|
|
|
|
my $reversed_ip = reverse_ip_address($ip); |
325
|
0
|
0
|
|
|
|
|
if (defined $reversed_ip) { |
326
|
0
|
|
|
|
|
|
dbg("asn: using first external relay IP for lookups: %s", $ip); |
327
|
|
|
|
|
|
|
} else { |
328
|
0
|
|
|
|
|
|
dbg("asn: could not parse first external relay IP: %s, skipping", $ip); |
329
|
0
|
|
|
|
|
|
return; |
330
|
|
|
|
|
|
|
} |
331
|
|
|
|
|
|
|
|
332
|
0
|
|
|
|
|
|
my $lookup_zone; |
333
|
0
|
0
|
|
|
|
|
if ($ip =~ /^$IPV4_ADDRESS$/o) { |
334
|
0
|
0
|
|
|
|
|
if (!defined $conf->{asnlookups}) { |
335
|
0
|
|
|
|
|
|
dbg("asn: asn_lookup for IPv4 not defined, skipping"); |
336
|
0
|
|
|
|
|
|
return; |
337
|
|
|
|
|
|
|
} |
338
|
0
|
|
|
|
|
|
$lookup_zone = "asnlookups"; |
339
|
|
|
|
|
|
|
} else { |
340
|
0
|
0
|
|
|
|
|
if (!defined $conf->{asnlookups_ipv6}) { |
341
|
0
|
|
|
|
|
|
dbg("asn: asn_lookup_ipv6 for IPv6 not defined, skipping"); |
342
|
0
|
|
|
|
|
|
return; |
343
|
|
|
|
|
|
|
} |
344
|
0
|
|
|
|
|
|
$lookup_zone = "asnlookups_ipv6"; |
345
|
|
|
|
|
|
|
} |
346
|
|
|
|
|
|
|
|
347
|
|
|
|
|
|
|
# we use arrays and array indices rather than hashes and hash keys |
348
|
|
|
|
|
|
|
# in case someone wants the same zone added to multiple sets of tags |
349
|
0
|
|
|
|
|
|
my $index = 0; |
350
|
0
|
|
|
|
|
|
foreach my $entry (@{$conf->{$lookup_zone}}) { |
|
0
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
# do the DNS query, have the callback process the result |
352
|
0
|
|
|
|
|
|
my $zone_index = $index; |
353
|
0
|
|
|
|
|
|
my $zone = $reversed_ip . '.' . $entry->{zone}; |
354
|
0
|
|
|
|
|
|
my $key = "asnlookup-${lookup_zone}-${zone_index}-".$entry->{zone}; |
355
|
|
|
|
|
|
|
my $ent = $pms->{async}->bgsend_and_start_lookup($zone, 'TXT', undef, |
356
|
|
|
|
|
|
|
{ type => 'ASN', key => $key, zone => $lookup_zone }, |
357
|
0
|
|
|
0
|
|
|
sub { my($ent, $pkt) = @_; |
358
|
0
|
|
|
|
|
|
$self->process_dns_result($pms, $pkt, $zone_index, $lookup_zone) }, |
359
|
|
|
|
|
|
|
master_deadline => $pms->{master_deadline} |
360
|
0
|
|
|
|
|
|
); |
361
|
0
|
0
|
|
|
|
|
$pms->register_async_rule_start($key) if $ent; |
362
|
0
|
|
|
|
|
|
$index++; |
363
|
|
|
|
|
|
|
} |
364
|
|
|
|
|
|
|
} |
365
|
|
|
|
|
|
|
|
366
|
|
|
|
|
|
|
# |
367
|
|
|
|
|
|
|
# TXT-RR format of response: |
368
|
|
|
|
|
|
|
# 3 fields, each as one TXT RR <character-string> (RFC 1035): ASN IP MASK |
369
|
|
|
|
|
|
|
# The latter two fields are combined to create a CIDR. |
370
|
|
|
|
|
|
|
# or: At least 2 fields made of a single or multiple |
371
|
|
|
|
|
|
|
# <character-string>s, fields are separated by a vertical bar. |
372
|
|
|
|
|
|
|
# They will be the ASN and CIDR fields in any order. |
373
|
|
|
|
|
|
|
# If only one field is returned, it is the ASN. There will |
374
|
|
|
|
|
|
|
# be no CIDR field in that case. |
375
|
|
|
|
|
|
|
# |
376
|
|
|
|
|
|
|
sub process_dns_result { |
377
|
0
|
|
|
0
|
0
|
|
my ($self, $pms, $pkt, $zone_index, $lookup_zone) = @_; |
378
|
|
|
|
|
|
|
|
379
|
0
|
|
|
|
|
|
my $conf = $self->{main}->{conf}; |
380
|
|
|
|
|
|
|
|
381
|
0
|
|
|
|
|
|
my $zone = $conf->{$lookup_zone}[$zone_index]->{zone}; |
382
|
0
|
|
|
|
|
|
my $asn_tag = $conf->{$lookup_zone}[$zone_index]->{asn_tag}; |
383
|
0
|
|
|
|
|
|
my $route_tag = $conf->{$lookup_zone}[$zone_index]->{route_tag}; |
384
|
|
|
|
|
|
|
|
385
|
0
|
|
|
|
|
|
my($any_asn_updates, $any_route_updates, $tag_value); |
386
|
|
|
|
|
|
|
|
387
|
0
|
|
|
|
|
|
my(@asn_tag_data, %asn_tag_data_seen); |
388
|
0
|
|
|
|
|
|
$tag_value = $pms->get_tag($asn_tag); |
389
|
0
|
0
|
|
|
|
|
if (defined $tag_value) { |
390
|
0
|
|
|
|
|
|
my $prefix = $pms->{conf}->{asn_prefix}; |
391
|
0
|
0
|
0
|
|
|
|
if (defined $prefix && $prefix ne '') { |
392
|
|
|
|
|
|
|
# must strip prefix before splitting on whitespace |
393
|
0
|
|
|
|
|
|
$tag_value =~ s/(^| )\Q$prefix\E(?=\d+)/$1/gs; |
394
|
|
|
|
|
|
|
} |
395
|
0
|
|
|
|
|
|
@asn_tag_data = split(/ /,$tag_value); |
396
|
0
|
|
|
|
|
|
%asn_tag_data_seen = map(($_,1), @asn_tag_data); |
397
|
|
|
|
|
|
|
} |
398
|
|
|
|
|
|
|
|
399
|
0
|
|
|
|
|
|
my(@route_tag_data, %route_tag_data_seen); |
400
|
0
|
|
|
|
|
|
$tag_value = $pms->get_tag($route_tag); |
401
|
0
|
0
|
|
|
|
|
if (defined $tag_value) { |
402
|
0
|
|
|
|
|
|
@route_tag_data = split(/ /,$tag_value); |
403
|
0
|
|
|
|
|
|
%route_tag_data_seen = map(($_,1), @route_tag_data); |
404
|
|
|
|
|
|
|
} |
405
|
|
|
|
|
|
|
|
406
|
|
|
|
|
|
|
# NOTE: $pkt will be undef if the DNS query was aborted (e.g. timed out) |
407
|
0
|
0
|
|
|
|
|
my @answer = !defined $pkt ? () : $pkt->answer; |
408
|
|
|
|
|
|
|
|
409
|
0
|
|
|
|
|
|
foreach my $rr (@answer) { |
410
|
|
|
|
|
|
|
#dbg("asn: %s: lookup result packet: %s", $zone, $rr->string); |
411
|
0
|
0
|
|
|
|
|
next if $rr->type ne 'TXT'; |
412
|
0
|
0
|
|
|
|
|
my @strings = $txtdata_can_provide_a_list ? $rr->txtdata : |
413
|
|
|
|
|
|
|
$rr->char_str_list; # historical |
414
|
0
|
0
|
|
|
|
|
next if !@strings; |
415
|
0
|
0
|
|
|
|
|
for (@strings) { utf8::encode($_) if utf8::is_utf8($_) } |
|
0
|
|
|
|
|
|
|
416
|
|
|
|
|
|
|
|
417
|
0
|
|
|
|
|
|
my @items; |
418
|
0
|
0
|
0
|
|
|
|
if (@strings > 1 && join('',@strings) !~ m{\|}) { |
419
|
|
|
|
|
|
|
# routeviews.org style, multiple string fields in a TXT RR |
420
|
0
|
|
|
|
|
|
@items = @strings; |
421
|
0
|
0
|
0
|
|
|
|
if (@items >= 3 && $items[1] !~ m{/} && $items[2] =~ /^\d+\z/) { |
|
|
|
0
|
|
|
|
|
422
|
0
|
|
|
|
|
|
$items[1] .= '/' . $items[2]; # append the net mask length to route |
423
|
|
|
|
|
|
|
} |
424
|
|
|
|
|
|
|
} else { |
425
|
|
|
|
|
|
|
# cymru.com and spameatingmonkey.net style, or just a single field |
426
|
0
|
|
|
|
|
|
@items = split(/\s*\|\s*/, join(' ',@strings)); |
427
|
|
|
|
|
|
|
} |
428
|
|
|
|
|
|
|
|
429
|
0
|
|
|
|
|
|
my(@route_value, @asn_value); |
430
|
0
|
0
|
0
|
|
|
|
if (@items && $items[0] =~ /(?: (?:^|\s+) (?:AS)? \d+ )+ \z/xsi) { |
|
|
0
|
0
|
|
|
|
|
431
|
|
|
|
|
|
|
# routeviews.org and cymru.com style, ASN is the first field, |
432
|
|
|
|
|
|
|
# possibly a whitespace-separated list (e.g. cymru.com) |
433
|
0
|
|
|
|
|
|
@asn_value = split(' ',$items[0]); |
434
|
0
|
0
|
|
|
|
|
@route_value = split(' ',$items[1]) if @items >= 2; |
435
|
|
|
|
|
|
|
} elsif (@items > 1 && $items[1] =~ /(?: (?:^|\s+) (?:AS)? \d+ )+ \z/xsi) { |
436
|
|
|
|
|
|
|
# spameatingmonkey.net style, ASN is the second field |
437
|
0
|
|
|
|
|
|
@asn_value = split(' ',$items[1]); |
438
|
0
|
|
|
|
|
|
@route_value = split(' ',$items[0]); |
439
|
|
|
|
|
|
|
} else { |
440
|
0
|
|
|
|
|
|
dbg("asn: unparseable response: %s", join(' ', map("\"$_\"",@strings))); |
441
|
|
|
|
|
|
|
} |
442
|
|
|
|
|
|
|
|
443
|
0
|
|
|
|
|
|
foreach my $route (@route_value) { |
444
|
0
|
0
|
0
|
|
|
|
if (!defined $route || $route eq '') { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
445
|
|
|
|
|
|
|
# ignore, just in case |
446
|
|
|
|
|
|
|
} elsif ($route =~ m{/0+\z}) { |
447
|
|
|
|
|
|
|
# unassigned/unannounced address space |
448
|
|
|
|
|
|
|
} elsif ($route_tag_data_seen{$route}) { |
449
|
0
|
|
|
|
|
|
dbg("asn: %s duplicate route %s", $route_tag, $route); |
450
|
|
|
|
|
|
|
} else { |
451
|
0
|
|
|
|
|
|
dbg("asn: %s added route %s", $route_tag, $route); |
452
|
0
|
|
|
|
|
|
push(@route_tag_data, $route); |
453
|
0
|
|
|
|
|
|
$route_tag_data_seen{$route} = 1; |
454
|
0
|
|
|
|
|
|
$any_route_updates = 1; |
455
|
|
|
|
|
|
|
} |
456
|
|
|
|
|
|
|
} |
457
|
|
|
|
|
|
|
|
458
|
0
|
|
|
|
|
|
foreach my $asn (@asn_value) { |
459
|
0
|
|
|
|
|
|
$asn =~ s/^AS(?=\d+)//si; |
460
|
0
|
0
|
0
|
|
|
|
if (!$asn || $asn == 4294967295) { |
|
|
0
|
|
|
|
|
|
461
|
|
|
|
|
|
|
# unassigned/unannounced address space |
462
|
|
|
|
|
|
|
} elsif ($asn_tag_data_seen{$asn}) { |
463
|
0
|
|
|
|
|
|
dbg("asn: %s duplicate asn %s", $asn_tag, $asn); |
464
|
|
|
|
|
|
|
} else { |
465
|
0
|
|
|
|
|
|
dbg("asn: %s added asn %s", $asn_tag, $asn); |
466
|
0
|
|
|
|
|
|
push(@asn_tag_data, $asn); |
467
|
0
|
|
|
|
|
|
$asn_tag_data_seen{$asn} = 1; |
468
|
0
|
|
|
|
|
|
$any_asn_updates = 1; |
469
|
|
|
|
|
|
|
} |
470
|
|
|
|
|
|
|
} |
471
|
|
|
|
|
|
|
} |
472
|
|
|
|
|
|
|
|
473
|
0
|
0
|
0
|
|
|
|
if ($any_asn_updates && @asn_tag_data) { |
474
|
0
|
|
|
|
|
|
$pms->{msg}->put_metadata('X-ASN', join(' ',@asn_tag_data)); |
475
|
0
|
|
|
|
|
|
my $prefix = $pms->{conf}->{asn_prefix}; |
476
|
0
|
0
|
0
|
|
|
|
if (defined $prefix && $prefix ne '') { s/^/$prefix/ for @asn_tag_data } |
|
0
|
|
|
|
|
|
|
477
|
0
|
0
|
|
|
|
|
$pms->set_tag($asn_tag, |
478
|
|
|
|
|
|
|
@asn_tag_data == 1 ? $asn_tag_data[0] : \@asn_tag_data); |
479
|
|
|
|
|
|
|
} |
480
|
0
|
0
|
0
|
|
|
|
if ($any_route_updates && @route_tag_data) { |
481
|
|
|
|
|
|
|
# Bayes already has X-ASN, Route is pointless duplicate, skip |
482
|
|
|
|
|
|
|
#$pms->{msg}->put_metadata('X-ASN-Route', join(' ',@route_tag_data)); |
483
|
0
|
0
|
|
|
|
|
$pms->set_tag($route_tag, |
484
|
|
|
|
|
|
|
@route_tag_data == 1 ? $route_tag_data[0] : \@route_tag_data); |
485
|
|
|
|
|
|
|
} |
486
|
|
|
|
|
|
|
} |
487
|
|
|
|
|
|
|
|
488
|
|
|
|
|
|
|
# Version features |
489
|
0
|
|
|
0
|
0
|
|
sub has_asn_lookup_ipv6 { 1 } |
490
|
|
|
|
|
|
|
|
491
|
|
|
|
|
|
|
1; |