File Coverage

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