File Coverage

blib/lib/Sendmail/AccessDB.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1              
2             package Sendmail::AccessDB;
3             #use DB_File;
4 3     3   6640 use BerkeleyDB;
  0            
  0            
5             use strict;
6             use Carp;
7              
8             BEGIN {
9             use Exporter ();
10             use vars qw ($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS
11             $sub_regex_lock $DB_FILE);
12             $VERSION = 1.03;
13             @ISA = qw (Exporter);
14             @EXPORT = qw ();
15             @EXPORT_OK = qw (spam_friend whitelisted lookup);
16             %EXPORT_TAGS = ();
17             $DB_FILE = '/etc/mail/access.db';
18             }
19              
20             =head1 NAME
21              
22             Sendmail::AccessDB - An interface to the Sendmail access.db list
23              
24             =head1 SYNOPSIS
25              
26             use Sendmail::AccessDB qw(spam_friend whitelisted);
27             $friend_or_hater = spam_friend('user@example.com');
28             $whitelisted = whitelisted('sender@example.com');
29              
30             =head1 DESCRIPTION
31              
32             This module is designed so that users of the Sendmail::Milter module (or
33             other Sendmail programmers) can ascertain if a user has elected to whitelist
34             themselves as a "spam friend" (where there should be no spam filtering on
35             mail to them) or, where spam-filtering is not the default, but an option, where
36             certain receipients have been labeled as "spam haters"
37              
38             =head1 USAGE
39              
40             use Sendmail::AccessDB qw(spam_friend);
41             $friend_or_hater = spam_friend('user@example.com');
42              
43             Ordinarily, this will look for such things as "Spam:user@example.com",
44             "Spam:user@", etc., in the /etc/mail/access.db file. There is an optional
45             second argument "Category", which could be used if you wanted to enable
46             specific checks, for example, if you wanted to customize down to a per-check
47             basis, you might use:
48              
49             $rbl_friend_or_hater = spam_friend('user@example.com',
50             'qualifier' => 'maps_rbl');
51             $dul_friend_or_hater = spam_friend('user@example.com',
52             'qualifier' => 'maps_dul');
53              
54             Caution should be taken when defining your own categories, as they may
55             inadvertantly conflict with Sendmail-defined categories.
56              
57             use Sendmail::AccessDB qw(whitelisted);
58             $whitelisted = whitelisted('sender@example.com');
59             $whitelisted_host = whitelisted('foo.example.com');
60             $whitelisted_addr = whitelisted('192.168.1.123');
61              
62             Would check for appropriate whitelisting entries in access.db. Some lookups
63             might be ambiguous, for example:
64              
65             $whitelisted = whitelisted('foobar');
66              
67             where it is hard to know if that is supposed to be a hostname, or a sender.
68             whitelisted() accepts the 'type' argument, such as:
69              
70             $whitelisted = whitelisted('foobar','type'=>'hostname');
71             $whitelisted = whitelisted('postmaster','type'=>'mail');
72              
73             It's also possible to feed the qualifier argument, if necessary, for example,
74             to do:
75            
76             $whitelisted = whitelisted('host.example.com','type'=>'hostname',
77             'qualifier' => 'Connect');
78              
79             which would check to see if this host has an OK flag set for the Connect
80             qualifier.
81              
82             There is also the generic "lookup", which, at its simplest, takes a single
83             argument:
84              
85             $rc = lookup('host.example.com');
86              
87             will do a lookup on host.example.com. But if you wanted to pay attention to
88             parent-domains, you might do:
89              
90             $rc = lookup('host.example.com', 'type'=>'hostname');
91              
92             but if you wanted to find out if 'host.example.com', or any of its parent
93             domains ('example.com' and 'com'), had a value in the "MyQual" qualifier, you
94             might do:
95              
96             $rc = lookup('host.example.com','type'=>'hostname','qualifier'=>'MyQual');
97              
98             which would look up, in order 'MyQual:host.example.com', 'MyQual:example.com',
99             and 'MyQual:com', returning the first (most specific) one found.
100              
101             =head1 BUGS
102              
103             None that I've found yet, but I'm sure they're there.
104              
105             =head1 SUPPORT
106              
107             Feel free to email me at
108              
109             =head1 AUTHOR
110              
111             Derek J. Balling
112             CPAN ID: DREDD
113             dredd@megacity.org
114             http://www.megacity.org/software.html
115              
116             =head1 COPYRIGHT
117              
118             Copyright (c) 2001 Derek J. Balling. All rights reserved.
119             This program is free software; you can redistribute
120             it and/or modify it under the same terms as Perl itself.
121              
122             The full text of the license can be found in the
123             LICENSE file included with this module.
124              
125             =head1 SEE ALSO
126              
127             perl(1).
128              
129             =head1 PUBLIC METHODS
130              
131             Each public function/method is described here.
132             These are how you should interact with this module.
133              
134             =cut
135              
136              
137             =head2 spam_friend
138              
139             Usage : $friend_or_hater = spam_friend($recipient,
140             ['qualifier' => $category])
141             Purpose : Consults the /etc/mail/access.db to check for spamfriendliness
142             Returns : 'FRIEND','HATER', or undef (which would mean default
143             behavior for that site)
144             Argument : The recipient e-mail address and an optional qualifier if
145             the default of 'Spam' is not desired.
146             Throws :
147             Comments :
148             See Also :
149              
150             =cut
151              
152             sub spam_friend
153             {
154             my $address = shift;
155             my $qual = shift || 'Spam';
156             return lookup($address,'qualifier'=>$qual,'type'=>'mail');
157             }
158              
159             =head2 whitelisted
160              
161             Usage : whitelisted($value)
162             Purpose : Determine if an e-mail address, hostname, or IP address is
163             explicitly whitelisted, in that it contains an "OK" or "RELAY"
164             value in the database.
165             Returns : 0/1, true or false as to whether the argument is whitelisted
166             Argument : Either an email-address (e.g., foo@example.com), an IP address
167             (e.g., 10.200.1.230), or a hostname (e.g., mailhost.example.com)
168             as well as 'type' and 'qualifer' arguments (see lookup for greater
169             detail)
170             Throws :
171             Comments : The code makes a pretty good attempt to figure out what type
172             of argument $value is, but it can be overriden using the 'type'
173             qualifier.
174             See Also :
175              
176             =cut
177              
178             sub whitelisted
179             {
180             my $address = shift;
181             my %args = @_;
182            
183             if (! defined $args{'type'})
184             {
185             if ($address =~ /\@/)
186             {
187             $args{'type'} = 'mail';
188             }
189             elsif ($address =~ /^(?:\d+\.){3}\d+/)
190             {
191             $args{'type'} = 'ip';
192             }
193             elsif ($address =~ /^[A-Za-z0-9\-\.]+$/)
194             {
195             $args{'type'} = 'hostname';
196             }
197             }
198             my $lookup = lookup($address,%args);
199             return ( (defined $lookup) and
200             ( ($lookup eq 'OK') or ($lookup eq 'RELAY') )
201             ) ? 1 : 0;
202             }
203              
204             =head2 lookup
205              
206             Usage : lookup ($lookup_key,
207             'type'=>{'mail','ip','hostname'} , [optional]
208             'qualifier'=>'qualifier' [optional]
209             'file'=>'filename' [optional]
210             )
211             Purpose : Do a generic lookup on a $lookup_key in the access.db and
212             return the value found (or undef if not)
213             Returns : value in access.db or undef if not found
214             Argument : $lookup_key - mandatory. 'type'=>mail/ip/hostname will cause
215             lookups against all necessary lookups according to sendmail logic
216             (for things like hostname lookups where subdomains inherit
217             attributes of parent domains, etc.), 'qualifier'=>$q, where $q
218             will be preprended to the beginning of all lookups, (e.g., $q =
219             'Spam', lookup would be against 'Spam:lookup_value')
220             Throws :
221             Comments : If not using 'type', the 'qualifier' field can be mimicked by
222             simply looking for 'Qualifier:lookup'.
223             See Also :
224              
225             =cut
226              
227              
228              
229             sub lookup
230             {
231             my ($address,%args) = @_;
232             my @check_list;
233             if (defined $args{'type'})
234             {
235             if ($args{'type'} eq 'mail')
236             {
237             @check_list = _expand_email($address);
238             }
239             elsif ($args{'type'} eq 'hostname')
240             {
241             @check_list = _expand_hostname($address);
242             }
243             elsif ($args{'type'} eq 'ip')
244             {
245             @check_list = _expand_ip($address);
246             }
247             }
248             else
249             {
250             @check_list = ($address);
251             }
252              
253             push(@check_list, '');
254              
255             my %access;
256              
257             my $filename = $DB_FILE;
258             if (defined $args{'file'})
259             {
260             $filename = $args{'file'};
261             }
262             my $db = tie %access, 'BerkeleyDB::Hash',
263             -Flags => DB_RDONLY,
264             -Filename => $filename
265             or die "Cannot open file $filename: $! $BerkeleyDB::Error\n";
266              
267              
268             foreach my $key (@check_list)
269             {
270             my $lookup = $key;
271              
272             if (defined $args{'qualifier'})
273             {
274             $lookup = "$args{'qualifier'}:$lookup";
275             }
276             $lookup = lc $lookup;
277              
278             # print STDERR "looking up '$lookup'\n";
279              
280             if ($access{$lookup})
281             {
282             my $local_rc = $access{$lookup};
283             # untie %access;
284             # print STDERR "Returning $local_rc\n";
285             return $local_rc;
286             }
287             }
288              
289             # untie %access;
290             return undef;
291             }
292            
293              
294              
295             sub _expand_ip : locked
296             {
297             my $address = shift;
298             my @expanded = ();
299            
300             if ($address =~ /^(?:\d+\.){3}\d+/)
301             {
302             push @expanded, $address;
303             my $shorter = $address;
304             $shorter =~ s/\.\d+$//;
305             push @expanded, ($shorter);
306             $shorter =~ s/\.\d+$//;
307             push @expanded, ($shorter);
308             $shorter =~ s/\.\d+$//;
309             push @expanded, ($shorter);
310             }
311             return @expanded;
312             }
313              
314             sub _expand_hostname : locked
315             {
316             my $hostname = shift;
317             my @expanded = ($hostname);
318             while (my ($shorter) = $hostname =~ /^[\w\-]+\.(.*)$/)
319             {
320             push @expanded, ($shorter) if $shorter;
321             $hostname = $shorter;
322             }
323             return @expanded;
324             }
325              
326             sub _expand_email : locked
327             {
328             my $address = shift;
329             my @to_check = ($address);
330             if ($address !~ /\@/)
331             {
332             push @to_check, ("$address\@");
333             }
334             elsif ($address =~ /^.*\@[A-Za-z0-9.\-]*/)
335             {
336             my ($left,$right) = $address =~ /^(.*\@)([A-Za-z0-9.\-]*)$/;
337             push @to_check, ($left) if (defined $left) and ($left) and ($left ne $address);
338             if ( (defined $right) and ($right) )
339             {
340             push @to_check, ( _expand_hostname($right) );
341             }
342             }
343             return @to_check;
344             }
345              
346              
347             =head1 PRIVATE METHODS
348              
349             Each private function/method is described here.
350             These methods and functions are considered private and are intended for
351             internal use by this module. They are B considered part of the public
352             interface and are described here for documentation purposes only.
353              
354             =head2 _expand_ip
355             =head2 _expand_hostname
356             =head2 _expand_address
357              
358             Usage : @expanded = _expand_ip($ip); # For example
359             Returns : Given an ip, hostname, or e-mail address, it will expand
360             that into the "appropriate lookups" which sendmail would use
361             (e.g., given '192.168.1.2', _expand_ip would return
362             192.168.1.2, 192.168.1, 192.168, and 192)
363             Argument : The IP Address, hostname, or e-mail address to expand
364             Throws :
365             Comments :
366             See Also :
367              
368             =cut
369              
370              
371             1; #this line is important and will help the module return a true value
372             __END__