package Sendmail::AccessDB;
#use DB_File;
use BerkeleyDB;
use strict;
use Carp;
BEGIN {
use Exporter ();
use vars qw ($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS
$sub_regex_lock $DB_FILE);
$VERSION = 1.01;
@ISA = qw (Exporter);
@EXPORT = qw ();
@EXPORT_OK = qw (spam_friend whitelisted lookup);
%EXPORT_TAGS = ();
$DB_FILE = '/etc/mail/access.db';
}
=head1 NAME
Sendmail::AccessDB - An interface to the Sendmail access.db list
=head1 SYNOPSIS
use Sendmail::AccessDB qw(spam_friend whitelisted);
$friend_or_hater = spam_friend('user@example.com');
$whitelisted = whitelisted('sender@example.com');
=head1 DESCRIPTION
This module is designed so that users of the Sendmail::Milter module (or
other Sendmail programmers) can ascertain if a user has elected to whitelist
themselves as a "spam friend" (where there should be no spam filtering on
mail to them) or, where spam-filtering is not the default, but an option, where
certain receipients have been labeled as "spam haters"
=head1 USAGE
use Sendmail::AccessDB qw(spam_friend);
$friend_or_hater = spam_friend('user@example.com');
Ordinarily, this will look for such things as "Spam:user@example.com",
"Spam:user@", etc., in the /etc/mail/access.db file. There is an optional
second argument "Category", which could be used if you wanted to enable
specific checks, for example, if you wanted to customize down to a per-check
basis, you might use:
$rbl_friend_or_hater = spam_friend('user@example.com',
'qualifier' => 'maps_rbl');
$dul_friend_or_hater = spam_friend('user@example.com',
'qualifier' => 'maps_dul');
Caution should be taken when defining your own categories, as they may
inadvertantly conflict with Sendmail-defined categories.
use Sendmail::AccessDB qw(whitelisted);
$whitelisted = whitelisted('sender@example.com');
$whitelisted_host = whitelisted('foo.example.com');
$whitelisted_addr = whitelisted('192.168.1.123');
Would check for appropriate whitelisting entries in access.db. Some lookups
might be ambiguous, for example:
$whitelisted = whitelisted('foobar');
where it is hard to know if that is supposed to be a hostname, or a sender.
whitelisted() accepts the 'type' argument, such as:
$whitelisted = whitelisted('foobar','type'=>'hostname');
$whitelisted = whitelisted('postmaster','type'=>'mail');
It's also possible to feed the qualifier argument, if necessary, for example,
to do:
$whitelisted = whitelisted('host.example.com','type'=>'hostname',
'qualifier' => 'Connect');
which would check to see if this host has an OK flag set for the Connect
qualifier.
There is also the generic "lookup", which, at its simplest, takes a single
argument:
$rc = lookup('host.example.com');
will do a lookup on host.example.com. But if you wanted to pay attention to
parent-domains, you might do:
$rc = lookup('host.example.com', 'type'=>'hostname');
but if you wanted to find out if 'host.example.com', or any of its parent
domains ('example.com' and 'com'), had a value in the "MyQual" qualifier, you
might do:
$rc = lookup('host.example.com','type'=>'hostname','qualifier'=>'MyQual');
which would look up, in order 'MyQual:host.example.com', 'MyQual:example.com',
and 'MyQual:com', returning the first (most specific) one found.
=head1 BUGS
None that I've found yet, but I'm sure they're there.
=head1 SUPPORT
Feel free to email me at <dredd@megacity.org>
=head1 AUTHOR
Derek J. Balling
CPAN ID: DREDD
dredd@megacity.org
http://www.megacity.org/software.html
=head1 COPYRIGHT
Copyright (c) 2001 Derek J. Balling. All rights reserved.
This program is free software; you can redistribute
it and/or modify it under the same terms as Perl itself.
The full text of the license can be found in the
LICENSE file included with this module.
=head1 SEE ALSO
perl(1).
=head1 PUBLIC METHODS
Each public function/method is described here.
These are how you should interact with this module.
=cut
=head2 spam_friend
Usage : $friend_or_hater = spam_friend($recipient,
['qualifier' => $category])
Purpose : Consults the /etc/mail/access.db to check for spamfriendliness
Returns : 'FRIEND','HATER', or undef (which would mean default
behavior for that site)
Argument : The recipient e-mail address and an optional qualifier if
the default of 'Spam' is not desired.
Throws :
Comments :
See Also :
=cut
sub spam_friend
{
my $address = shift;
my $qual = shift || 'Spam';
return lookup($address,'qualifier'=>$qual,'type'=>'mail');
}
=head2 whitelisted
Usage : whitelisted($value)
Purpose : Determine if an e-mail address, hostname, or IP address is
explicitly whitelisted, in that it contains an "OK" or "RELAY"
value in the database.
Returns : 0/1, true or false as to whether the argument is whitelisted
Argument : Either an email-address (e.g., foo@example.com), an IP address
(e.g., 10.200.1.230), or a hostname (e.g., mailhost.example.com)
as well as 'type' and 'qualifer' arguments (see lookup for greater
detail)
Throws :
Comments : The code makes a pretty good attempt to figure out what type
of argument $value is, but it can be overriden using the 'type'
qualifier.
See Also :
=cut
sub whitelisted
{
my $address = shift;
my %args = @_;
if (! defined $args{'type'})
{
if ($address =~ /\@/)
{
$args{'type'} = 'mail';
}
elsif ($address =~ /^(?:\d+\.){3}\d+/)
{
$args{'type'} = 'ip';
}
elsif ($address =~ /^[A-Za-z0-9\-\.]+$/)
{
$args{'type'} = 'hostname';
}
}
my $lookup = lookup($address,%args);
return ( (defined $lookup) and
( ($lookup eq 'OK') or ($lookup eq 'RELAY') )
) ? 1 : 0;
}
=head2 lookup
Usage : lookup ($lookup_key,
'type'=>{'mail','ip','hostname'} , [optional]
'qualifier'=>'qualifier' [optional]
'file'=>'filename' [optional]
)
Purpose : Do a generic lookup on a $lookup_key in the access.db and
return the value found (or undef if not)
Returns : value in access.db or undef if not found
Argument : $lookup_key - mandatory. 'type'=>mail/ip/hostname will cause
lookups against all necessary lookups according to sendmail logic
(for things like hostname lookups where subdomains inherit
attributes of parent domains, etc.), 'qualifier'=>$q, where $q
will be preprended to the beginning of all lookups, (e.g., $q =
'Spam', lookup would be against 'Spam:lookup_value')
Throws :
Comments : If not using 'type', the 'qualifier' field can be mimicked by
simply looking for 'Qualifier:lookup'.
See Also :
=cut
sub lookup
{
my ($address,%args) = @_;
my @check_list;
if (defined $args{'type'})
{
if ($args{'type'} eq 'mail')
{
@check_list = _expand_email($address);
}
elsif ($args{'type'} eq 'hostname')
{
@check_list = _expand_hostname($address);
}
elsif ($args{'type'} eq 'ip')
{
@check_list = _expand_ip($address);
}
}
else
{
@check_list = ($address);
}
my %access;
my $filename = $DB_FILE;
if (defined $args{'file'})
{
$filename = $args{'file'};
}
my $db = tie %access, 'BerkeleyDB::Hash',
-Flags => DB_RDONLY,
-Filename => $filename
or die "Cannot open file $filename: $! $BerkeleyDB::Error\n";
foreach my $key (@check_list)
{
my $lookup = $key;
if (defined $args{'qualifier'})
{
$lookup = "$args{'qualifier'}:$lookup";
}
$lookup = lc $lookup;
# print STDERR "looking up '$lookup'\n";
if ($access{$lookup})
{
my $local_rc = $access{$lookup};
# untie %access;
# print STDERR "Returning $local_rc\n";
return $local_rc;
}
}
# untie %access;
return undef;
}
sub _expand_ip : locked
{
my $address = shift;
my @expanded = ();
if ($address =~ /^(?:\d+\.){3}\d+/)
{
push @expanded, $address;
my $shorter = $address;
$shorter =~ s/\.\d+$//;
push @expanded, ($shorter);
$shorter =~ s/\.\d+$//;
push @expanded, ($shorter);
$shorter =~ s/\.\d+$//;
push @expanded, ($shorter);
}
return @expanded;
}
sub _expand_hostname : locked
{
my $hostname = shift;
my @expanded = ($hostname);
while (my ($shorter) = $hostname =~ /^[\w\-]+\.(.*)$/)
{
push @expanded, ($shorter) if $shorter;
$hostname = $shorter;
}
return @expanded;
}
sub _expand_email : locked
{
my $address = shift;
my @to_check = ($address);
if ($address !~ /\@/)
{
push @to_check, ("$address\@");
}
elsif ($address =~ /^.*\@[A-Za-z0-9.\-]*/)
{
my ($left,$right) = $address =~ /^(.*\@)([A-Za-z0-9.\-]*)$/;
push @to_check, ($left) if (defined $left) and ($left) and ($left ne $address);
if ( (defined $right) and ($right) )
{
push @to_check, ( _expand_hostname($right) );
}
}
return @to_check;
}
=head1 PRIVATE METHODS
Each private function/method is described here.
These methods and functions are considered private and are intended for
internal use by this module. They are B<not> considered part of the public
interface and are described here for documentation purposes only.
=head2 _expand_ip
=head2 _expand_hostname
=head2 _expand_address
Usage : @expanded = _expand_ip($ip); # For example
Returns : Given an ip, hostname, or e-mail address, it will expand
that into the "appropriate lookups" which sendmail would use
(e.g., given '192.168.1.2', _expand_ip would return
192.168.1.2, 192.168.1, 192.168, and 192)
Argument : The IP Address, hostname, or e-mail address to expand
Throws :
Comments :
See Also :
=cut
1; #this line is important and will help the module return a true value
__END__