#!/usr/local/bin/perl # $Id: blq,v 1.15 2002/02/23 00:12:07 chip Exp $ # # blq - block list query # # See for latest version. # # Chip Rosenthal # # # Perl POD documentation at the end. # $0 =~ s!.*/!!; use strict; use Getopt::Std; use Net::hostent; use Socket; use vars qw($Usage %ZoneTags $DefaultId $ShowAddrFlag); %ZoneTags = ( # # IDs for zones hosted by mail-abuse.org (MAPS) # "rbl" => "blackholes.mail-abuse.org", "dul" => "dialups.mail-abuse.org", "rss" => "relays.mail-abuse.org", # uncomment below if you are an RBL+ subscriber # "rbl+" => "rbl-plus.mail-abuse.org", # alias for back-compatibility with original name "rrss" => "rss", # aggregate of all MAPS zones "maps" => [qw(rbl dul rss)], # Spamhaus Block List "sbl" => "sbl.spamhaus.org", # Relay Stop List "rsl" => "relays.visi.com", # # IDs for zones hosted by osirusoft.com # "relays" => "relays.osirusoft.com", "dialups" => "dialups.relays.osirusoft.com", "spamsites" => "spamsites.relays.osirusoft.com", "spamhaus" => "spamhaus.relays.osirusoft.com", "spews" => "spews.relays.osirusoft.com", "blocktest" => "blocktest.relays.osirusoft.com", "outputs" => "outputs.relays.osirusoft.com", # aggregate of all osirusoft.com zones ... except: # "blocktest" excluded ... it's not supposed to be used for filtering "osirusoft" => [qw(relays dialups spamsites spamhaus spews outputs)], # same as above ... for use in "all" aggregate ... except: # "spamhaus" excluded ... it's just an alternate feed of SBL # "blocktest" included ... "osirusoft2" => [qw(relays dialups spamsites spews blocktest outputs)], # # ORBS is defunct # "orbs" => [qw(inputs.orbs.org outputs.orbs.org delayed-outputs.orbs.org manual.orbs.org spamsources.orbs.org untestable-netblocks.orbs.org spamsource-netblocks.orbs.org testing.orbs.org ok.orbs.org)], # # test all zones (except orbs) # "all" => [qw(maps sbl rsl osirusoft2)] ); $Usage = "usage: $0 [-a] [list-id-or-zone[,...]] host-name-or-address\n" . " (known list-ids = " . join(", ", keys %ZoneTags) . ")\n"; $DefaultId = "rbl"; # # Grab command line arguments. # my %opts; getopts('a', \%opts) or die $Usage; $ShowAddrFlag = $opts{'a'}; my @server_list = munge_server_spec((@ARGV > 1) ? shift(@ARGV) : $DefaultId); if (@ARGV != 1) { die $Usage } my @query_list = canonicalize_query(shift(@ARGV)); # # Iterate through the list of servers, performing the requested queries. # my($zone, $query, $h, @result, $ex); $ex = 0; foreach $zone (@server_list) { foreach $query (@query_list) { # output format is: # 206.47.217.48 : spews.relays.osirusoft.com : BLOCKED undef(@result); push(@result, $query->{ADDR}); if (defined($query->{NAME})) { push(@result, $query->{NAME}); } push(@result, ":", $zone, ":"); $h = query_zone($query->{ADDR}, $zone); if (defined($h)) { push(@result, "BLOCKED"); if ($ShowAddrFlag) { $_ = inet_ntoa(@{$h->addr_list}[0]); push(@result, "($_)"); } $ex = 2 } else { push(@result, "ok"); } print join(" ", @result), "\n"; } } exit($ex); # # munge_server_spec - given a $server_spec (which consists of a list of # block list server zones and IDs), generate the @server_list (which is # a formal list of block list server zones). # sub munge_server_spec { die q[usage: munge_server_spec($$server_spec)] unless (@_ == 1); my $server_spec = shift; my @server_list_result = ( ); my @spec_queue; my %did_server; my $t; @spec_queue = split(/[\s,]+/, $server_spec); while (@spec_queue > 0) { # grab next entry from the specification $_ = shift(@spec_queue); # if it might be a DNS zone then add it to our list if (/\./) { push(@server_list_result, $_) if (! $did_server{$_}++); next; } # see if it is a tag we recognize $t = $ZoneTags{$_} or die "$0: unknown block list \"$_\"\n", $Usage; # tags may refer to a single entry or an aggregate of entries if (ref($t) eq "ARRAY") { unshift(@spec_queue, @{$t}); } else { unshift(@spec_queue, $t); } } return @server_list_result; } # # canonicalize_query() - Given a $query of some form (either a hostname # or hostaddr) produce a list of ($hostname, $hostaddr) pairs. The list # typically will have only a single entry. The exception is if a hostname # resolves to multiple addresses. # sub canonicalize_query { die q[usage: canonicalize_query($query)] unless (@_ == 1); my $query = shift; my($addr, $name, $h); my @ret = (); if ($query =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/) { # query specified as an address $addr = $query; $h = gethostbyaddr(inet_aton($addr)); $name = ($h ? $h->name : undef); push(@ret, {'NAME' => $name, 'ADDR' => $addr}); } else { # query specified as a hostname $name = $query; $h = gethostbyname($name) or die "$0: gethostbyname($name) failed\n"; foreach $addr (@{$h->addr_list}) { push(@ret, {'NAME' => $name, 'ADDR' => inet_ntoa($addr)}); } } return @ret; } # # query_zone() - Query for $addr within the block list published at $zone. # sub query_zone { die q[usage: query_zone($addr, $zone)] unless (@_ == 2); my($addr, $zone) = @_; # put dot on end to avoid searchlist $zone .= "." unless ($zone =~ /\.$/); $addr =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/ or die "$0: bad address \"$addr\"\n"; return gethostbyname("$4.$3.$2.$1.$zone"); } __END__ =head1 NAME blq - Inquire an email block list server. =head1 SYNOPSIS B I<-a> [list-id-or-zone[, ...]] host-name-or-address =head1 DESCRIPTION Several organizations publish mail abuse lists via DNS. B inquires those lists to determine if a particular host is present. The I selects which list to query. It may be the full DNS zone of the block list (such as "blackholes.mail-abuse.org"), one of a number of pre-defined IDs (see below), or a list (comma or space delimited) of these items. As distributed, the pre-defined set of IDs includes: List-Id List-Zone ------- -------------------- rbl blackholes.mail-abuse.org dul dialups.mail-abuse.org rss relays.mail-abuse.org rrss rss (for back compatibility) maps (all mail-abuse.org zones) sbl sbl.spamhaus.org rsl relays.visi.com relays relays.osirusoft.com dialups dialups.relays.osirusoft.com spamsites spamsites.relays.osirusoft.com spamhaus spamhaus.relays.osirusoft.com (alternate feed of SBL) spews spews.relays.osirusoft.com blocktest blocktest.relays.osirusoft.com (not for filtering) outputs outputs.relays.osirusoft.com osirusoft (all osirusoft.com zones, except blocktest) all (all the above) orbs (the set of (now defunct) orbs.org zones) If not specified, the default I is B. The I is the query to perform, specified either as a name or IP address. All the block lists are indexed by address, not name. Thus, a given hostname will be resolved to an address for the query. If a name resolves to multiple addresses, they all will be queried. The output contains three colon-delimited fields, and looks something like: blackholes.mail-abuse.org : 192.168.117.89 relay.spamhausen.com : BLOCKED The first field lists the zone queried. The second field lists the query: the host address followed by the name it resolves to. The third field lists the result: "ok" if the host is not listed or "BLOCKED" if it is. Some block lists provide additional information on a listed entry. This information is encoded as an IP address. The B<-a> option displays the address codes encountered for blocked entries. =head1 SEE ALSO http://mail-abuse.org/rbl/ http://mail-abuse.org/dul/ http://mail-abuse.org/rss/ http://www.spamhaus.org/SBL/ http://relays.osirusoft.com/ http://relays.visi.com/ =head1 DIAGNOSTICS An exit status of zero indicates the host was not listed ("ok"). An exit status of two indicates that it was listed ("BLOCKED"). Any other non-zero exit status is an error. =head1 BUGS Inclusion of a particular list in this utility should B be construed as an endorsement by the program author. I use some of these lists for email filtering. I believe some of these lists are evil. You should visit their web pages, read their policies, and decide for yourself. =head1 AUTHOR Chip Rosenthal $Id: blq,v 1.15 2002/02/23 00:12:07 chip Exp $ See http://www.unicom.com/sw/#blq for latest version.