File Coverage

blib/lib/Mail/SPF/Result.pm
Criterion Covered Total %
statement 98 142 69.0
branch 12 36 33.3
condition 1 8 12.5
subroutine 30 37 81.0
pod 6 9 66.6
total 147 232 63.3


line stmt bran cond sub pod time code
1             #
2             # Mail::SPF::Result
3             # SPF result class.
4             #
5             # (C) 2005-2012 Julian Mehnle
6             # $Id: Result.pm 57 2012-01-30 08:15:31Z julian $
7             #
8             ##############################################################################
9              
10             package Mail::SPF::Result;
11              
12             =head1 NAME
13              
14             Mail::SPF::Result - SPF result class
15              
16             =cut
17              
18 4     4   800 use warnings;
  4         9  
  4         157  
19 4     4   22 use strict;
  4         10  
  4         137  
20              
21 4     4   21 use utf8; # Hack to keep Perl 5.6 from whining about /[\p{}]/.
  4         6  
  4         33  
22              
23 4     4   111 use base 'Error', 'Mail::SPF::Base';
  4         8  
  4         499  
24             # An SPF result is not really a code exception in ideology, but in form.
25             # The Error base class fits our purpose, anyway.
26              
27 4     4   25 use Mail::SPF::Util;
  4         9  
  4         98  
28              
29 4     4   20 use Error ':try';
  4         10  
  4         31  
30              
31 4     4   710 use constant TRUE => (0 == 0);
  4         10  
  4         283  
32 4     4   21 use constant FALSE => not TRUE;
  4         8  
  4         317  
33              
34 4         213 use constant result_classes => {
35             pass => 'Mail::SPF::Result::Pass',
36             fail => 'Mail::SPF::Result::Fail',
37             softfail => 'Mail::SPF::Result::SoftFail',
38             neutral => 'Mail::SPF::Result::Neutral',
39             'neutral-by-default'
40             => 'Mail::SPF::Result::NeutralByDefault',
41             none => 'Mail::SPF::Result::None',
42             error => 'Mail::SPF::Result::Error',
43             permerror => 'Mail::SPF::Result::PermError',
44             temperror => 'Mail::SPF::Result::TempError'
45 4     4   23 };
  4         6  
46              
47 4     4   21 use constant received_spf_header_name => 'Received-SPF';
  4         7  
  4         211  
48              
49 4         225 use constant received_spf_header_scope_names_by_scope => {
50             helo => 'helo',
51             mfrom => 'mailfrom',
52             pra => 'pra'
53 4     4   20 };
  4         8  
54              
55 4         226 use constant received_spf_header_identity_key_names_by_scope => {
56             helo => 'helo',
57             mfrom => 'envelope-from',
58             pra => 'pra'
59 4     4   22 };
  4         9  
60              
61 4     4   4056 use constant atext_pattern => qr/[\p{IsAlnum}!#\$%&'*+\-\/=?^_`{|}~]/;
  4         10  
  4         413  
62              
63 4         7 use constant dot_atom_pattern => qr/
64 4         9 (${\atext_pattern})+ ( \. (${\atext_pattern})+ )*
  4         85  
65 4     4   21 /x;
  4         10  
66              
67             # Interface:
68             ##############################################################################
69              
70             =head1 SYNOPSIS
71              
72             For the general usage of I objects in code that calls
73             Mail::SPF, see L. For the detailed interface of I
74             and its derivatives, see below.
75              
76             =head2 Throwing results
77              
78             package Mail::SPF::Foo;
79             use Error ':try';
80             use Mail::SPF::Result;
81              
82             sub foo {
83             if (...) {
84             $server->throw_result('pass', $request)
85             }
86             else {
87             $server->throw_result('permerror', $request, 'Invalid foo');
88             }
89             }
90              
91             =head2 Catching results
92              
93             package Mail::SPF::Bar;
94             use Error ':try';
95             use Mail::SPF::Foo;
96              
97             try {
98             Mail::SPF::Foo->foo();
99             }
100             catch Mail::SPF::Result with {
101             my ($result) = @_;
102             ...
103             };
104              
105             =head2 Using results
106              
107             my $result_name = $result->name;
108             my $result_code = $result->code;
109             my $request = $result->request;
110             my $local_exp = $result->local_explanation;
111             my $authority_exp = $result->authority_explanation
112             if $result->can('authority_explanation');
113             my $spf_header = $result->received_spf_header;
114              
115             =cut
116              
117             # Implementation:
118             ##############################################################################
119              
120             =head1 DESCRIPTION
121              
122             An object of class B represents the result of an SPF
123             request.
124              
125             There is usually no need to construct an SPF result object directly using the
126             C constructor. Instead, use the C class method to signal to the
127             calling code that a definite SPF result has been determined. In other words,
128             use Mail::SPF::Result and its derivatives just like exceptions. See L
129             or L for how to handle exceptions in Perl.
130              
131             =head2 Constructor
132              
133             The following constructor is provided:
134              
135             =over
136              
137             =item B: returns I
138              
139             =item B: returns I
140              
141             Creates a new SPF result object and associates the given I
142             and I objects with it. An optional result text may be
143             specified.
144              
145             =cut
146              
147             sub new {
148 5     5 1 597 my ($self, @args) = @_;
149              
150 5         10 local $Error::Depth = $Error::Depth + 1;
151              
152 5 100       41 $self =
153             ref($self) ? # Was new() invoked on a class or an object?
154             bless({ %$self }, ref($self)) # Object: clone source result object.
155             : $self->SUPER::new(); # Class: create new result object.
156              
157             # Set/override fields:
158 5 50       524 $self->{server} = shift(@args) if @args;
159 5 50       18 defined($self->{server})
160             or throw Mail::SPF::EOptionRequired('Mail::SPF server object required');
161 5 50       16 $self->{request} = shift(@args) if @args;
162 5 50       15 defined($self->{request})
163             or throw Mail::SPF::EOptionRequired('Request object required');
164 5 100       14 $self->{'-text'} = shift(@args) if @args;
165              
166 5         15 return $self;
167             }
168              
169             =back
170              
171             =head2 Class methods
172              
173             The following class methods are provided:
174              
175             =over
176              
177             =item B: throws I
178              
179             =item B: throws I
180              
181             Throws a new SPF result object, associating the given I and
182             I objects with it. An optional result text may be
183             specified.
184              
185             I: Do not write code invoking C on I result class names
186             as this would ignore any derivative result classes provided by B
187             extension modules. Invoke the L|Mail::SPF::Server/throw_result>
188             method on a I object instead.
189              
190             =cut
191              
192             sub throw {
193 2     2 1 294 my ($self, @args) = @_;
194 2         4 local $Error::Depth = $Error::Depth + 1;
195 2         6 $self = $self->new(@args);
196             # Always create/clone a new result object, not just when throwing for the first time!
197 2         10 die($Error::THROWN = $self);
198             }
199              
200             =item B: returns I
201              
202             I. Returns the result name of the result class (or object). For
203             classes of the I hierarchy, this roughly corresponds to
204             the trailing part of the class name. For example, returns C
205             if invoked on I. Also see the L
206             method. This method may also be used as an instance method.
207              
208             This method must be implemented by sub-classes of Mail::SPF::Result for which
209             the result I differs from the result I.
210              
211             =cut
212              
213             # This method being implemented here does not make it any less abstract,
214             # because the code() method it uses is still abstract.
215             sub name {
216 0     0 0 0 my ($self) = @_;
217 0         0 return $self->code;
218             }
219              
220             =item B: returns I
221              
222             =item B: returns I
223              
224             Maps the given result name to the corresponding I class,
225             or returns the result base class (the class on which it is invoked) if no
226             result name is given. If an unknown result name is specified, returns
227             B.
228              
229             =cut
230              
231             sub class {
232 9     9 1 1003 my ($self, $name) = @_;
233 9 100 33     68 return defined($name) ? $self->result_classes->{lc($name)} : (ref($self) || $self);
234             }
235              
236             =item B: returns I
237              
238             If the class (or object) on which this method is invoked represents the given
239             result name (or a derivative name), returns B. Returns B
240             otherwise. This method may also be used as an instance method.
241              
242             For example, C<< Mail::SPF::Result::NeutralByDefault->isa_by_name('neutral') >>
243             returns B.
244              
245             =cut
246              
247             sub isa_by_name {
248 6     6 1 669 my ($self, $name) = @_;
249 6         22 my $suspect_class = $self->class($name);
250 6 100       27 return FALSE if not defined($suspect_class);
251 4         31 return $self->isa($suspect_class);
252             }
253              
254             =item B: returns I
255              
256             I. Returns the basic SPF result code (C<"pass">, C<"fail">,
257             C<"softfail">, C<"neutral">, C<"none">, C<"error">, C<"permerror">,
258             C<"temperror">) of the result class on which it is invoked. All valid result
259             codes are valid result names as well, the reverse however does not apply. This
260             method may also be used as an instance method.
261              
262             This method is abstract and must be implemented by sub-classes of
263             Mail::SPF::Result.
264              
265             =item B: returns I
266              
267             If the class (or object) on which this method is invoked represents the given
268             result code, returns B. Returns B otherwise. This method may
269             also be used as an instance method.
270              
271             I: The L method provides a superset of this method's
272             functionality.
273              
274             =cut
275              
276             sub is_code {
277 2     2 1 5 my ($self, $code) = @_;
278 2         5 return $self->isa_by_name($code);
279             }
280              
281             =item B: returns I
282              
283             Returns B<'Received-SPF'> as the field name for C header fields.
284             This method should be overridden by B extension modules that provide
285             non-standard features (such as local policy) with the capacity to dilute the
286             purity of SPF results, in order not to deceive users of the header field into
287             mistaking it as an indication of a natural SPF result.
288              
289             =back
290              
291             =head2 Instance methods
292              
293             The following instance methods are provided:
294              
295             =over
296              
297             =item B: throws I
298              
299             =item B: throws I
300              
301             =item B: throws I
302              
303             Re-throws an existing SPF result object. If I and
304             I objects are specified, associates them with the result
305             object, replacing the prior server and request objects. If a result text is
306             specified as well, overrides the prior result text.
307              
308             =item B: returns I
309              
310             Returns the Mail::SPF server object that produced the result at hand.
311              
312             =item B: returns I
313              
314             Returns the SPF request that led to the result at hand.
315              
316             =cut
317              
318             # Read-only accessors:
319             __PACKAGE__->make_accessor($_, TRUE)
320             foreach qw(server request);
321              
322             =item B: returns I
323              
324             Returns the text message of the result object.
325              
326             =item B: returns I
327              
328             Returns the result's name and text message formatted as a string. You can
329             simply use a Mail::SPF::Result object as a string for the same effect, see
330             L.
331              
332             =cut
333              
334             sub stringify {
335 0     0 1   my ($self) = @_;
336 0           return sprintf(
337             "%s (%s)",
338             $self->name,
339             Mail::SPF::Util->sanitize_string($self->SUPER::stringify)
340             );
341             }
342              
343             =item B: returns I; throws I,
344             I
345              
346             Returns a locally generated explanation for the result.
347              
348             The local explanation is prefixed with the authority domain whose sender policy
349             is responsible for the result. If the responsible sender policy referred to
350             another domain's policy (using the C mechanism or the C
351             modifier), that other domain which is I responsible for the result is
352             also included in the local explanation's head. For example:
353              
354             example.com:
355              
356             The authority domain C's sender policy is directly responsible for
357             the result.
358              
359             example.com ... other.example.org:
360              
361             The authority domain C (directly or indirectly) referred to the
362             domain C, whose sender policy then led to the result.
363              
364             =cut
365              
366             sub local_explanation {
367 0     0 0   my ($self) = @_;
368 0           my $local_explanation = $self->{local_explanation};
369              
370 0 0         return $local_explanation
371             if defined($local_explanation);
372              
373             # Prepare local explanation:
374 0           my $request = $self->{request};
375 0           $local_explanation = $request->state('local_explanation');
376 0 0         if (defined($local_explanation)) {
377 0           $local_explanation = sprintf("%s (%s)", $local_explanation->expand, lcfirst($self->text));
378             }
379             else {
380 0           $local_explanation = $self->text;
381             }
382              
383             # Resolve authority domains of root-request and bottom sub-request:
384 0           my $root_request = $request->root_request;
385 0 0         $local_explanation =
386             $request == $root_request ?
387             sprintf("%s: %s", $request->authority_domain, $local_explanation)
388             : sprintf("%s ... %s: %s",
389             $root_request->authority_domain, $request->authority_domain, $local_explanation);
390              
391 0           return $self->{local_explanation} = Mail::SPF::Util->sanitize_string($local_explanation);
392             }
393              
394             =item B: returns I
395              
396             Returns a string containing an appropriate C header field for the
397             result object. The header field is not line-wrapped and contains no trailing
398             newline character.
399              
400             =cut
401              
402             sub received_spf_header {
403 0     0 0   my ($self) = @_;
404 0 0         return $self->{received_spf_header}
405             if defined($self->{received_spf_header});
406 0           my $scope_name =
407             $self->received_spf_header_scope_names_by_scope->{$self->{request}->scope};
408 0           my $identity_key_name =
409             $self->received_spf_header_identity_key_names_by_scope->{$self->{request}->scope};
410 0 0 0       my @info_pairs = (
      0        
411             receiver => $self->{server}->hostname || 'unknown',
412             identity => $scope_name,
413             $identity_key_name => $self->{request}->identity,
414             (
415             ($self->{request}->scope ne 'helo' and defined($self->{request}->helo_identity)) ?
416             (helo => $self->{request}->helo_identity)
417             : ()
418             ),
419             'client-ip' => Mail::SPF::Util->ip_address_to_string($self->{request}->ip_address)
420             );
421 0           my $info_string;
422 0           while (@info_pairs) {
423 0           my $key = shift(@info_pairs);
424 0           my $value = shift(@info_pairs);
425 0 0         $info_string .= '; ' if defined($info_string);
426 0 0         if ($value !~ /^${\dot_atom_pattern}$/o) {
  0            
427 0           $value =~ s/(["\\])/\\$1/g; # Escape '\' and '"' characters.
428 0           $value = '"' . $value . '"'; # Double-quote value.
429             }
430 0           $info_string .= "$key=$value";
431             }
432 0           return $self->{received_spf_header} = sprintf(
433             "%s: %s (%s) %s",
434             $self->received_spf_header_name,
435             $self->code,
436             $self->local_explanation,
437             $info_string
438             );
439             }
440              
441             =back
442              
443             =head1 OVERLOADING
444              
445             If a Mail::SPF::Result object is used as a I, the L method
446             is used to convert the object into a string.
447              
448             =head1 RESULT CLASSES
449              
450             The following result classes are provided:
451              
452             =over
453              
454             =item *
455              
456             I
457              
458             =item *
459              
460             I
461              
462             =item *
463              
464             I
465              
466             =item *
467              
468             I
469              
470             =over
471              
472             =item *
473              
474             I
475              
476             This is a special case of the C result that is thrown as a default
477             when "falling off" the end of the record during evaluation. See RFC 4408,
478             4.7.
479              
480             =back
481              
482             =item *
483              
484             I
485              
486             =item *
487              
488             I
489              
490             =over
491              
492             =item *
493              
494             I
495              
496             =item *
497              
498             I
499              
500             =back
501              
502             =back
503              
504             The following result classes have additional functionality:
505              
506             =over
507              
508             =item I
509              
510             The following additional instance method is provided:
511              
512             =over
513              
514             =item B: returns I; throws I,
515             I
516              
517             Returns the authority domain's explanation for the result. Be aware that the
518             authority domain may be a malicious party and thus the authority explanation
519             should not be trusted blindly. See RFC 4408, 10.5, for a detailed discussion
520             of this issue.
521              
522             =back
523              
524             =back
525              
526             =cut
527              
528             package Mail::SPF::Result::Pass;
529             our @ISA = 'Mail::SPF::Result';
530 4     4   33743 use constant code => 'pass';
  4         13  
  4         448  
531              
532             package Mail::SPF::Result::Fail;
533             our @ISA = 'Mail::SPF::Result';
534 4     4   25 use Error ':try';
  4         17  
  4         41  
535 4     4   748 use Mail::SPF::Exception;
  4         11  
  4         39  
536 4     4   223 use constant code => 'fail';
  4         10  
  4         2276  
537              
538             sub authority_explanation {
539 0     0     my ($self) = @_;
540 0           my $authority_explanation = $self->{authority_explanation};
541              
542 0 0         return $authority_explanation
543             if defined($authority_explanation);
544              
545 0           my $server = $self->{server};
546 0           my $request = $self->{request};
547              
548 0           my $authority_explanation_macrostring = $request->state('authority_explanation');
549              
550             # If an explicit explanation was specified by the authority domain...
551 0 0         if (defined($authority_explanation_macrostring)) {
552             try {
553             # ... then try to expand it:
554 0     0     $authority_explanation = $authority_explanation_macrostring->expand;
555             }
556 0     0     catch Mail::SPF::EInvalidMacroString with {};
  0            
557             # Ignore expansion errors and leave authority explanation undefined.
558             }
559              
560             # If no authority explanation could be determined so far...
561 0 0         if (not defined($authority_explanation)) {
562             # ... then use the server's default authority explanation:
563 0           $authority_explanation =
564             $server->default_authority_explanation->new(request => $request)->expand;
565             }
566              
567 0           return $self->{authority_explanation} = $authority_explanation;
568             }
569              
570             package Mail::SPF::Result::SoftFail;
571             our @ISA = 'Mail::SPF::Result';
572 4     4   69 use constant code => 'softfail';
  4         8  
  4         406  
573              
574             package Mail::SPF::Result::Neutral;
575             our @ISA = 'Mail::SPF::Result';
576 4     4   25 use constant code => 'neutral';
  4         11  
  4         307  
577              
578             package Mail::SPF::Result::NeutralByDefault;
579             our @ISA = 'Mail::SPF::Result::Neutral';
580 4     4   26 use constant name => 'neutral-by-default';
  4         9  
  4         344  
581             # This is a special-case of the Neutral result that is thrown as a default
582             # when "falling off" the end of the record. See Mail::SPF::Record::eval().
583              
584             package Mail::SPF::Result::None;
585             our @ISA = 'Mail::SPF::Result';
586 4     4   23 use constant code => 'none';
  4         8  
  4         333  
587              
588             package Mail::SPF::Result::Error;
589             our @ISA = 'Mail::SPF::Result';
590 4     4   25 use constant code => 'error';
  4         9  
  4         365  
591              
592             package Mail::SPF::Result::PermError;
593             our @ISA = 'Mail::SPF::Result::Error';
594 4     4   41 use constant code => 'permerror';
  4         7  
  4         1815  
595              
596             package Mail::SPF::Result::TempError;
597             our @ISA = 'Mail::SPF::Result::Error';
598 4     4   29 use constant code => 'temperror';
  4         8  
  4         405  
599              
600             =head1 SEE ALSO
601              
602             L, L, L, L
603              
604             L
605              
606             For availability, support, and license information, see the README file
607             included with Mail::SPF.
608              
609             =head1 AUTHORS
610              
611             Julian Mehnle
612              
613             =cut
614              
615             package Mail::SPF::Result;
616              
617             TRUE;