File Coverage

blib/lib/Mail/Milter/Chain.pm
Criterion Covered Total %
statement 16 18 88.8
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 22 24 91.6


line stmt bran cond sub pod time code
1             # $Id: Chain.pm,v 1.10 2004/04/23 15:51:39 tvierling Exp $
2             #
3             # Copyright (c) 2002-2004 Todd Vierling
4             # All rights reserved.
5             #
6             # Redistribution and use in source and binary forms, with or without
7             # modification, are permitted provided that the following conditions are met:
8             #
9             # 1. Redistributions of source code must retain the above copyright notice,
10             # this list of conditions and the following disclaimer.
11             #
12             # 2. Redistributions in binary form must reproduce the above copyright
13             # notice, this list of conditions and the following disclaimer in the
14             # documentation and/or other materials provided with the distribution.
15             #
16             # 3. Neither the name of the author nor the names of contributors may be used
17             # to endorse or promote products derived from this software without specific
18             # prior written permission.
19             #
20             # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21             # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22             # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23             # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
24             # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25             # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26             # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27             # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28             # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29             # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30             # POSSIBILITY OF SUCH DAMAGE.
31              
32             package Mail::Milter::Chain;
33              
34 1     1   29435 use 5.006;
  1         4  
  1         40  
35              
36 1     1   5 use strict;
  1         2  
  1         41  
37 1     1   4 use warnings;
  1         2  
  1         33  
38              
39 1     1   5 use Carp;
  1         2  
  1         109  
40 1     1   801 use Mail::Milter;
  1         2  
  1         9  
41 1     1   424 use Sendmail::Milter 0.18; # get needed constants
  0            
  0            
42             use UNIVERSAL;
43              
44             our $VERSION = '0.03';
45              
46             =pod
47              
48             =head1 NAME
49              
50             Mail::Milter::Chain - Perl extension for chaining milter callbacks
51              
52             =head1 SYNOPSIS
53              
54             use Mail::Milter::Chain;
55              
56             my $chain = new Mail::Milter::Chain({ connect => \&foo, ... }, ...);
57             $chain->register({ connect => \&bar, ... });
58             $chain->register({ connect => \&baz, ... });
59              
60             $chain->accept_break(1);
61              
62             use Sendmail::Milter;
63             ...
64             Sendmail::Milter::register('foo', $chain, SMFI_CURR_ACTS);
65              
66             =head1 DESCRIPTION
67              
68             Mail::Milter::Chain allows multiple milter callback sets to be registered
69             in a single milter server instance, simulating multiple milters running in
70             separate servers. This is typically much less resource intensive than
71             running each milter in its own server process.
72              
73             Any contained milter returning SMFIS_REJECT, SMFIS_TEMPFAIL, or
74             SMFIS_DISCARD will terminate the entire chain and return the respective
75             code up to the containing chain or milter server.
76              
77             Normally, a milter returning SMFIS_ACCEPT will remove only that milter
78             from the chain, allowing others to continue processing the message.
79             Alternatively, SMFIS_ACCEPT can be made to terminate the entire chain as
80             is done for error results; see C below.
81              
82             A C is itself a milter callback hash reference, and
83             can thus be passed directly to C or another
84             Mail::Milter::Chain container. IMPORTANT CAVEAT: Once this object has
85             been registered with a parent container (a milter or another chain), DO
86             NOT call C on this object any longer. This will result in
87             difficult to diagnose problems at callback time.
88              
89             =head1 METHODS
90              
91             =over 4
92              
93             =item new([HASHREF, ...])
94              
95             Creates a Mail::Milter::Chain object. For convenience, accepts one or
96             more hash references corresponding to individual callback sets that will
97             be registered with this chain.
98              
99             =cut
100              
101             sub new ($) {
102             my $this = bless {}, shift;
103              
104             $this->{_acceptbreak} = 0;
105             $this->{_chain} = [];
106              
107             # "connect" and "helo" use the global chain, and whittle out
108             # callbacks to be ignored for the rest of the connection.
109              
110             $this->{connect} = sub {
111             $this->{_curchain} = [ @{$this->{_chain}} ];
112              
113             $this->dispatch('connect', @_);
114             };
115              
116             $this->{helo} = sub {
117             my $rc = $this->dispatch('helo', @_);
118             $this->{_connchain} = [ @{$this->{_curchain}} ];
119              
120             $rc;
121             };
122              
123             # "envfrom" uses the chain whittled by "connect" and "helo"
124             # each pass through.
125              
126             $this->{envfrom} = sub {
127             $this->{_curchain} = [ @{$this->{_connchain}} ];
128              
129             $this->dispatch('envfrom', @_);
130             };
131              
132             # "close" must use the global chain always, and must also
133             # clean up any internal state. Every callback must be called;
134             # there are no shortcuts.
135              
136             $this->{close} = sub {
137             my $ctx = shift;
138             my $chain = $this->{_chain};
139              
140             for (my $i = 0; $i < scalar @$chain; $i++) {
141             my $cb = $chain->[$i];
142             $ctx->setpriv($cb->{_priv});
143             &{$cb->{close}}($ctx, @_) if defined($cb->{close});
144             }
145              
146             $ctx->setpriv(undef);
147             SMFIS_CONTINUE;
148             };
149              
150             foreach my $callbacks (@_) {
151             $this->register($callbacks);
152             }
153              
154             $this;
155             }
156              
157             =pod
158              
159             =item accept_break(FLAG)
160              
161             If FLAG is 0 (the default), SMFIS_ACCEPT will only remove the current
162             milter from the list of callbacks, thus simulating a completely
163             independent milter server.
164              
165             If FLAG is 1, SMFIS_ACCEPT will terminate the entire chain and propagate
166             SMFIS_ACCEPT up to the parent chain or milter server. This allows a
167             milter to provide a sort of "whitelist" effect, where SMFIS_ACCEPT speaks
168             for the entire chain rather than just one milter callback set.
169              
170             This method returns a reference to the object itself, allowing this
171             method call to be chained.
172              
173             =cut
174              
175             sub accept_break ($$) {
176             my $this = shift;
177             my $flag = shift;
178              
179             croak 'accept_break: flag argument is undef' unless defined($flag);
180             $this->{_acceptbreak} = $flag;
181              
182             $this;
183             }
184              
185             # internal method to add dispatch closure hook as a callback
186             sub create_callback ($$) {
187             my $this = shift;
188             my $cbname = shift;
189              
190             return 0 if defined($this->{$cbname});
191              
192             $this->{$cbname} = sub {
193             $this->dispatch($cbname, @_);
194             };
195              
196             1;
197             }
198              
199             # internal method to dispatch a callback
200             sub dispatch ($$;@) {
201             my $this = shift;
202             my $cbname = shift;
203             my $ctx = shift;
204             # @_ is remaining args
205              
206             my $chain = $this->{_curchain};
207             my $rc = SMFIS_CONTINUE;
208              
209             for (my $i = 0; $i < scalar @$chain; $i++) {
210             my $cb = $chain->[$i];
211             $ctx->setpriv($cb->{_priv});
212              
213             my $newrc = defined($cb->{$cbname}) ?
214             &{$cb->{$cbname}}($ctx, @_) :
215             $rc;
216              
217             if ($newrc == SMFIS_TEMPFAIL || $newrc == SMFIS_REJECT) {
218             # If "envrcpt", these are special and don't nuke.
219             $rc = $newrc;
220             @$chain = () unless $cbname eq 'envrcpt';
221             } elsif ($newrc == SMFIS_DISCARD) {
222             $rc = $newrc;
223             @$chain = ();
224             } elsif ($newrc == SMFIS_ACCEPT) {
225             if ($this->{_acceptbreak}) {
226             @$chain = ();
227             } else {
228             splice(@$chain, $i, 1);
229             $i--;
230             }
231             } elsif ($newrc != SMFIS_CONTINUE) {
232             warn "chain element returned invalid result $newrc\n";
233              
234             $rc = SMFIS_TEMPFAIL;
235             @$chain = ();
236             }
237              
238             $cb->{_priv} = $ctx->getpriv();
239             }
240              
241             # If we're still at SMFIS_CONTINUE and the chain is empty,
242             # convert to a SMFIS_ACCEPT to bubble up to the parent.
243             $rc = SMFIS_ACCEPT if ($rc == SMFIS_CONTINUE && !scalar @$chain);
244              
245             $ctx->setpriv(undef);
246             $rc;
247             }
248              
249             =pod
250              
251             =item register(HASHREF)
252              
253             Registers a callback set with this chain. Do not call after this chain
254             has itself been registered with a parent container (chain or milter
255             server).
256              
257             =cut
258              
259             sub register ($$) {
260             my $this = shift;
261             my $callbacks = shift;
262             my $pkg = caller;
263              
264             croak 'register: callbacks is undef' unless defined($callbacks);
265             croak 'register: callbacks not hash ref' unless UNIVERSAL::isa($callbacks, 'HASH');
266              
267             # make internal copy, and convert to code references
268             my $ncallbacks = {};
269              
270             foreach my $cbname (keys %Sendmail::Milter::DEFAULT_CALLBACKS) {
271             my $cb = $callbacks->{$cbname};
272             next unless defined($cb);
273              
274             $ncallbacks->{$cbname} = Mail::Milter::resolve_callback($cb, $pkg);
275             $this->create_callback($cbname);
276             }
277              
278             # add to chain
279             push(@{$this->{_chain}}, $ncallbacks);
280              
281             1;
282             }
283              
284             1;
285             __END__