File Coverage

blib/lib/App/Milter/Limit.pm
Criterion Covered Total %
statement 79 115 68.7
branch 7 26 26.9
condition 4 10 40.0
subroutine 18 23 78.2
pod 3 4 75.0
total 111 178 62.3


line stmt bran cond sub pod time code
1             #
2             # This file is part of App-Milter-Limit
3             #
4             # This software is copyright (c) 2010 by Michael Schout.
5             #
6             # This is free software; you can redistribute it and/or modify it under
7             # the same terms as the Perl 5 programming language system itself.
8             #
9              
10             package App::Milter::Limit;
11             $App::Milter::Limit::VERSION = '0.53';
12             # ABSTRACT: Sendmail Milter that limits message rate by sender
13              
14 5     5   2824 use strict;
  5         11  
  5         127  
15 5     5   24 use warnings;
  5         9  
  5         137  
16              
17 5     5   26 use base qw(Class::Accessor Class::Singleton);
  5         8  
  5         1594  
18              
19 5     5   10212 use Carp;
  5         11  
  5         387  
20 5     5   1514 use App::Milter::Limit::Config;
  5         15  
  5         25  
21 5     5   1666 use App::Milter::Limit::Log;
  5         15  
  5         274  
22 5     5   2145 use App::Milter::Limit::Util;
  5         10  
  5         152  
23 5     5   1650 use Sendmail::PMilter 0.98 ':all';
  5         34703  
  5         1002  
24 5     5   41 use Sys::Syslog ();
  5         10  
  5         4384  
25              
26             __PACKAGE__->mk_accessors(qw(driver milter));
27              
28              
29             sub _new_instance {
30 4     4   2488 my ($class, $driver) = @_;
31              
32 4 50       24 croak "usage: new(driver)" unless defined $driver;
33              
34 4         34 my $self = $class->SUPER::_new_instance();
35              
36 4         138 $self->init($driver);
37              
38 4         76 return $self;
39             }
40              
41             sub init {
42 4     4 0 20 my ($self, $driver) = @_;
43              
44 4         28 $self->_init_log;
45              
46 4         20 $self->_init_statedir;
47              
48 4         36 $self->milter(new Sendmail::PMilter);
49              
50 4         122 $self->_init_driver($driver);
51             }
52              
53             # initialize logging
54             sub _init_log {
55 4     4   16 my $self = shift;
56              
57 4         24 my $conf = $self->config->section('log');
58 4   50     150 $$conf{identity} ||= 'milter-limit';
59 4   50     32 $$conf{facility} ||= 'mail';
60              
61 4         36 Sys::Syslog::openlog($$conf{identity}, $$conf{options}, $$conf{facility});
62 4         110 info("syslog initialized");
63              
64             $SIG{__WARN__} = sub {
65 0     0   0 Sys::Syslog::syslog('warning', "warning: ".join('', @_));
66 4         1506 };
67              
68             $SIG{__DIE__} = sub {
69 0     0   0 Sys::Syslog::syslog('crit', "fatal: ".join('',@_));
70 0         0 die @_;
71 4         72 };
72             }
73              
74             # initialize the configured state dir.
75             # default: /var/run/milter-limit
76             sub _init_statedir {
77 4     4   10 my $self = shift;
78              
79 4         16 my $conf = $self->config->global;
80              
81 4         80 App::Milter::Limit::Util::make_path($$conf{state_dir});
82             }
83              
84             sub _init_driver {
85 4     4   14 my ($self, $driver) = @_;
86              
87 4         14 my $driver_class = "App::Milter::Limit::Plugin::$driver";
88              
89 4         342 eval "require $driver_class";
90 4 50       64 if ($@) {
91 0         0 die "failed to load $driver_class: $@\n";
92             }
93 4         34 debug("loaded driver $driver");
94              
95 4         766 $self->driver($driver_class->instance);
96             }
97              
98              
99             sub register {
100 2     2 1 7023 my $self = shift;
101              
102 2         86 my $milter = $self->milter;
103              
104 2         282 my $conf = $self->config->global;
105              
106 2 50       102 if ($$conf{connection}) {
107 2         54 $milter->setconn($$conf{connection});
108             }
109             else {
110             # figure out the connection from sendmail
111             $milter->auto_setconn($$conf{name})
112 0 0       0 or croak "auto_setconn failed";
113             }
114              
115 2         2076 my %callbacks = (
116             envfrom => \&_envfrom_callback
117             );
118              
119 2         44 $milter->register($$conf{name}, \%callbacks, SMFI_CURR_ACTS);
120              
121 2         256 debug("registered as $$conf{name}");
122             }
123              
124             # drop user/group privs.
125             sub _drop_privileges {
126 2     2   9 my $self = shift;
127              
128 2         20 my $conf = $self->config->global;
129              
130 2 50       68 if (defined $$conf{group}) {
131 0         0 ($(,$)) = ($$conf{group}, $$conf{group});
132             }
133              
134 2 50       23 if (defined $$conf{user}) {
135 0         0 ($<,$>) = ($$conf{user}, $$conf{user});
136             }
137             }
138              
139              
140             sub main {
141 2     2 1 722 my $self = shift;
142              
143 2         22 $self->_drop_privileges;
144              
145 2         11 my $milter = $self->milter;
146              
147 2         40 my $conf = $self->config->global;
148              
149             my %dispatch_args = (
150             max_children => $$conf{max_children} || 5,
151 2   50     98 max_requests_per_child => $$conf{max_requests_per_child} || 100
      50        
152             );
153              
154 2         26 my $driver = $self->driver;
155              
156             # add child_init hook if necessary
157 2 50       113 if ($driver->can('child_init')) {
158 0         0 debug("child_init hook registered");
159 0     0   0 $dispatch_args{child_init} = sub { $driver->child_init };
  0         0  
160             }
161              
162             # add child_exit hook if necessary
163 2 50       29 if ($driver->can('child_exit')) {
164 0         0 debug("child_exit hook registered");
165 0     0   0 $dispatch_args{child_exit} = sub { $driver->child_exit };
  0         0  
166             }
167              
168 2         52 my $dispatcher = Sendmail::PMilter::prefork_dispatcher(%dispatch_args);
169              
170 2         365 $milter->set_dispatcher($dispatcher);
171              
172 2         37 info("starting");
173              
174 2         350 $milter->main;
175             }
176              
177             sub _envfrom_callback {
178 0     0   0 my ($ctx, $from) = @_;
179              
180 0         0 my $self = __PACKAGE__->instance();
181              
182 0         0 my $conf = $self->config->global;
183              
184 0 0       0 if (defined $$conf{limit_from}) {
185 0         0 my $val = $ctx->getsymval($$conf{limit_from});
186 0 0       0 if (defined $val) {
187 0         0 debug("overriding From value with $val");
188 0         0 $from = $val;
189             }
190             }
191              
192             # strip angle brackets
193 0         0 $from =~ s/(?:^\<)|(?:\>$)//g;
194              
195             # do not restrict NULL sender (bounces)
196 0 0       0 unless (length $from) {
197 0         0 return SMFIS_CONTINUE;
198             }
199              
200 0   0     0 my $reply = $$conf{reply} || 'reject';
201              
202 0         0 my $count = $self->driver->query($from);
203 0         0 debug("$from [$count/$$conf{limit}]");
204              
205 0 0       0 if ($count > $$conf{limit}) {
206 0 0       0 if ($reply eq 'defer') {
207 0         0 info("$from exceeded message limit, deferring");
208              
209 0         0 $ctx->setreply(450, '4.7.1', 'Message limit exceeded');
210              
211 0         0 return SMFIS_TEMPFAIL;
212             }
213             else {
214 0         0 info("$from exceeded message limit, rejecting");
215              
216 0         0 $ctx->setreply(550, '5.7.1', 'Message limit exceeded');
217              
218 0         0 return SMFIS_REJECT;
219             }
220             }
221             else {
222 0         0 return SMFIS_CONTINUE;
223             }
224             }
225              
226              
227             # shortcut to get the config.
228             sub config {
229 14     14 1 128 App::Milter::Limit::Config->instance;
230             }
231              
232             1;
233              
234             __END__