You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@spamassassin.apache.org by do...@apache.org on 2007/04/10 08:55:13 UTC
svn commit: r527045 - in /spamassassin/trunk:
lib/Mail/SpamAssassin/Plugin/ASN.pm rules/25_asn.cf
Author: dos
Date: Mon Apr 9 23:55:09 2007
New Revision: 527045
URL: http://svn.apache.org/viewvc?view=rev&rev=527045
Log:
Most of the ASN plugin re-written:
- remove potentially abusive duplicate lookup option
(if you *really* need to query for the same thing two or more times in
order to get a response, just duplicate the asn_lookup config lines)
- re-implement the asn_lookup as an actual config option instead of a header
eval function
- allow for queries against more than one zone at a time
- make the template tag names configurable
- register the query with AsyncLoop
- process the results via the callback rather than polling
Modified:
spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/ASN.pm
spamassassin/trunk/rules/25_asn.cf
Modified: spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/ASN.pm
URL: http://svn.apache.org/viewvc/spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/ASN.pm?view=diff&rev=527045&r1=527044&r2=527045
==============================================================================
--- spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/ASN.pm (original)
+++ spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/ASN.pm Mon Apr 9 23:55:09 2007
@@ -27,7 +27,9 @@
loadplugin Mail::SpamAssassin::Plugin::ASN
- header ASN_LOOKUP eval:asn_lookup('asn.routeviews.org', 2)
+ asn_lookup asn.routeviews.org _ASN_ _ASNCIDR_
+
+ add_header all ASN _ASN_ _ASNCIDR_
=head1 DESCRIPTION
@@ -40,12 +42,10 @@
=head1 TEMPLATE TAGS
-This plugin adds two tags, C<_ASN_> and C<_ASNCIDR_>, which can be
-used in places where such tags can usually be used. For example:
-
- add_header all ASN _ASN_ _ASNCIDR_
+This plugin allows you to create template tags containing the connecting
+IP's AS number and route info for that AS number.
-may add something like:
+The default config will add a header that looks like this:
X-Spam-ASN: AS24940 213.239.192.0/18
@@ -67,15 +67,6 @@
Bayes learning will probably trigger on the _ASNCIDR_ tag, but probably not
very well on the _ASN_ tag alone.
-B<Note> that the zone to lookup the ASN data in must be given as the
-first parameter to the asn_lookup eval function. This is especially
-important if you use a locally mirrored zone.
-
-B<Note> the second parameter to asn_lookup is the number of queries to start.
-This should be set to somewhere between 2 and 5 but may depend on your local
-nameserver configuration. If you run a local mirror, setting this to 1 should
-probably be enough.
-
=head1 SEE ALSO
http://www.routeviews.org/ - all data regarding routing, ASNs etc
@@ -108,103 +99,162 @@
my $self = $class->SUPER::new($mailsa);
bless ($self, $class);
- $self->register_eval_rule("asn_lookup");
+ $self->set_config($mailsa->{conf});
return $self;
}
-sub asn_lookup {
- my ($self, $scanner, $zone, $num_lookups) = @_;
- if (!$scanner->is_dns_available()) {
- $self->{dns_not_available} = 1;
- return;
- } else {
- # due to re-testing dns may become available after being unavailable
- $self->{dns_not_available} = 0;
- }
+###########################################################################
- # Default to empty strings; otherwise, the tags will be left as _ASN_
- # and _ASNCIDR_ which may confuse bayes learning, I suppose.
- $scanner->{tag_data}->{ASN} = '';
- $scanner->{tag_data}->{ASNCIDR} = '';
-
- # We need to grab this here since the check_tick event does not
- # get *our* name, but the name of whatever rule is currently
- # being worked on.
- $scanner->{myname} = $scanner->get_current_eval_rule_name();
-
- my $ip = '';
- foreach my $relay (@{$scanner->{relays_untrusted}}) {
- if ($relay->{ip_private}) {
- dbg("ASN: skipping untrusted relay $relay->{ip}, it's private");
- } else {
- $ip = $relay->{ip};
- last;
+sub set_config {
+ my ($self, $conf) = @_;
+ my @cmds = ();
+
+=head1 USER SETTINGS
+
+=over 4
+
+=item asn_lookup asn-zone.example.com [ _ASNTAG_ _ASNCIDRTAG_ ]
+
+Use this to lookup the ASN info for first external IP address in the specified
+zone and add the AS number to the first specified tag and routing info to the
+second specified tag.
+
+If no tags are specified the AS number will be added to the _ASN_ tag and the
+routing info will be added to the _ASNCIDR_ tag. You must specify either none
+or both of the tags. Tags must start and end with an underscore.
+
+If two or more I<asn_lookup>s use the same set of template tags, the results of
+their lookups will be appended to each other in the template tag values in no
+particular order. Duplicate results will be omitted when combining results.
+In a similar fashion, you can also use the same template tag for both the AS
+number tag and the routing info tag.
+
+Examples:
+
+ asn_lookup asn.routeviews.org
+
+ asn_lookup asn.routeviews.org _ASN_ _ASNCIDR_
+ asn_lookup myview.example.com _MYASN_ _MYASNCIDR_
+
+ asn_lookup asn.routeviews.org _COMBINEDASN_ _COMBINEDASNCIDR_
+ asn_lookup myview.example.com _COMBINEDASN_ _COMBINEDASNCIDR_
+
+ asn_lookup in1tag.example.net _ASNDATA_ _ASNDATA_
+
+=cut
+
+ push (@cmds, {
+ setting => 'asn_lookup',
+ code => sub {
+ my ($self, $key, $value, $line) = @_;
+ unless (defined $value && $value !~ /^$/) {
+ return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
+ }
+ unless ($value =~ /^(\S+?)\.?(?:\s+_(\S+)_\s+_(\S+)_)?$/) {
+ return $Mail::SpamAssassin::Conf::INVALID_VALUE;
+ }
+ my $zone = $1.'.';
+ my $asn_tag = (defined $2 ? $2 : 'ASN');
+ my $route_tag = (defined $3 ? $3 : 'ASNCIDR');
+
+ push @{$self->{main}->{conf}->{asnlookups}}, { zone=>$zone, asn_tag=>$asn_tag, route_tag=>$route_tag };
}
+ });
+
+ $conf->{parser}->register_commands(\@cmds);
+}
+
+# ---------------------------------------------------------------------------
+
+sub parsed_metadata {
+ my ($self, $opts) = @_;
+
+ my $scanner = $opts->{permsgstatus};
+ my $conf = $self->{main}->{conf};
+
+ unless ($conf->{asnlookups}) {
+ dbg("asn: no asn_lookup configured, skipping ASN lookups");
+ return; # no asn_lookups mean no tags need to be initialized
}
-
- if ($ip eq '') {
- dbg("ASN: $scanner->{myname}: No IP address from relays_external");
- return;
- } else {
- dbg("ASN: $scanner->{myname}: external IP address $ip");
- }
-
- my $lookup = '';
- if ($ip =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/) {
- $lookup = "$4.$3.$2.$1.$zone";
- }
-
- if ($lookup eq '') {
- dbg("ASN: $scanner->{myname}: $ip does not look like an IP address");
- return;
+
+ # get reversed IP-quad of last external relay to lookup
+ # don't return until we've initialized the template tags
+ my $reversed_ip_quad;
+ my $relay = $scanner->{relays_external}->[0];
+ if (!$scanner->is_dns_available()) {
+ dbg("asn: DNS is not available, skipping ASN checks");
+ } elsif ($relay->{ip_private}) {
+ dbg("asn: first external relay is a private IP, skipping ASN check");
} else {
- dbg("ASN: $scanner->{myname}: will look up $lookup");
+ if (defined $relay->{ip} && $relay->{ip} =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/) {
+ $reversed_ip_quad = "$4.$3.$2.$1";
+ dbg("asn: using first external relay IP for lookups: $relay->{ip}");
+ } else {
+ dbg("asn: could not parse IP from first external relay, skipping ASN check");
+ }
}
- # DNS magic - start the lookup and have the Net::DNS package
- # store the result in our own structure
- for (my $i = 0; $i < $num_lookups; $i++) {
- $scanner->{main}->{resolver}->bgsend($lookup, 'TXT', undef, sub {
+ # random note: we use arrays and array indices rather than hashes and hash
+ # keys in case someone wants the same zone added to multiple sets of tags
+ my $index = 0;
+ foreach my $entry (@{$conf->{asnlookups}}) {
+ # initialize the tag data so that if no result is returned from the DNS
+ # query we won't end up with a missing tag
+ unless (defined $scanner->{tag_data}->{$entry->{asn_tag}}) {
+ $scanner->{tag_data}->{$entry->{asn_tag}} = '';
+ }
+ unless (defined $scanner->{tag_data}->{$entry->{route_tag}}) {
+ $scanner->{tag_data}->{$entry->{route_tag}} = '';
+ }
+ next unless $reversed_ip_quad;
+
+ # do the DNS query, have the callback process the result rather than poll for them later
+ my $zone_index = $index;
+ my $id = $scanner->{main}->{resolver}->bgsend("${reversed_ip_quad}.$entry->{zone}", 'TXT', undef, sub {
my $pkt = shift;
- my $id = shift;
- $scanner->{asnlookup} = $pkt;
+ $self->process_dns_result($scanner, $pkt, $zone_index);
});
+
+ $scanner->{async}->start_lookup({ key=>"asnlookup-${zone_index}-$entry->{zone}", id=>$id, type=>'TXT' });
+ dbg("asn: launched DNS TXT query for ${reversed_ip_quad}.$entry->{zone} in background");
+
+ $index++;
}
-
- return;
}
-sub check_tick {
- my ($self, $opts) = @_;
-
- return if ($self->{dns_not_available});
+sub process_dns_result {
+ my ($self, $scanner, $response, $zone_index) = @_;
- my $pms = $opts->{permsgstatus};
+ my $conf = $self->{main}->{conf};
- # This will be defined if Net::DNS had something to deliver (see
- # ->bgsend() in sub asn_lookup() above)
- if ($pms->{asnlookup}) {
-
- # The regular Net::DNS dance around RRs; make sure to delete
- # the asnlookup structure, otherwise we would re-do on each
- # call of check_tick
- my $packet = delete $pms->{asnlookup};
- my @answer = $packet->answer;
- foreach my $rr (@answer) {
- dbg("ASN: $pms->{myname}: lookup result packet: " . $rr->string);
- if ($rr->type eq 'TXT') {
- my @items = split(/ /, $rr->txtdata);
- $pms->{tag_data}->{ASN} = sprintf('AS%s', $items[0]);
- my $c = sprintf('%s/%s ', $items[1], $items[2]);
- if (!($pms->{tag_data}->{ASNCIDR} =~ /$c/)) {
- $pms->{tag_data}->{ASNCIDR} .= $c;
+ my $zone = $conf->{asnlookups}[$zone_index]->{zone};
+ my $asn_tag = $conf->{asnlookups}[$zone_index]->{asn_tag};
+ my $route_tag = $conf->{asnlookups}[$zone_index]->{route_tag};
+
+ my @answer = $response->answer;
+
+ foreach my $rr (@answer) {
+ dbg("asn: $zone: lookup result packet: '".$rr->string."'");
+ if ($rr->type eq 'TXT') {
+ my @items = split(/ /, $rr->txtdata);
+ unless ($#items == 2) {
+ dbg("asn: TXT query response format unknown, ignoring zone: $zone response: '".$rr->txtdata."'");
+ next;
+ }
+ unless ($scanner->{tag_data}->{$asn_tag} =~ /\bAS$items[0]\b/) {
+ if ($scanner->{tag_data}->{$asn_tag}) {
+ $scanner->{tag_data}->{$asn_tag} .= " AS$items[0]";
+ } else {
+ $scanner->{tag_data}->{$asn_tag} = "AS$items[0]";
+ }
+ }
+ unless ($scanner->{tag_data}->{$route_tag} =~ m{\b$items[1]/$items[2]\b}) {
+ if ($scanner->{tag_data}->{$route_tag}) {
+ $scanner->{tag_data}->{$route_tag} .= " $items[1]/$items[2]";
+ } else {
+ $scanner->{tag_data}->{$route_tag} = "$items[1]/$items[2]";
}
-
- # We are calling the internal _handle_hit because we want the
- # score to be zero, but still show it up in the report
- # 20061217 - disabled,will give score anyway :/
- # $pms->_handle_hit($pms->{myname}, 0.001, sprintf('AS%s %s/%s', @items));
}
}
}
Modified: spamassassin/trunk/rules/25_asn.cf
URL: http://svn.apache.org/viewvc/spamassassin/trunk/rules/25_asn.cf?view=diff&rev=527045&r1=527044&r2=527045
==============================================================================
--- spamassassin/trunk/rules/25_asn.cf (original)
+++ spamassassin/trunk/rules/25_asn.cf Mon Apr 9 23:55:09 2007
@@ -24,12 +24,12 @@
###########################################################################
# Requires the Mail::SpamAssassin::Plugin::ASN plugin be loaded.
-# This plugin defines two tags, "_ASN_" and "_ASNCIDR_", which will be
-# populated with ASN and route info, respectively. See the plugin's POD
-# docs for more info.
+
+# This plugin queries asn.routeviews.org for ASN and route info and adds a
+# header containing the data returned so that it can be used by the bayes
+# tokenizer. See the plugin's POD docs for more info.
ifplugin Mail::SpamAssassin::Plugin::ASN
- header ASN_LOOKUP eval:asn_lookup('asn.routeviews.org', 2)
- # Add a header so that bayes can tokenize the ASN data
- add_header all ASN _ASN_ _ASNCIDR_
+ asn_lookup asn.routeviews.org _ASN_ _ASNCIDR_
+ add_header all ASN _ASN_ _ASNCIDR_
endif # Mail::SpamAssassin::Plugin::ASN