File Coverage

blib/lib/Email/Auth/AddressHash.pm
Criterion Covered Total %
statement 35 48 72.9
branch 12 18 66.6
condition 2 3 66.6
subroutine 7 15 46.6
pod 12 12 100.0
total 68 96 70.8


line stmt bran cond sub pod time code
1             ##############################################
2             # Email::Auth::AddressHash
3             #
4             # Copyright 2004, Tara L Andrews
5             #
6             #
7             # This program is distributed in the hope that it will be useful,
8             # but WITHOUT ANY WARRANTY; without even the implied warranty of
9             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10             #
11             ##############################################
12              
13             package Email::Auth::AddressHash;
14              
15             =head1 NAME
16              
17             Email::Auth::AddressHash - Authentication based on email address
18             extension hash
19              
20             =head1 SYNOPSIS
21              
22             use Email::Auth::AddressHash;
23              
24             my $auth = Email::Auth::AddressHash->new('hashlen' => 8,
25             'secret' = 'My Secret');
26              
27              
28             my $is_valid = $auth->check_hash('myuser@theirdomain.com', '83c3dac5');
29              
30             my $correct_answer = $auth->generate_hash('myuser@theirdomain.com');
31              
32             my $parts = $auth->split_address('myaddr+38274dc9@mydomain.com');
33              
34             my $passedhash = $parts->{'hash'};
35              
36             =head1 DESCRIPTION
37              
38             This is a relatively simple module designed for applications which
39             receive email. It provides a mechanism for authenticating email
40             requests, by checking that the To: address, which should be in the
41             form "username+hash@mydomain.com", contains the correct hash value for
42             that particular sender. It uses the sender address and a locally-set
43             secret string to determine the correct hash for the user. A single
44             AddressHash object may be used for multiple authentication checks
45             within the same system.
46              
47             =head1 METHODS
48              
49             =cut
50              
51 1     1   559 use strict;
  1         2  
  1         36  
52 1     1   6 use vars qw($VERSION);
  1         2  
  1         48  
53 1     1   5 use Digest::MD5 qw(md5_hex);
  1         5  
  1         2806  
54              
55             $VERSION = "1.0";
56              
57             =over 4
58              
59             =item Email::Auth::AddressHash->new($secret, $hashlen, $prefix, $hashtype)
60              
61             Takes four arguments. They are listed with their defaults. They
62             are described more fully in the ACCESSORS section.
63             'secret' - PLEASE set this; the default is stupid on purpose.
64             'hashlen' - Default is 6.
65             'prefix' - Default is no prefix.
66             'hashtype' - Default (and only supported type) is md5.
67              
68             If you do use a hash prefix, you may skip setting the 'prefix'
69             variable if you wish, just realize that you will have to strip the
70             prefix yourself before passing your hash to check_auth, instead of
71             letting the split_address method (see below) do it for you.
72              
73              
74             =cut
75              
76             sub new {
77 2     2 1 23 my ($class, $secret, $hashlen, $prefix, $hashtype) = @_;
78 2 50       6 $secret = 'swordfish' unless $secret;
79 2 50       6 $hashlen = 6 unless $hashlen;
80 2 50       7 $hashtype = 'md5' unless $hashtype;
81              
82 2         8 my $self = {'secret' => $secret,
83             'hashlen' => $hashlen,
84             'prefix' => $prefix,
85             'hashtype' => $hashtype
86             };
87              
88 2         5 bless ($self, $class);
89 2         6 return($self);
90             }
91              
92             =item $authenticator->check_hash($sender_address, $hashstring)
93              
94             Takes two arguments, the sender's address and the email extension
95             that the sender sent his/her request to. Returns true or false,
96             indicating whether the given hash matches the calculated hash.
97              
98             =cut
99              
100             sub check_hash {
101 4     4 1 13 my ($self, $address, $hash) = @_;
102 4 100       9 return ($hash eq $self->generate_hash($address, 0)) ? 1 : 0;
103             }
104              
105             =item $authenticator->generate_hash($sender_address, $with_prefix)
106              
107             Takes a single argument, the sender's address. Returns the correctly
108             calculated hash for the given sender. If $with_prefix is set to a
109             true value, the instance prefix (if any) is prepended.
110              
111             =cut
112              
113             sub generate_hash {
114 6     6 1 12 my ($self, $user, $withpre) = @_;
115 6 50       18 if ($self->{'hashtype'} eq 'md5') {
116 6         10 my $key = $user . $self->{'secret'};
117 6         31 my $md5key = substr(md5_hex($key), 0, $self->{'hashlen'});
118 6 100 66     27 if ($withpre && $self->{'prefix'}) {
119 1         5 $md5key = $self->{'prefix'} . $md5key;
120             }
121 6         31 return $md5key;
122             } else {
123 0         0 warn("Hash method " . $self->{'hashtype'} . " not recognized!");
124 0         0 return '';
125             }
126             }
127              
128             =item $partsref = $authenticator->split_address($address)
129              
130             =item $rcvdhash = $authenticator->split_address($address)->{'extension'}
131              
132             A convenience method. Takes an email address and returns a reference
133             to a hash containing the keys 'username', 'extension', and 'domain'.
134             Returns undef if parsing failed. This is a fine way to isolate the
135             hash to test against.
136              
137             =cut
138              
139             sub split_address {
140 4     4 1 1491 my ($self, $address) = @_;
141 4         6 my $answer = {};
142 4         6 my ($lhs, $rhs);
143 4 50       20 if ($address =~ /^([\w+-]+)@([\w.-]+)$/) {
144 4         14 ($lhs, $answer->{'domain'}) = ($1, $2);
145 4 50       17 if ($lhs =~ /^([\w-]+)\+(\w+)$/) {
146 4         12 ($answer->{'user'}, $answer->{'extension'}) = ($1, $2);
147 4 100       15 if ($self->{'prefix'}) {
148 2         20 $answer->{'extension'} =~ s/^$self->{'prefix'}//;
149             }
150             } else {
151 0         0 $answer->{'user'} = $lhs;
152             }
153             } else {
154 0         0 warn('Could not parse address');
155 0         0 return undef;
156             }
157 4         13 return $answer;
158             }
159              
160             # Accessors. Don't worry, I won't use gratuitous one-liners
161             # elsewhere.
162              
163             =back
164              
165             =head1 INSTANCE VARIABLES AND THEIR ACCESSORS
166              
167             =over 4
168              
169             =item $authenticator->set_secret('My Secret')
170              
171             =item $authenticator->get_secret()
172              
173             The authenticator secret is a string that is used in the hashing
174             algorithm. It should be set locally in your program. It should not
175             change too often, unless you like annoying your users by changing the
176             email address they should use for your program all the time.
177              
178             =cut
179              
180             sub set_secret {
181 0     0 1   $_[0]->secret = $_[1];
182             }
183              
184             sub get_secret {
185 0     0 1   return $_[0]->secret;
186             }
187              
188             =item $authenticator->set_prefix('ma')
189              
190             =item $authenticator->get_prefix()
191              
192             The prefix is a fixed string that you expect to appear at the
193             beginning of every email extension received by your application. You
194             may not need this, but it is useful if you expect a single email
195             account to be able to run several different programs, and want to
196             differentiate the requests via something like procmail. In the above
197             example, with the prefix set to 'ma', users should send all requests
198             to an address like myprog+ma38c319@mydomain.com.
199              
200             =cut
201              
202             sub set_prefix {
203 0     0 1   $_[0]->prefix = $_[1];
204             }
205              
206             sub get_prefix {
207 0     0 1   return $_[0]->prefix;
208             }
209              
210             =item $authenticator->set_hashlen($length)
211              
212             =item $authenticator->get_hashlen()
213              
214             This is the length you expect your authentication hashes to be, not
215             counting any prefix you have set. The default length is 6.
216              
217             =cut
218              
219             sub set_hashlen {
220 0     0 1   $_[0]->hashlen = $_[1];
221             }
222              
223             sub get_hashlen {
224 0     0 1   return $_[0]->hashlen;
225             }
226              
227             =item $authenticator->set_hashtype($type)
228              
229             =item $authenticator->get_hashtype()
230              
231             This is the hashing algorithm that the module should use. Currently
232             the only supported algorithm is md5.
233              
234             =cut
235              
236             sub set_hashtype {
237 0     0 1   $_[0]->hashtype = $_[1];
238             }
239              
240             sub get_hashtype {
241 0     0 1   return $_[0]->hashtype;
242             }
243              
244             1;
245              
246             =back
247              
248             =head1 AUTHOR
249              
250             Tara L Andrews
251              
252             =head1 SEE ALSO
253              
254             L
255              
256             =cut