File Coverage

lib/Crypt/Password.pm
Criterion Covered Total %
statement 57 77 74.0
branch 15 30 50.0
condition 5 12 41.6
subroutine 14 17 82.3
pod 3 9 33.3
total 94 145 64.8


line stmt bran cond sub pod time code
1             package Crypt::Password;
2 1     1   579 use Exporter 'import';
  1         2  
  1         55  
3             @EXPORT = (qw'password crypt_password check_password');
4             our $VERSION = "0.28";
5             our $TESTMODE = 0;
6              
7 1     1   3 use Carp;
  1         1  
  1         1659  
8              
9             # from libc6 crypt/crypt-entry.c
10             our %alg_to_id = (
11             md5 => '1',
12             blowfish => '2',
13             sha256 => '5',
14             sha512 => '6',
15             );
16             our %id_to_alg = reverse %alg_to_id;
17              
18             # switches off embodying crypted-looking passwords, like crypt_password()
19             our $definitely_crypt;
20              
21             our $crypt_flav = do {
22             $^O =~ /^MSWin|cygwin/ ? 'windows' : do {
23             $_ = (`man crypt`)[-1];
24             !defined($_) ? 'freesec' :
25             /DragonFly/ ? 'dragonflybsd' :
26             /NetBSD/ ? 'netbsd' :
27             /OpenBSD/ ? 'openbsd' :
28             /FreeBSD/ ? do {
29             /FreeBSD ([\d\.]+)/; # seems 9.0 starts supporting Modular format
30             $1 >= 9 ? 'freebsd' : 'freebsd_lt_9'
31             } :
32             /MirOS/ ? 'windows' :
33             /FreeSec/ ? 'freesec' :
34             'glib'
35             }
36             };
37             our $flav_dispatch = {
38             glib => {
39             looks_crypted => sub {
40             return $_[0] =~ m{^\$.+\$.*\$.+$}
41             },
42             salt_provided => sub {
43             return shift;
44             },
45             extract_salt => sub {
46             return (split /\$/, $_[0])[2]
47             },
48             format_crypted => sub {
49             return shift;
50             },
51             form_salt => sub {
52             my ($s, $self) = @_;
53             unless ($s =~ /^\$.+\$.*(\$.+)?$/) {
54             if ($self->{algorithm_id}) {
55             # put algorithm id in the salt
56             $s =~ s/^/\$$self->{algorithm_id}\$/;
57             }
58             else {
59             # ->check(), alg and salt from ourselves, the rest be ignored
60             $s = "$self";
61             }
62             }
63             $s = $1 if $s =~ /^(\$.+?\$.+?)\$/;
64             return $s;
65             },
66             default_algorithm => sub {
67             return "sha256";
68             },
69             },
70             freesec => { # {{{
71             looks_crypted => sub {
72             # with our dollar-signs added in around the salt
73             return $_[0] =~ /^\$(_.{8}|.{2})\$ (.{11})?$/x
74             },
75             salt_provided => sub {
76             my $provided = shift;
77             # salt must be 2 or 8 or entropy leaks in around the side
78             # I am serious
79             if ($provided =~ m/^\$(_.{8}|_?.{2})\$(.{11})?$/
80             || $provided =~ m/^ (_.{8}|_?.{2}) (.{11})?$/x) {
81             $provided = $1;
82             }
83             if ($provided =~ /^_..?$/) {
84             croak "Bad salt input:"
85             ." 2-character salt cannot start with _";
86             }
87             $provided =~ s/^_//;
88             if ($provided !~ m/^(.{8}|.{2})$/) {
89             croak "Bad salt input:"
90             ." salt must be 2 or 8 characters long";
91             }
92             return $provided;
93             },
94             extract_salt => sub {
95             $_[0] =~ /^\$(_.{8}|.{2})\$ (.{11})?$/x;
96             my $s = $1;
97             $s || croak "Bad crypted input:"
98             ." salt must be 2 or 8 characters long";
99             $s =~ s/^_//;
100             return $s
101             },
102             format_crypted => sub {
103             my $crypt = shift;
104             # makes pretty ambiguous crypt strings, lets add some dollar signs
105             $crypt =~ s/^(_.{8}|..)(.{11})$/\$$1\$$2/
106             || croak "failed to understand Extended-format crypt: '$crypt'";
107             return $crypt;
108             },
109             form_salt => sub {
110             my ($s) = @_;
111             if (length($s) == 8) {
112             $s = "_$s"
113             }
114             return $s;
115             },
116             default_algorithm => sub {
117             return "DES" # does nothing
118             },
119             }, # }}}
120             freebsd_lt_9 => {
121             base => "freesec",
122             },
123             netbsd => {
124             base => "freebsd_lt_9",
125             },
126             openbsd => {
127             base => "freebsd_lt_9",
128             },
129             dragonflybsd => {
130             base => "freebsd_lt_9",
131             },
132             freebsd => {
133             base => "glib",
134             },
135             windows => {
136             base => "freesec",
137             looks_crypted => sub {
138             return $_[0] =~ /^\$.+\$.+$/
139             },
140             salt_provided => sub {
141             $_[0] =~ /^\$(.+?)\$.+/ ? $1 : $_[0]
142             },
143             extract_salt => sub {
144             $_[0] =~ /^\$(.+?)\$.+$/; return $1
145             },
146             format_crypted => sub {
147             my ($c, $i, $s) = @_;
148             my ($sa, $sb) = $s =~ /^(..)(.+)$/;
149             if ($TESTMODE) {
150             warn "# '$c' ".($c =~ /^$sb(?!$sa)/?"":"!")."= first 2 chars of salt ($s";
151             }
152             # first two characters of salt is used, it seems
153             $c =~ s/^$sa/\$$s\$/;
154             return $1;
155             },
156             form_salt => sub {
157             return shift;
158             $_[0] =~ /^(\$|,|_)/ ? $_[0] : "_$_[0]"
159             },
160             },
161             };
162              
163             sub flav {
164 4     4 0 5 my $func = shift;
165 4   50     7 my $flav = $flav_dispatch->{$crypt_flav} || die;
166 4 50       12 unless (exists $flav->{$func}) {
167 0 0       0 if (exists $flav->{base}) {
168 0         0 local $crypt_flav = $flav->{base};
169 0         0 return flav($func, @_);
170             }
171 0         0 die "no $func handler for (crypt flavour: $crypt_flav)";
172             }
173 4         9 return $flav->{$func}->(@_);
174             }
175              
176             sub new {
177 0     0 0 0 shift;
178 0         0 password(@_);
179             }
180              
181             sub password {
182             return _password(@_)->{crypted}
183 1     1 1 405 }
184              
185             sub crypt_password {
186 0     0 1 0 local $definitely_crypt = 1;
187 0         0 return password(@_);
188             }
189              
190             sub check_password {
191 0     0 1 0 my ($saved, $wild) = @_;
192 0         0 return $saved eq crypt_password($wild, $saved);
193             }
194              
195             sub _password {
196 1     1   3 my $self = bless {}, __PACKAGE__;
197              
198 1         6 $self->input(shift);
199            
200 1 50       12 unless ($self->{crypted}) {
201 1         2 $self->salt(shift);
202            
203 1         3 $self->algorithm(shift);
204            
205 1         1 $self->crypt();
206             }
207              
208 0         0 $self;
209             }
210              
211             sub crypt {
212 1     1 0 1 my $self = shift;
213            
214 1   33     4 $self->{crypted} ||= $self->_crypt;
215              
216 0         0 return "$self->{crypted}";
217             }
218              
219             sub input {
220 1     1 0 1 my $self = shift;
221 1         2 $self->{input} = shift;
222 1 50 33     6 if (!$definitely_crypt && $self->_looks_crypted($self->{input})) {
223             $self->{crypted} = delete $self->{input}
224 0         0 }
225             }
226              
227             sub _looks_crypted {
228 1     1   1 my $self = shift;
229 1   50     2 my $string = shift || return;
230              
231 1         3 return flav(looks_crypted => $string);
232             }
233              
234             sub salt {
235 22     22 0 1910 my $self = shift;
236 22         21 my $provided = shift;
237 22 50       31 if (defined $provided) {
238 0         0 $self->{salt} = flav(salt_provided => $provided);
239             }
240             else {
241 22 100       47 return $self->{salt} if defined $self->{salt};
242 21         18 return $self->{salt} = do {
243 21 50       24 if ($self->{crypted}) {
244 0         0 return flav(extract_salt => $self->{crypted});
245             }
246             else {
247 21         26 $self->_invent_salt()
248             }
249             };
250             }
251             }
252              
253             sub algorithm {
254 2     2 0 1 my $self = shift;
255 2         2 $alg = shift;
256 2 100       4 if ($alg) {
    50          
257 1         6 $alg =~ s/^\$?(.+)\$?$/$1/;
258 1 50       4 if (exists $alg_to_id{lc $alg}) {
259 0         0 $self->{algorithm_id} = $alg_to_id{lc $alg};
260 0         0 $self->{algorithm} = lc $alg;
261             }
262             else {
263             # $alg will be passed anyway, it may not be known to %id_to_alg
264 1         2 $self->{algorithm_id} = $alg;
265 1         3 $self->{algorithm} = $id_to_alg{lc $alg};
266             }
267             }
268             elsif (!$self->{algorithm}) {
269 1         2 $self->algorithm(flav("default_algorithm"));
270             }
271             else {
272             $self->{algorithm}
273 0         0 }
274             }
275              
276             sub _crypt {
277 1     1   1 my $self = shift;
278            
279 1 50       3 defined $self->{input} || croak "no input!";
280 1 50       2 $self->{algorithm_id} || croak "no algorithm!";
281 1 50       3 defined $self->{salt} || croak "invalid salt!";
282              
283 1         2 my $input = delete $self->{input};
284 1         2 my $salt = $self->_form_salt();
285              
286 1         2 return _do_crypt($input, $salt);
287             }
288              
289             sub _do_crypt {
290 1     1   1 my ($input, $salt) = @_;
291 1         310 my $crypt = CORE::crypt($input, $salt);
292 1         4 $crypt = flav(format_crypted => $crypt, $input, $salt);
293 0 0       0 warn "# $input $salt = $crypt\n" if $TESTMODE;
294 0         0 return $crypt;
295             }
296              
297             sub _form_salt {
298 1     1   0 my $self = shift;
299 1         2 my $s = $self->salt;
300 1 50       2 croak "undef salt!?" unless defined $s;
301 1         2 return flav(form_salt => $s, $self);
302             }
303              
304             our @valid_salt = ( "/", ".", "a".."z", "A".."Z", "0".."9" );
305              
306             sub _invent_salt {
307 21   50 21   62 my $many = $_[1] || 8;
308 21         25 join "", map { $valid_salt[rand(@valid_salt)] } 1..$many;
  168         319  
309             }
310              
311             1;
312              
313             __END__