File Coverage

blib/lib/App/Milter/Limit.pm
Criterion Covered Total %
statement 24 105 22.8
branch 0 20 0.0
condition 0 10 0.0
subroutine 8 22 36.3
pod 3 4 75.0
total 35 161 21.7


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