File Coverage

blib/lib/Mail/SPF/Request.pm
Criterion Covered Total %
statement 79 89 88.7
branch 34 42 80.9
condition 13 23 56.5
subroutine 14 16 87.5
pod 5 6 83.3
total 145 176 82.3


line stmt bran cond sub pod time code
1             #
2             # Mail::SPF::Request
3             # SPF request class.
4             #
5             # (C) 2005-2012 Julian Mehnle
6             # 2005 Shevek
7             # $Id: Request.pm 57 2012-01-30 08:15:31Z julian $
8             #
9             ##############################################################################
10              
11             package Mail::SPF::Request;
12              
13             =head1 NAME
14              
15             Mail::SPF::Request - SPF request class
16              
17             =cut
18              
19 4     4   88163 use warnings;
  4         12  
  4         148  
20 4     4   26 use strict;
  4         7  
  4         164  
21              
22 4     4   22 use base 'Mail::SPF::Base';
  4         8  
  4         1550  
23              
24 4     4   2710 use NetAddr::IP;
  4         97342  
  4         36  
25              
26 4     4   2095 use Mail::SPF::Util;
  4         13  
  4         135  
27              
28 4     4   23 use constant TRUE => (0 == 0);
  4         8  
  4         251  
29 4     4   21 use constant FALSE => not TRUE;
  4         8  
  4         329  
30              
31 4         254 use constant versions_for_scope => {
32             helo => [1 ],
33             mfrom => [1, 2],
34             pra => [ 2]
35 4     4   21 };
  4         8  
36              
37 4         186 use constant scopes_by_version => {
38             1 => ['helo', 'mfrom' ],
39             2 => [ 'mfrom', 'pra']
40 4     4   21 };
  4         7  
41              
42 4     4   21 use constant default_localpart => 'postmaster';
  4         10  
  4         4942  
43              
44             # Interface:
45             ##############################################################################
46              
47             =head1 SYNOPSIS
48              
49             use Mail::SPF;
50              
51             my $request = Mail::SPF::Request->new(
52             versions => [1, 2], # optional
53             scope => 'mfrom', # or 'helo', 'pra'
54             identity => 'fred@example.com',
55             ip_address => '192.168.0.1',
56             helo_identity # optional,
57             => 'mta.example.com' # for %{h} macro expansion
58             );
59              
60             my @versions = $request->versions;
61             my $scope = $request->scope;
62             my $authority_domain
63             = $request->authority_domain;
64             my $identity = $request->identity; # 'localpart@domain' or 'domain'
65             my $domain = $request->domain;
66             my $localpart = $request->localpart;
67             my $ip_address = $request->ip_address; # IPv4 or IPv6 address
68             my $ip_address_v6 # native IPv6 address or
69             = $request->ip_address_v6; # IPv4-mapped IPv6 address
70             my $helo_identity # additional HELO identity
71             = $request->helo_identity; # for non-HELO scopes
72              
73             my $record = $request->record;
74             # the record selected during processing of the request, may be undef
75              
76             $request->state(field => 'value');
77             my $value = $request->state('field');
78              
79             =cut
80              
81             # Implementation:
82             ##############################################################################
83              
84             =head1 DESCRIPTION
85              
86             An object of class B represents an SPF request.
87              
88             =head2 Constructors
89              
90             The following constructors are provided:
91              
92             =over
93              
94             =item B: returns I
95              
96             Creates a new SPF request object. The request is considered the
97             I for any subsequent sub-requests (see the L
98             constructor).
99              
100             %options is a list of key/value pairs representing any of the following
101             options:
102              
103             =over
104              
105             =item B
106              
107             A reference to an I of Is listing the versions of SPF records
108             that may be used for the SPF check. Only those record versions that cover the
109             desired scope will actually be used. At least one applicable version must be
110             specified. For a single record version, a simple scalar may be specified
111             instead of an array-ref. Defaults to all versions that cover the desired scope
112             (see below); defaults to B<[1, 2]> for the default scope of B<'mfrom'>.
113              
114             The following versions are supported:
115              
116             =over
117              
118             =item B<1>
119              
120             Use C records.
121              
122             =item B<2>
123              
124             Use C records.
125              
126             =back
127              
128             I: A value of B<1> (or B<[1]>) means that only C records
129             should be used for the SPF check. If at the same time a scope of B<'pra'> is
130             specified, a I exception will be thrown as C
131             records do not cover the PRA scope.
132              
133             =item B
134              
135             A string denoting the authorization scope of the identity that should be
136             checked. Defaults to B<'mfrom'>. The following scope values are supported:
137              
138             =over
139              
140             =item B<'helo'>
141              
142             The given identity is the C parameter of an SMTP transaction (RFC 2821)
143             and should be checked against SPF records that cover the C scope
144             (C). See the SPFv1 specification (RFC 4408) for the formal definition
145             of the C scope.
146              
147             =item B<'mfrom'>
148              
149             The given identity is the C parameter of an SMTP transaction (RFC
150             2821), and should be checked against SPF records that cover the C scope
151             (C and C). See the SPFv1 specification (RFC 4408) for
152             the formal definition of the C scope.
153              
154             I: In the case of an empty C SMTP transaction parameter (C<<
155             MAIL FROM:<> >>), you should perform a check with the C scope instead.
156              
157             =item B<'pra'>
158              
159             The given identity is the "Purported Responsible Address" of an internet
160             message (RFC 2822) and should be checked against SPF records that cover the
161             C scope (C). See the PRA specification (RFC 4407) for the
162             formal definition of the PRA scope.
163              
164             =back
165              
166             =item B
167              
168             A string denoting the domain name that should be queried for sender policy
169             records. Defaults to the domain of the C option. There is usually
170             no need to specify the C option.
171              
172             =item B
173              
174             I. A string denoting the sender identity whose authorization should
175             be checked. This is a domain name for the C scope, and an e-mail address
176             for the C and C scopes.
177              
178             I: An empty identity must not be passed. In the case of an empty C
179             FROM> SMTP transaction parameter, you should perform a check with the C
180             scope instead.
181              
182             =item B
183              
184             I for checks with the C, C, and C scopes. Either a
185             string or a I object denoting the IP address of the host claiming
186             the identity that is being checked. Can be either an IPv4 or an IPv6 address.
187             An IPv4-mapped IPv6 address (e.g. '::ffff:192.168.0.1') is treated as an IPv4
188             address.
189              
190             =item B
191              
192             A string denoting the C SMTP transaction parameter in the case that the
193             main identity is of a scope other than C. This identity is then used
194             merely for the expansion of C<%{h}> macros during the policy evaluation of the
195             main identity. Defaults to B, which will be expanded to B<'unknown'>.
196             If the main identity is of the C scope, this option is unused.
197              
198             =back
199              
200             =cut
201              
202             sub new {
203 18     18 1 6382 my ($self, %options) = @_;
204              
205             # Create new object:
206 18         115 $self = $self->SUPER::new(%options);
207             # If the request object already has a state hash, clone its contents:
208 18 100       79 $self->{state} = { %{$self->{state}} }
  1         5  
209             if ref($self->{state}) eq 'HASH';
210              
211             # Scope:
212 18   100     157 $self->{scope} ||= 'mfrom';
213 18 100       112 my $versions_for_scope = $self->versions_for_scope->{$self->{scope}}
214             or throw Mail::SPF::EInvalidScope("Invalid scope '$self->{scope}'");
215              
216             # Versions:
217 17 100       47 if (not defined($self->{versions})) {
218             # No versions specified, use all versions relevant to scope:
219 9         22 $self->{versions} = $versions_for_scope;
220             }
221             else {
222 8 100       40 if (not ref($self->{versions})) {
    100          
223             # Single version specified as scalar:
224 2         7 $self->{versions} = [$self->{versions}];
225             }
226             elsif (ref($self->{versions}) ne 'ARRAY') {
227             # Something other than scalar or array-ref specified:
228 1         90 throw Mail::SPF::EInvalidOptionValue(
229             "'versions' option must be string or array-ref");
230             }
231              
232             # All requested record versions must be supported:
233 7         50 my @unsupported_versions = grep(
234             (not defined($self->scopes_by_version->{$_})),
235 7         10 @{$self->{versions}}
236             );
237             not @unsupported_versions
238 7 100       30 or throw Mail::SPF::EInvalidOptionValue(
239             'Unsupported record version(s) ' .
240             join(', ', map("'$_'", @unsupported_versions)));
241              
242             # Use only those record versions that are relevant to the requested scope:
243 6         9 my %versions_for_scope;
244 6         28 @versions_for_scope{@$versions_for_scope} = ();
245 6         8 my @versions = grep(exists($versions_for_scope{$_}), @{$self->{versions}});
  6         27  
246              
247             # Require at least one relevant record version that covers the scope:
248             @versions
249 1         7 or throw Mail::SPF::EInvalidScope(
250             "Invalid scope '$self->{scope}' for record version(s) " .
251 6 100       20 join(', ', @{$self->{versions}}));
252              
253 5         19 $self->{versions} = \@versions;
254             }
255              
256             # Identity:
257 14 100       54 defined($self->{identity})
258             or throw Mail::SPF::EOptionRequired("Missing required 'identity' option");
259 13 50       41 length($self->{identity})
260             or throw Mail::SPF::EInvalidOptionValue("'identity' option must not be empty");
261              
262             # Extract domain and localpart from identity:
263 13 100 66     194 if (
      100        
264             ($self->{scope} eq 'mfrom' or $self->{scope} eq 'pra') and
265             $self->{identity} =~ /^(.*)@(.*?)$/
266             ) {
267 9         33 $self->{domain} = $2;
268 9         28 $self->{localpart} = $1;
269             }
270             else {
271 4         25 $self->{domain} = $self->{identity};
272             }
273 13         142 $self->{domain} =~ s/^(.*?)\.?$/\L$1/;
274             # Lower-case domain and remove eventual trailing dot.
275 13 100 66     77 $self->{localpart} = $self->default_localpart
276             if not defined($self->{localpart}) or not length($self->{localpart});
277              
278             # HELO identity:
279 13 100       38 if ($self->{scope} eq 'helo') {
280 2   33     13 $self->{helo_identity} ||= $self->{identity};
281             }
282              
283             # IP address:
284 13 100 66     110 throw Mail::SPF::EOptionRequired("Missing required 'ip_address' option")
285             if grep($self->{scope} eq $_, qw(helo mfrom pra))
286             and not defined($self->{ip_address});
287              
288             # Ensure ip_address is a NetAddr::IP object:
289 12 100       74 if (not UNIVERSAL::isa($self->{ip_address}, 'NetAddr::IP')) {
290 10 50       73 my $ip_address = NetAddr::IP->new($self->{ip_address})
291             or throw Mail::SPF::EInvalidOptionValue("Invalid IP address '$self->{ip_address}'");
292 10         6033 $self->{ip_address} = $ip_address;
293             }
294              
295             # Convert IPv4 address to IPv4-mapped IPv6 address:
296 12 100       75 if (Mail::SPF::Util->ipv6_address_is_ipv4_mapped($self->{ip_address})) {
    50          
    0          
297 1         1721 $self->{ip_address_v6} = $self->{ip_address}; # Accept as IPv6 address as-is.
298 1         7 $self->{ip_address} = Mail::SPF::Util->ipv6_address_to_ipv4($self->{ip_address});
299             }
300             elsif ($self->{ip_address}->version == 4) {
301 11         341 $self->{ip_address_v6} = Mail::SPF::Util->ipv4_address_to_ipv6($self->{ip_address});
302             }
303             elsif ($self->{ip_address}->version == 6) {
304 0         0 $self->{ip_address_v6} = $self->{ip_address};
305             }
306             else {
307 0         0 throw Mail::SPF::EInvalidOptionValue(
308             "Unexpected IP address version '" . $self->{ip_address}->version . "'");
309             }
310              
311 12         4109 return $self;
312             }
313              
314             =item B: returns I
315              
316             Must be invoked on an existing request object. Creates a new sub-request
317             object by cloning the invoked request, which is then considered the new
318             request's I. Any specified options (see the L
319             constructor) override the parameters of the super-request. There is usually no
320             need to specify any options I the C option.
321              
322             =cut
323              
324             sub new_sub_request {
325 0     0 1 0 my ($super_request, %options) = @_;
326 0 0       0 UNIVERSAL::isa($super_request, __PACKAGE__)
327             or throw Mail::SPF::EInstanceMethod;
328 0         0 my $self = $super_request->new(%options);
329 0         0 $self->{super_request} = $super_request;
330 0         0 $self->{root_request} = $super_request->root_request;
331 0         0 return $self;
332             }
333              
334             =back
335              
336             =head2 Instance methods
337              
338             The following instance methods are provided:
339              
340             =over
341              
342             =item B: returns I
343              
344             Returns the root of the request's chain of super-requests. Specifically,
345             returns the request itself if it has no super-requests.
346              
347             =cut
348              
349             sub root_request {
350 0     0 0 0 my ($self) = @_;
351             # Read-only!
352 0   0     0 return $self->{root_request} || $self;
353             }
354              
355             =item B: returns I
356              
357             Returns the super-request of the request, or B if there is none.
358              
359             =cut
360              
361             # Make read-only accessor:
362             __PACKAGE__->make_accessor('super_request', TRUE);
363              
364             =item B: returns I of I
365              
366             Returns a list of the SPF record versions that are used for request. See the
367             description of the L constructor's C option.
368              
369             =cut
370              
371             sub versions {
372 5     5 1 2719 my ($self) = @_;
373             # Read-only!
374 5         9 return @{$self->{versions}};
  5         40  
375             }
376              
377             =item B: returns I
378              
379             Returns the scope of the request. See the description of the L
380             constructor's C option.
381              
382             =item B: returns I
383              
384             Returns the authority domain of the request. See the description of the
385             L constructor's C option.
386              
387             =cut
388              
389             sub authority_domain {
390 6     6 1 11 my ($self) = @_;
391 6   33     56 return $self->{authority_domain} || $self->{domain};
392             }
393              
394             =item B: returns I
395              
396             Returns the identity of the request. See the description of the L
397             constructor's C option.
398              
399             =item B: returns I
400              
401             Returns the identity domain of the request. See the description of the
402             L constructor's C option.
403              
404             =item B: returns I
405              
406             Returns the identity localpart of the request. See the description of the
407             L constructor's C option.
408              
409             =item B: returns I
410              
411             Returns the IP address of the request as a I object. See the
412             description of the L constructor's C option.
413              
414             =item B: returns I
415              
416             Like the C method, however, an IPv4 address is returned as an
417             IPv4-mapped IPv6 address (e.g. '::ffff:192.168.0.1') to facilitate uniform
418             processing.
419              
420             =item B: returns I
421              
422             Returns the C SMTP transaction parameter of the request. See the
423             description of the L constructor's C option.
424              
425             =cut
426              
427             # Make read-only accessors:
428             __PACKAGE__->make_accessor($_, TRUE)
429             foreach qw(
430             scope identity domain localpart
431             ip_address ip_address_v6 helo_identity
432             );
433              
434             =item B: returns I
435              
436             Returns the SPF record selected during the processing of the request, or
437             B if there is none.
438              
439             =cut
440              
441             # Make read/write accessor:
442             __PACKAGE__->make_accessor('record', FALSE);
443              
444             =item B: returns anything
445              
446             =item B: returns anything
447              
448             Provides an interface for storing temporary state information with the request
449             object. This is primarily meant to be used internally by I
450             and other Mail::SPF classes.
451              
452             If C<$value> is specified, stores it in a state field named C<$field>. Returns
453             the current (new) value of the state field named C<$field>. This method may be
454             used as an lvalue.
455              
456             =cut
457              
458             sub state :lvalue {
459 5     5 1 23 my ($self, $field, @value) = @_;
460 5 50       13 defined($field)
461             or throw Mail::SPF::EOptionRequired('Field name required');
462 5 100       16 $self->{state}->{$field} = $value[0]
463             if @value;
464 5         31 $self->{state}->{$field};
465             }
466              
467             =back
468              
469             =head1 SEE ALSO
470              
471             L, L
472              
473             L
474              
475             For availability, support, and license information, see the README file
476             included with Mail::SPF.
477              
478             =head1 AUTHORS
479              
480             Julian Mehnle , Shevek
481              
482             =cut
483              
484             TRUE;