File Coverage

blib/lib/Mail/SPF/Mech.pm
Criterion Covered Total %
statement 52 121 42.9
branch 0 48 0.0
condition 0 9 0.0
subroutine 17 32 53.1
pod 6 12 50.0
total 75 222 33.7


line stmt bran cond sub pod time code
1             #
2             # Mail::SPF::Mech
3             # SPF record mechanism class.
4             #
5             # (C) 2005-2012 Julian Mehnle
6             # 2005 Shevek
7             # $Id: Mech.pm 57 2012-01-30 08:15:31Z julian $
8             #
9             ##############################################################################
10              
11             package Mail::SPF::Mech;
12              
13             =head1 NAME
14              
15             Mail::SPF::Mech - SPF record mechanism base class
16              
17             =cut
18              
19 1     1   1511 use warnings;
  1         3  
  1         77  
20 1     1   7 use strict;
  1         2  
  1         40  
21              
22 1     1   6 use utf8; # Hack to keep Perl 5.6 from whining about /[\p{}]/.
  1         2  
  1         8  
23              
24 1     1   29 use base 'Mail::SPF::Term';
  1         2  
  1         93  
25              
26 1     1   5 use Error ':try';
  1         2  
  1         8  
27 1     1   166 use NetAddr::IP;
  1         3  
  1         7  
28              
29 1     1   669 use Mail::SPF::Record;
  1         3  
  1         27  
30 1     1   531 use Mail::SPF::MacroString;
  1         4  
  1         27  
31 1     1   5 use Mail::SPF::Util;
  1         2  
  1         21  
32              
33 1     1   4 use constant TRUE => (0 == 0);
  1         2  
  1         48  
34 1     1   5 use constant FALSE => not TRUE;
  1         1  
  1         39  
35              
36 1     1   5 use constant default_qualifier => Mail::SPF::Record->default_qualifier;
  1         1  
  1         39  
37 1     1   5 use constant default_ipv4_prefix_length => 32;
  1         1  
  1         31  
38 1     1   5 use constant default_ipv6_prefix_length => 128;
  1         1  
  1         43  
39              
40 1     1   4 use constant qualifier_pattern => qr/[+\-~?]/;
  1         1  
  1         61  
41 1     1   5 use constant name_pattern => qr/ ${\__PACKAGE__->SUPER::name_pattern} (?= [:\/\x20] | $ ) /x;
  1         2  
  1         2  
  1         29  
42              
43 1         1133 use constant explanation_templates_by_result_code => {
44             pass => "Sender is authorized to use '%{s}' in '%{_scope}' identity",
45             fail => "Sender is not authorized to use '%{s}' in '%{_scope}' identity",
46             softfail => "Sender is not authorized to use '%{s}' in '%{_scope}' identity, however domain is not currently prepared for false failures",
47             neutral => "Domain does not state whether sender is authorized to use '%{s}' in '%{_scope}' identity"
48 1     1   191 };
  1         2  
49              
50             =head1 DESCRIPTION
51              
52             An object of class B represents a mechanism within an SPF
53             record. Mail::SPF::Mech cannot be instantiated directly. Create an instance
54             of a concrete sub-class instead.
55              
56             =head2 Constructors
57              
58             The following constructors are provided:
59              
60             =over
61              
62             =item B: returns I
63              
64             I. Creates a new SPF record mechanism object.
65              
66             %options is a list of key/value pairs representing any of the following
67             options:
68              
69             =over
70              
71             =item B
72              
73             A I denoting the unparsed text of the mechanism.
74              
75             =item B
76              
77             A single-character I denoting the qualifier of the mechanism. Any of
78             the following may be specified: B<'+'> (C), B<'-'> (C),
79             B<'~'> (C), B<'?'> (C). See RFC 4408, 4.6.2 and 2.5, for
80             their meanings. Defaults to B<'+'>.
81              
82             =item B
83              
84             A I denoting the name of the mechanism. I if a generic
85             I object (as opposed to a specific sub-class) is being
86             constructed.
87              
88             =item B
89              
90             A I object denoting an optional IP address network parameter of
91             the mechanism. Can be either an IPv4 or an IPv6 address, with an optional
92             network prefix length. IPv4-mapped IPv6 addresses (e.g. '::ffff:192.168.0.1')
93             must I be specified directly, but as plain IPv4 addresses.
94              
95             =item B
96              
97             Either a plain I or a I object denoting an
98             optional C parameter of the mechanism.
99              
100             =item B
101              
102             =item B
103              
104             A I denoting an optional IPv4 or IPv6 network prefix length for the
105             C of the mechanism. Note that these options do not apply to the
106             C option, which already includes an optional network prefix
107             length.
108              
109             =back
110              
111             Other options may be specified by sub-classes of Mail::SPF::Mech.
112              
113             =cut
114              
115             sub new {
116 0     0 1   my ($self, %options) = @_;
117 0 0         $self->class ne __PACKAGE__
118             or throw Mail::SPF::EAbstractClass;
119 0           $self = $self->SUPER::new(%options);
120 0 0         $self->{parse_text} = $self->{text} if not defined($self->{parse_text});
121 0 0 0       $self->{domain_spec} = Mail::SPF::MacroString->new(text => $self->{domain_spec})
122             if defined($self->{domain_spec})
123             and not UNIVERSAL::isa($self->{domain_spec}, 'Mail::SPF::MacroString');
124 0           return $self;
125             }
126              
127             =item B: returns I;
128             throws I, I
129              
130             I. Creates a new SPF record mechanism object by parsing the string and
131             any options given.
132              
133             =back
134              
135             =head2 Class methods
136              
137             The following class methods are provided:
138              
139             =over
140              
141             =item B: returns I
142              
143             Returns the default qualifier, i.e. B<'+'>.
144              
145             =item B: returns I
146              
147             Returns the default IPv4 network prefix length, i.e. B<32>.
148              
149             =item B: returns I
150              
151             Returns the default IPv6 network prefix length, i.e. B<128>.
152              
153             =item B: returns I
154              
155             Returns a regular expression that matches any legal mechanism qualifier, i.e. B<'+'>,
156             B<'-'>, B<'~'>, or B<'?'>.
157              
158             =item B: returns I
159              
160             I. Returns the name of the mechanism.
161              
162             This method is abstract and must be implemented by sub-classes of
163             Mail::SPF::Mech.
164              
165             =item B: returns I
166              
167             Returns a regular expression that matches any legal mechanism name.
168              
169             =back
170              
171             =head2 Instance methods
172              
173             The following instance methods are provided:
174              
175             =over
176              
177             =cut
178              
179             sub parse {
180 0     0 0   my ($self) = @_;
181 0 0         defined($self->{parse_text})
182             or throw Mail::SPF::ENothingToParse('Nothing to parse for mechanism');
183 0           $self->parse_qualifier();
184 0           $self->parse_name();
185 0           $self->parse_params();
186 0           $self->parse_end();
187 0           return;
188             }
189              
190             sub parse_qualifier {
191 0     0 0   my ($self) = @_;
192 0 0         if ($self->{parse_text} =~ s/^(${\$self->qualifier_pattern})?//) {
  0            
193 0   0       $self->{qualifier} = $1 || $self->default_qualifier;
194             }
195             else {
196 0           throw Mail::SPF::EInvalidMechQualifier(
197             "Invalid qualifier encountered in '" . $self->text . "'");
198             }
199 0           return;
200             }
201              
202             sub parse_name {
203 0     0 0   my ($self) = @_;
204 0 0         if ($self->{parse_text} =~ s/^ (${\$self->name_pattern}) (?: : (?=.) )? //x) {
  0            
205 0           $self->{name} = $1;
206             }
207             else {
208 0           throw Mail::SPF::EInvalidMech(
209             "Unexpected mechanism name encountered in '" . $self->text . "'");
210             }
211 0           return;
212             }
213              
214             sub parse_params {
215 0     0 0   my ($self) = @_;
216             # Parse generic string of parameters text (should be overridden in sub-classes):
217 0 0         if ($self->{parse_text} =~ s/^(.*)//) {
218 0           $self->{params_text} = $1;
219             }
220 0           return;
221             }
222              
223             sub parse_end {
224 0     0 0   my ($self) = @_;
225 0 0         $self->{parse_text} eq ''
226             or throw Mail::SPF::EJunkInTerm("Junk encountered in mechanism '" . $self->text . "'");
227 0           delete($self->{parse_text});
228 0           return;
229             }
230              
231             =item B: returns I; throws I
232              
233             Returns the unparsed text of the mechanism. Throws a
234             I exception if the mechanism was created
235             synthetically instead of being parsed, and no text was provided.
236              
237             =item B: returns I
238              
239             Returns the qualifier of the mechanism. See the description of the C
240             constructor's C option.
241              
242             =cut
243              
244             sub qualifier {
245 0     0 1   my ($self) = @_;
246             # Read-only!
247 0   0       return $self->{qualifier} || $self->default_qualifier;
248             }
249              
250             =item B: returns I
251              
252             I. Returns the mechanism's parameters formatted as a string.
253              
254             A sub-class of Mail::SPF::Mech does not have to implement this method if it
255             supports no parameters.
256              
257             =item B: returns I
258              
259             Formats the mechanism's qualifier, name, and parameters as a string and returns
260             it. (A qualifier that matches the default of B<'+'> is omitted.) You can
261             simply use a Mail::SPF::Mech object as a string for the same effect, see
262             L<"OVERLOADING">.
263              
264             =cut
265              
266             sub stringify {
267 0     0 0   my ($self) = @_;
268 0 0         my $params = $self->can('params') ? $self->params : undef;
269 0 0         return sprintf(
    0          
270             '%s%s%s',
271             $self->qualifier eq $self->default_qualifier ? '' : $self->qualifier,
272             $self->name,
273             defined($params) ? $params : ''
274             );
275             }
276              
277             =item B: returns I
278              
279             Returns the target domain of the mechanism. Depending on whether the mechanism
280             does have an explicit C parameter, this is either the
281             macro-expanded C parameter, or the request's authority domain
282             (see L) otherwise. Both a
283             I and a I object are required for
284             resolving the target domain.
285              
286             =cut
287              
288             sub domain {
289 0     0 1   my ($self, $server, $request) = @_;
290 0 0         defined($server)
291             or throw Mail::SPF::EOptionRequired('Mail::SPF server object required for target domain resolution');
292 0 0         defined($request)
293             or throw Mail::SPF::EOptionRequired('Request object required for target domain resolution');
294 0 0         return $self->{domain_spec}->new(server => $server, request => $request)
295             if defined($self->{domain_spec});
296 0           return $request->authority_domain;
297             }
298              
299             =item B: returns I; throws I
300              
301             I. Checks whether the mechanism matches the parameters of the given
302             request (see L) and returns B if it does, or B
303             otherwise. In any case, takes both a I and a
304             I object.
305              
306             This method is abstract and must be implemented by sub-classes of
307             Mail::SPF::Mech.
308              
309             =item B: returns I;
310             throws I
311              
312             =item B: returns I;
313             throws I
314              
315             Checks whether the mechanism's target domain name (that is, any of its DNS C
316             or C records) matches the given request's IP address (see
317             L), and returns B if it does, or B
318             otherwise. If an explicit domain is specified, it is used instead of the
319             mechanism's target domain. The mechanism's IP network prefix lengths are
320             respected when matching DNS address records against the request's IP address.
321             See RFC 4408, 5, for the exact algorithm used.
322              
323             This method exists mainly for the convenience of sub-classes of
324             Mail::SPF::Mech.
325              
326             =cut
327              
328             sub match_in_domain {
329 0     0 1   my ($self, $server, $request, $domain) = @_;
330              
331 0 0         $domain = $self->domain($server, $request)
332             if not defined($domain);
333              
334 0           my $ipv4_prefix_length = $self->ipv4_prefix_length;
335 0           my $ipv6_prefix_length = $self->ipv6_prefix_length;
336 0 0         my $addr_rr_type = $request->ip_address->version == 4 ? 'A' : 'AAAA';
337              
338 0           my $packet = $server->dns_lookup($domain, $addr_rr_type);
339 0 0         my @rrs = $packet->answer
340             or $server->count_void_dns_lookup($request);
341              
342 0           foreach my $rr (@rrs) {
343 0 0         if ($rr->type eq 'A') {
    0          
    0          
344 0           my $network = NetAddr::IP->new($rr->address, $ipv4_prefix_length);
345 0 0         return TRUE
346             if $network->contains($request->ip_address);
347             }
348             elsif ($rr->type eq 'AAAA') {
349 0           my $network = NetAddr::IP->new($rr->address, $ipv6_prefix_length);
350 0 0         return TRUE
351             if $network->contains($request->ip_address_v6);
352             }
353             elsif ($rr->type eq 'CNAME') {
354             # Ignore -- we should have gotten the A/AAAA records anyway.
355             }
356             else {
357             # Unexpected RR type.
358             # TODO Generate debug info or ignore silently.
359             }
360             }
361 0           return FALSE;
362             }
363              
364             =item B
365              
366             Locally generates an explanation for why the mechanism caused the given result,
367             and stores it in the given request object's state.
368              
369             There is no need to override this method in sub-classes. See the
370             L method.
371              
372             =cut
373              
374             sub explain {
375 0     0 1   my ($self, $server, $request, $result) = @_;
376 0           my $explanation_template = $self->explanation_template($server, $request, $result);
377             return
378 0 0         if not defined($explanation_template);
379             try {
380 0     0     my $explanation = Mail::SPF::MacroString->new(
381             text => $explanation_template,
382             server => $server,
383             request => $request,
384             is_explanation => TRUE
385             );
386 0           $request->state('local_explanation', $explanation);
387             }
388 0     0     catch Mail::SPF::Exception with {}
389 0     0     catch Mail::SPF::Result with {};
  0            
390 0           return;
391             }
392              
393             =item B: returns I
394              
395             Returns a macro string template for a locally generated explanation for why the
396             mechanism caused the given result object.
397              
398             Sub-classes should either define an C
399             hash constant with their own templates, or override this method.
400              
401             =cut
402              
403             sub explanation_template {
404 0     0 1   my ($self, $server, $request, $result) = @_;
405             return undef
406 0 0         if not $self->can('explanation_templates_by_result_code');
407 0           return $self->explanation_templates_by_result_code->{$result->code};
408             }
409              
410             =back
411              
412             =head1 OVERLOADING
413              
414             If a Mail::SPF::Mech object is used as a I, the C method is
415             used to convert the object into a string.
416              
417             =head1 SEE ALSO
418              
419             L,
420             L,
421             L,
422             L,
423             L,
424             L,
425             L,
426             L
427              
428             L, L, L
429              
430             L
431              
432             For availability, support, and license information, see the README file
433             included with Mail::SPF.
434              
435             =head1 AUTHORS
436              
437             Julian Mehnle , Shevek
438              
439             =cut
440              
441             TRUE;