File Coverage

blib/lib/Mail/MtPolicyd/Plugin/PostfixMap.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package Mail::MtPolicyd::Plugin::PostfixMap;
2              
3 2     2   1597 use Moose;
  2         3  
  2         10  
4 2     2   8620 use namespace::autoclean;
  2         3  
  2         16  
5              
6             our $VERSION = '2.01'; # VERSION
7             # ABSTRACT: mtpolicyd plugin for accessing a postfix access map
8              
9             extends 'Mail::MtPolicyd::Plugin';
10             with 'Mail::MtPolicyd::Plugin::Role::Scoring';
11             with 'Mail::MtPolicyd::Plugin::Role::UserConfig' => {
12             'uc_attributes' => [ 'enabled' ],
13             };
14              
15 2     2   182 use Mail::MtPolicyd::Plugin::Result;
  2         2  
  2         36  
16              
17 2     2   426 use BerkeleyDB;
  0            
  0            
18             use BerkeleyDB::Hash;
19              
20              
21             has 'enabled' => ( is => 'rw', isa => 'Str', default => 'on' );
22              
23             has 'db_file' => ( is => 'rw', isa => 'Str', required => 1 );
24             has _map => (
25             is => 'ro', isa => 'HashRef', lazy => 1,
26             default => sub {
27             my $self = shift;
28             my %map;
29             my $db = tie %map, 'BerkeleyDB::Hash',
30             -Filename => $self->db_file,
31             -Flags => DB_RDONLY
32             or die "Cannot open ".$self->db_file.": $!\n" ;
33             $db->filter_fetch_key ( sub { s/\0$// } ) ;
34             $db->filter_store_key ( sub { $_ .= "\0" } ) ;
35             $db->filter_fetch_value( sub { s/\0$// } ) ;
36             $db->filter_store_value( sub { $_ .= "\0" } ) ;
37             return(\%map);
38             },
39             );
40              
41             has 'score' => ( is => 'rw', isa => 'Maybe[Num]' );
42             has 'match_action' => ( is => 'rw', isa => 'Maybe[Str]' );
43             has 'not_match_action' => ( is => 'rw', isa => 'Maybe[Str]' );
44              
45             sub _match_ipv4 {
46             my ( $self, $ip ) = @_;
47             my @octs = split('\.', $ip);
48              
49             while( @octs ) {
50             my $key = join('.', @octs);
51             my $value = $self->_map->{$key};
52             if( defined $value ) {
53             return( $key, $value );
54             }
55             pop(@octs);
56             }
57              
58             return;
59             }
60              
61             sub _match_ipv6 {
62             my ( $self, $ip ) = @_;
63              
64             for(;;) {
65             my $value = $self->_map->{$ip};
66             if( $value ) {
67             return( $ip, $value );
68             }
69             if( $ip !~ m/:/) {
70             last;
71             }
72             # remove last part
73             $ip =~ s/:+[^:]+$//;
74             }
75              
76             return;
77             }
78              
79             sub _query_db {
80             my ( $self, $ip ) = @_;
81             my ( $key, $value );
82             if( $ip =~ m/^\d+\.\d+\.\d+\.\d+$/) {
83             ( $key, $value ) = $self->_match_ipv4( $ip );
84             } elsif( $ip =~ m/^[:0-9a-f]+$/) {
85             ( $key, $value ) = $self->_match_ipv6( $ip );
86             } else {
87             die('ip is neither a valid ipv4 nor ipv6 address.');
88             }
89              
90             if( ! defined $value ) {
91             return;
92             }
93              
94             if( $value eq 'OK' || $value =~ m/^\d+$/) {
95             return( 1, $key, $value );
96             }
97            
98             return(0, $key, $value);
99             }
100              
101             sub run {
102             my ( $self, $r ) = @_;
103             my $ip = $r->attr('client_address');
104             my $session = $r->session;
105             my $config;
106              
107             if( $self->get_uc( $session, 'enabled') eq 'off' ) {
108             return;
109             }
110              
111             if( ! defined $ip) {
112             $self->log($r, 'no attribute \'client_address\' in request');
113             return;
114             }
115              
116             my ( $match, $key, $value ) = $r->do_cached( $self->name.'-result',
117             sub { $self->_query_db($ip) } );
118             if( $match ) {
119             $self->log($r, 'client_address '.$ip.' matched '.$self->name.' ('.
120             $key.' '.$value.')' );
121             if( defined $self->score
122             && ! $r->is_already_done($self->name.'-score') ) {
123             $self->add_score($r, $self->name => $self->score);
124             }
125             if( defined $self->match_action ) {
126             return Mail::MtPolicyd::Plugin::Result->new(
127             action => $self->match_action,
128             abort => 1,
129             );
130             }
131             } else {
132             $self->log($r, 'client_address '.$ip.' did not match '.$self->name);
133             if( defined $self->not_match_action ) {
134             return Mail::MtPolicyd::Plugin::Result->new(
135             action => $self->not_match_action,
136             abort => 1,
137             );
138             }
139             }
140              
141             return;
142             }
143              
144             __PACKAGE__->meta->make_immutable;
145              
146             1;
147              
148             __END__
149              
150             =pod
151              
152             =encoding UTF-8
153              
154             =head1 NAME
155              
156             Mail::MtPolicyd::Plugin::PostfixMap - mtpolicyd plugin for accessing a postfix access map
157              
158             =head1 VERSION
159              
160             version 2.01
161              
162             =head1 SYNOPSIS
163              
164             <Plugin whitelist>
165             module="PostfixMap"
166             db_file="/etc/postfix/whitelist.db"
167             match_action=dunno
168             </Plugin>
169              
170             <Plugin blacklist>
171             moduel="PostfixMap"
172             db_file="/etc/postfix/blacklist.db"
173             match_action="reject you are blacklisted!"
174             </Plugin>
175              
176             =head1 DESCRIPTION
177              
178             Plugin checks the client_address against a postfix hash table.
179              
180             It will only check if the IP address matches the list.
181             'OK' or a numerical value will be interpreted as a 'true' value.
182             All other actions or values will be treaded as 'false'.
183              
184             =head1 EXAMPLE TABLE
185              
186             /etc/postfix/whitelist:
187              
188             123.123.123.123 OK
189             123.123.122 OK
190             123.12 OK
191             fe80::250:56ff:fe85:56f5 OK
192             fe80::250:56ff:fe83 OK
193              
194             generate whitelist.db:
195              
196             $ postmap whitelist
197              
198             =head2 PARAMETERS
199              
200             The module takes the following parameters:
201              
202             =over
203              
204             =item (uc_)enabled (default: "on")
205              
206             Could be set to 'off' to deactivate check. Could be used to activate/deactivate check per user.
207              
208             =back
209              
210             By default the plugin will do nothing. One of the following actions should be specified:
211              
212             =over
213              
214             =item match_action (default: empty)
215              
216             If given this action will be returned to the MTA if the SQL query matched.
217              
218             =item not_match_action (default: empty)
219              
220             If given this action will be returned to the MTA if the SQL query DID NOT matched.
221              
222             =item score (default: empty)
223              
224             If given this score will be applied to the session.
225              
226             =back
227              
228             =head1 AUTHOR
229              
230             Markus Benning <ich@markusbenning.de>
231              
232             =head1 COPYRIGHT AND LICENSE
233              
234             This software is Copyright (c) 2014 by Markus Benning <ich@markusbenning.de>.
235              
236             This is free software, licensed under:
237              
238             The GNU General Public License, Version 2, June 1991
239              
240             =cut