#!/usr/local/bin/perl # $Id: blq,v 1.11 2001/05/10 18:56:33 chip Exp $ # # blq - block list query # # See for latest version. # # Chip Rosenthal # # # Perl POD documentation at the end. # $0 =~ s!.*/!!; use strict; use Net::hostent; use Socket; use vars qw($Usage %ZoneTags $DefaultId); %ZoneTags = ( # IDs that correspond to individual zones "rbl" => "blackholes.mail-abuse.org", "dul" => "dialups.mail-abuse.org", "rss" => "relays.mail-abuse.org", "orbs" => "inputs.orbs.org", # RBL+ subscribers may want to enable this # "rbl+" => "rbl-plus.mail-abuse.org", # old alias for compatibility "rrss" => "rss", # IDs that correspond to aggregates of zones "maps" => [qw(rbl dul rss)], "all-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)], "all" => [qw(rbl dul rss all-orbs)], ); $Usage = "usage: $0 [list-id-or-zone[,...]] host-name-or-address\n" . " (known list-ids = " . join(", ", keys %ZoneTags) . ")\n"; $DefaultId = "rbl"; # # Grab command line arguments. # my @ServerList = munge_server_spec(@ARGV > 1 ? shift(@ARGV) : $DefaultId); die $Usage unless (@ARGV == 1); my @QueryList = canonicalize_query(shift(@ARGV)); # # Iterate through the list of servers, performing the requested queries. # my $ex = 0; my($block_list_zone, $query, $r); foreach $block_list_zone (@ServerList) { foreach $query (@QueryList) { if (is_listed($query->{ADDR}, $block_list_zone)) { $r = "BLOCKED"; $ex = 2 } else { $r = "ok"; } if (defined($query->{NAME})) { @_ = ( $query->{ADDR}, $query->{NAME} ); } else { @_ = ( $query->{ADDR} ); } push(@_, ":", $block_list_zone, ":", $r); print join(" ", @_), "\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; } # # is_listed() - Determine whether $addr is contained in the block list # within the indicated $zone. # sub is_listed { die q[usage: is_listed($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 defined(gethostbyname("$4.$3.$2.$1.$zone")); } __END__ =head1 NAME blq - Inquire an email block list server. =head1 SYNOPSIS B [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 orbs inputs.orbs.org rrss rss (for back compatibility) all (all the above) maps rbl,dul,rss 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. =head1 SEE ALSO http://mail-abuse.org/rbl/ http://mail-abuse.org/dul/ http://mail-abuse.org/rss/ http://www.orbs.org/ =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 Even though I included support for ORBS and IMRSS, I do not support their use for email filtering. I do use the others, but I urge you to visit their web pages, read their policies, and decide for yourself. =head1 AUTHOR Chip Rosenthal $Id: blq,v 1.11 2001/05/10 18:56:33 chip Exp $ See http://www.unicom.com/sw/#blq for latest version.