File Coverage

blib/lib/Mail/SPF/Result.pm
Criterion Covered Total %
statement 98 143 68.5
branch 12 38 31.5
condition 1 11 9.0
subroutine 30 37 81.0
pod 6 9 66.6
total 147 238 61.7


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