line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package App::Genpass; |
2
|
|
|
|
|
|
|
# ABSTRACT: Quickly and easily create secure passwords |
3
|
|
|
|
|
|
|
$App::Genpass::VERSION = '2.401'; |
4
|
5
|
|
|
5
|
|
2203
|
use Carp; |
|
5
|
|
|
|
|
7
|
|
|
5
|
|
|
|
|
329
|
|
5
|
5
|
|
|
5
|
|
2589
|
use Moo; |
|
5
|
|
|
|
|
55526
|
|
|
5
|
|
|
|
|
23
|
|
6
|
5
|
|
|
5
|
|
8224
|
use Sub::Quote 'quote_sub'; |
|
5
|
|
|
|
|
13182
|
|
|
5
|
|
|
|
|
287
|
|
7
|
5
|
|
|
5
|
|
2616
|
use MooX::Types::MooseLike::Base qw/Int Str Bool ArrayRef/; |
|
5
|
|
|
|
|
25020
|
|
|
5
|
|
|
|
|
499
|
|
8
|
5
|
|
|
5
|
|
3861
|
use Getopt::Long qw/:config no_ignore_case/; |
|
5
|
|
|
|
|
52945
|
|
|
5
|
|
|
|
|
28
|
|
9
|
5
|
|
|
5
|
|
807
|
use File::Spec; |
|
5
|
|
|
|
|
7
|
|
|
5
|
|
|
|
|
89
|
|
10
|
5
|
|
|
5
|
|
2069
|
use Config::Any; |
|
5
|
|
|
|
|
35427
|
|
|
5
|
|
|
|
|
165
|
|
11
|
5
|
|
|
5
|
|
2716
|
use File::HomeDir; |
|
5
|
|
|
|
|
20930
|
|
|
5
|
|
|
|
|
290
|
|
12
|
5
|
|
|
5
|
|
2584
|
use List::AllUtils qw( any none shuffle ); |
|
5
|
|
|
|
|
54650
|
|
|
5
|
|
|
|
|
378
|
|
13
|
5
|
|
|
5
|
|
2510
|
use Math::Random::Secure; |
|
5
|
|
|
|
|
534823
|
|
|
5
|
|
|
|
|
5498
|
|
14
|
|
|
|
|
|
|
|
15
|
89997
|
|
|
89997
|
|
227648
|
sub _rand ($) { Math::Random::Secure::rand(shift) } |
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
has uppercase => ( |
18
|
|
|
|
|
|
|
is => 'ro', |
19
|
|
|
|
|
|
|
isa => ArrayRef, |
20
|
|
|
|
|
|
|
default => quote_sub( q{ [ 'A' .. 'Z' ] } ), |
21
|
|
|
|
|
|
|
); |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
has lowercase => ( |
24
|
|
|
|
|
|
|
is => 'ro', |
25
|
|
|
|
|
|
|
isa => ArrayRef, |
26
|
|
|
|
|
|
|
default => quote_sub( q{ [ 'a' .. 'z' ] } ), |
27
|
|
|
|
|
|
|
); |
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
has numerical => ( |
30
|
|
|
|
|
|
|
is => 'ro', |
31
|
|
|
|
|
|
|
isa => ArrayRef, |
32
|
|
|
|
|
|
|
default => quote_sub( q{ [ '0' .. '9' ] } ), |
33
|
|
|
|
|
|
|
); |
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
has unreadable => ( |
36
|
|
|
|
|
|
|
is => 'ro', |
37
|
|
|
|
|
|
|
isa => ArrayRef, |
38
|
|
|
|
|
|
|
default => quote_sub( q{ [ split //sm, q{oO0l1I} ] } ), |
39
|
|
|
|
|
|
|
); |
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
has specials => ( |
42
|
|
|
|
|
|
|
is => 'ro', |
43
|
|
|
|
|
|
|
isa => ArrayRef, |
44
|
|
|
|
|
|
|
default => quote_sub( q{ [ split //sm, q{!@#$%^&*()} ] } ), |
45
|
|
|
|
|
|
|
); |
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
has number => ( |
48
|
|
|
|
|
|
|
is => 'ro', |
49
|
|
|
|
|
|
|
isa => Int, |
50
|
|
|
|
|
|
|
default => quote_sub( q{1} ), |
51
|
|
|
|
|
|
|
); |
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
has readable => ( |
54
|
|
|
|
|
|
|
is => 'ro', |
55
|
|
|
|
|
|
|
isa => Bool, |
56
|
|
|
|
|
|
|
default => quote_sub( q{1} ), |
57
|
|
|
|
|
|
|
); |
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
has verify => ( |
60
|
|
|
|
|
|
|
is => 'ro', |
61
|
|
|
|
|
|
|
isa => Bool, |
62
|
|
|
|
|
|
|
default => quote_sub( q{1} ), |
63
|
|
|
|
|
|
|
); |
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
has length => ( |
66
|
|
|
|
|
|
|
is => 'ro', |
67
|
|
|
|
|
|
|
isa => Int, |
68
|
|
|
|
|
|
|
); |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
has minlength => ( |
71
|
|
|
|
|
|
|
is => 'rw', |
72
|
|
|
|
|
|
|
isa => Int, |
73
|
|
|
|
|
|
|
default => quote_sub( q{8} ), |
74
|
|
|
|
|
|
|
); |
75
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
has maxlength => ( |
77
|
|
|
|
|
|
|
is => 'rw', |
78
|
|
|
|
|
|
|
isa => Int, |
79
|
|
|
|
|
|
|
default => quote_sub( q{10} ), |
80
|
|
|
|
|
|
|
); |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
sub parse_opts { |
83
|
0
|
|
|
0
|
1
|
0
|
my $class = shift; |
84
|
0
|
|
|
|
|
0
|
my %opts = (); |
85
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
GetOptions( |
87
|
|
|
|
|
|
|
'configfile=s' => \$opts{'configfile'}, |
88
|
|
|
|
|
|
|
'lowercase=s@' => \$opts{'lowercase'}, |
89
|
|
|
|
|
|
|
'uppercase=s@' => \$opts{'uppercase'}, |
90
|
|
|
|
|
|
|
'numerical=i@' => \$opts{'numerical'}, |
91
|
|
|
|
|
|
|
'unreadable=s@' => \$opts{'unreadable'}, |
92
|
|
|
|
|
|
|
'specials=s@' => \$opts{'specials'}, |
93
|
|
|
|
|
|
|
'n|number=i' => \$opts{'number'}, |
94
|
|
|
|
|
|
|
'r|readable!' => \$opts{'readable'}, |
95
|
|
|
|
|
|
|
'v|verify!' => \$opts{'verify'}, |
96
|
|
|
|
|
|
|
'l|length=i' => \$opts{'length'}, |
97
|
|
|
|
|
|
|
'm|minlength=i' => \$opts{'minlength'}, |
98
|
0
|
0
|
|
|
|
0
|
'x|maxlength=i' => \$opts{'maxlength'}, |
99
|
|
|
|
|
|
|
) or croak q{Can't get options.}; |
100
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
# remove undefined keys |
102
|
0
|
|
|
|
|
0
|
foreach my $key ( keys %opts ) { |
103
|
0
|
0
|
|
|
|
0
|
defined $opts{$key} or delete $opts{$key}; |
104
|
|
|
|
|
|
|
} |
105
|
|
|
|
|
|
|
|
106
|
0
|
|
|
|
|
0
|
return %opts; |
107
|
|
|
|
|
|
|
} |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
sub new_with_options { |
110
|
0
|
|
|
0
|
1
|
0
|
my $class = shift; |
111
|
0
|
|
|
|
|
0
|
my %opts = $class->parse_opts; |
112
|
0
|
|
|
|
|
0
|
my @configs = ( |
113
|
|
|
|
|
|
|
File::Spec->catfile( File::HomeDir->my_home, '.genpass.yaml' ), |
114
|
|
|
|
|
|
|
'/etc/genpass.yaml', |
115
|
|
|
|
|
|
|
); |
116
|
|
|
|
|
|
|
|
117
|
0
|
0
|
|
|
|
0
|
if ( ! exists $opts{'configfile'} ) { |
118
|
0
|
|
|
|
|
0
|
foreach my $file (@configs) { |
119
|
0
|
0
|
0
|
|
|
0
|
if ( -e $file && -r $file ) { |
120
|
0
|
|
|
|
|
0
|
$opts{'configfile'} = $file; |
121
|
0
|
|
|
|
|
0
|
last; |
122
|
|
|
|
|
|
|
} |
123
|
|
|
|
|
|
|
} |
124
|
|
|
|
|
|
|
} |
125
|
|
|
|
|
|
|
|
126
|
0
|
0
|
|
|
|
0
|
if ( exists $opts{'configfile'} ) { |
127
|
|
|
|
|
|
|
%opts = ( |
128
|
|
|
|
|
|
|
%opts, |
129
|
0
|
|
|
|
|
0
|
%{ $class->get_config_from_file( $opts{'configfile'} ) }, |
|
0
|
|
|
|
|
0
|
|
130
|
|
|
|
|
|
|
); |
131
|
|
|
|
|
|
|
} |
132
|
|
|
|
|
|
|
|
133
|
0
|
|
|
|
|
0
|
my $self = $class->new( %opts, @_ ); |
134
|
|
|
|
|
|
|
|
135
|
0
|
|
|
|
|
0
|
return $self; |
136
|
|
|
|
|
|
|
} |
137
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
sub get_config_from_file { |
139
|
0
|
|
|
0
|
1
|
0
|
my ($class, $file) = @_; |
140
|
|
|
|
|
|
|
|
141
|
0
|
0
|
|
|
|
0
|
$file = $file->() if ref $file eq 'CODE'; |
142
|
0
|
0
|
|
|
|
0
|
my $files_ref = ref $file eq 'ARRAY' ? $file : [$file]; |
143
|
|
|
|
|
|
|
|
144
|
0
|
|
|
|
|
0
|
my $can_config_any_args = $class->can('config_any_args'); |
145
|
0
|
0
|
|
|
|
0
|
my $extra_args = $can_config_any_args ? |
146
|
|
|
|
|
|
|
$can_config_any_args->($class, $file) : {}; |
147
|
|
|
|
|
|
|
; |
148
|
0
|
|
|
|
|
0
|
my $raw_cfany = Config::Any->load_files({ |
149
|
|
|
|
|
|
|
%$extra_args, |
150
|
|
|
|
|
|
|
use_ext => 1, |
151
|
|
|
|
|
|
|
files => $files_ref, |
152
|
|
|
|
|
|
|
flatten_to_hash => 1, |
153
|
|
|
|
|
|
|
} ); |
154
|
|
|
|
|
|
|
|
155
|
0
|
|
|
|
|
0
|
my %raw_config; |
156
|
0
|
|
|
|
|
0
|
foreach my $file_tested ( reverse @{$files_ref} ) { |
|
0
|
|
|
|
|
0
|
|
157
|
0
|
0
|
|
|
|
0
|
if ( ! exists $raw_cfany->{$file_tested} ) { |
158
|
0
|
|
|
|
|
0
|
warn qq{Specified configfile '$file_tested' does not exist, } . |
159
|
|
|
|
|
|
|
qq{is empty, or is not readable\n}; |
160
|
0
|
|
|
|
|
0
|
next; |
161
|
|
|
|
|
|
|
} |
162
|
|
|
|
|
|
|
|
163
|
0
|
|
|
|
|
0
|
my $cfany_hash = $raw_cfany->{$file_tested}; |
164
|
0
|
0
|
0
|
|
|
0
|
die "configfile must represent a hash structure in file: $file_tested" |
|
|
|
0
|
|
|
|
|
165
|
|
|
|
|
|
|
unless $cfany_hash && ref $cfany_hash && ref $cfany_hash eq 'HASH'; |
166
|
|
|
|
|
|
|
|
167
|
0
|
|
|
|
|
0
|
%raw_config = ( %raw_config, %{$cfany_hash} ); |
|
0
|
|
|
|
|
0
|
|
168
|
|
|
|
|
|
|
} |
169
|
|
|
|
|
|
|
|
170
|
0
|
|
|
|
|
0
|
\%raw_config; |
171
|
|
|
|
|
|
|
} |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
sub _get_chars { |
174
|
5010
|
|
|
5010
|
|
11106
|
my $self = shift; |
175
|
5010
|
|
|
|
|
5921
|
my @all_types = qw( lowercase uppercase numerical specials ); |
176
|
5010
|
|
|
|
|
3675
|
my @chars = (); |
177
|
5010
|
|
|
|
|
3888
|
my @types = (); |
178
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
# adding all the combinations |
180
|
5010
|
|
|
|
|
5387
|
foreach my $type (@all_types) { |
181
|
20040
|
50
|
|
|
|
30937
|
if ( my $ref = $self->$type ) { |
182
|
20040
|
|
|
|
|
11752
|
push @chars, @{$ref}; |
|
20040
|
|
|
|
|
50756
|
|
183
|
20040
|
|
|
|
|
20560
|
push @types, $type; |
184
|
|
|
|
|
|
|
} |
185
|
|
|
|
|
|
|
} |
186
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
# removing the unreadable chars |
188
|
5010
|
100
|
|
|
|
7706
|
if ( $self->readable ) { |
189
|
|
|
|
|
|
|
my @remove_chars = ( |
190
|
5007
|
|
|
|
|
5190
|
@{ $self->unreadable }, |
191
|
5007
|
|
|
|
|
3453
|
@{ $self->specials }, |
|
5007
|
|
|
|
|
13439
|
|
192
|
|
|
|
|
|
|
); |
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
@chars = grep { |
195
|
5007
|
|
|
|
|
5322
|
local $a = $_; |
|
360368
|
|
|
|
|
256879
|
|
196
|
360368
|
|
|
5165216
|
|
503090
|
none { $a eq $_ } @remove_chars; |
|
5165216
|
|
|
|
|
3060000
|
|
197
|
|
|
|
|
|
|
} @chars; |
198
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
# removing specials |
200
|
5007
|
|
|
|
|
12399
|
pop @types; |
201
|
|
|
|
|
|
|
} |
202
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
# make both refs |
204
|
5010
|
|
|
|
|
64336
|
return [ \@types, @chars ]; |
205
|
|
|
|
|
|
|
} |
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
sub generate { |
208
|
5008
|
|
|
5008
|
1
|
55323
|
my ( $self, $number ) = @_; |
209
|
|
|
|
|
|
|
|
210
|
5008
|
|
|
|
|
3501
|
my $length; |
211
|
5008
|
|
|
|
|
5945
|
my $verify = $self->verify; |
212
|
5008
|
|
|
|
|
4643
|
my @passwords = (); |
213
|
5008
|
|
|
|
|
3566
|
my @verifications = (); |
214
|
5008
|
|
|
|
|
3648
|
my $EMPTY = q{}; |
215
|
|
|
|
|
|
|
|
216
|
5008
|
|
|
|
|
3649
|
my ( $char_types, @chars ) = @{ $self->_get_chars }; |
|
5008
|
|
|
|
|
6058
|
|
217
|
|
|
|
|
|
|
|
218
|
5008
|
|
|
|
|
16163
|
my @char_types = @{$char_types}; |
|
5008
|
|
|
|
|
6092
|
|
219
|
5008
|
|
|
|
|
4521
|
my $num_of_types = scalar @char_types; |
220
|
|
|
|
|
|
|
|
221
|
5008
|
50
|
66
|
|
|
107343
|
if ( (defined($self->length) && $num_of_types > $self->length) |
|
|
|
33
|
|
|
|
|
222
|
|
|
|
|
|
|
|| ($num_of_types > $self->minlength) ) { |
223
|
0
|
0
|
|
|
|
0
|
$length = defined($self->length) ? $self->length : $self->minlength.' minimum'; |
224
|
0
|
|
|
|
|
0
|
croak <<"_DIE_MSG"; |
225
|
|
|
|
|
|
|
You wanted a shorter password that the variety of characters you've selected. |
226
|
|
|
|
|
|
|
You requested $num_of_types types of characters but only have $length length. |
227
|
|
|
|
|
|
|
_DIE_MSG |
228
|
|
|
|
|
|
|
} |
229
|
|
|
|
|
|
|
|
230
|
5008
|
100
|
|
|
|
87860
|
if ($self->minlength > $self->maxlength) { |
231
|
1
|
|
|
|
|
219
|
carp "minlength > maxlength, so I'm switching them"; |
232
|
1
|
|
|
|
|
113
|
my $min = $self->maxlength; |
233
|
1
|
|
|
|
|
17
|
$self->maxlength($self->minlength); |
234
|
1
|
|
|
|
|
59
|
$self->minlength($min); |
235
|
|
|
|
|
|
|
} |
236
|
|
|
|
|
|
|
|
237
|
5008
|
|
66
|
|
|
135274
|
$length = $self->length |
238
|
|
|
|
|
|
|
|| $self->minlength + int(_rand(abs($self->maxlength - $self->minlength) + 1)); |
239
|
|
|
|
|
|
|
|
240
|
5008
|
|
66
|
|
|
139461
|
$number ||= $self->number; |
241
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
# each password iteration needed |
243
|
5008
|
|
|
|
|
7407
|
foreach my $pass_iter ( 1 .. $number ) { |
244
|
5044
|
|
|
|
|
4368
|
my $password = $EMPTY; |
245
|
5044
|
|
|
|
|
4226
|
my $char_type = shift @char_types; |
246
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
# generating the password |
248
|
5044
|
|
|
|
|
7919
|
while ( $length > length $password ) { |
249
|
42395
|
|
|
|
|
42308
|
my $char = $chars[ int _rand @chars ]; |
250
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
# for verifying, we just check that it has small capital letters |
252
|
|
|
|
|
|
|
# if that doesn't work, we keep asking it to get a new random one |
253
|
|
|
|
|
|
|
# the check if it has large capital letters and so on |
254
|
42395
|
100
|
66
|
|
|
641212
|
if ( $verify && $char_type && @{ $self->$char_type } ) { |
|
15143
|
|
66
|
|
|
39436
|
|
255
|
|
|
|
|
|
|
# verify $char_type |
256
|
15143
|
50
|
|
|
|
8351
|
if ( @{ $self->$char_type } ) { |
|
15143
|
|
|
|
|
21628
|
|
257
|
15143
|
|
|
817101
|
|
27355
|
while ( ! any { $_ eq $char } @{ $self->$char_type } ) { |
|
817101
|
|
|
|
|
494332
|
|
|
58741
|
|
|
|
|
617638
|
|
258
|
43598
|
|
|
|
|
64334
|
$char = $chars[ int _rand @chars ]; |
259
|
|
|
|
|
|
|
} |
260
|
|
|
|
|
|
|
} |
261
|
|
|
|
|
|
|
|
262
|
|
|
|
|
|
|
$char_type = |
263
|
15143
|
100
|
|
|
|
29344
|
scalar @char_types > 0 ? shift @char_types : $EMPTY; |
264
|
|
|
|
|
|
|
} |
265
|
|
|
|
|
|
|
|
266
|
42395
|
|
|
|
|
63705
|
$password .= $char; |
267
|
|
|
|
|
|
|
} |
268
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
# since the verification process creates a situation of ordered types |
270
|
|
|
|
|
|
|
# (lowercase, uppercase, numerical, special) |
271
|
|
|
|
|
|
|
# we need to shuffle the string |
272
|
5044
|
|
|
|
|
18086
|
$password = join $EMPTY, shuffle( split //sm, $password ); |
273
|
|
|
|
|
|
|
|
274
|
5044
|
100
|
|
|
|
29757
|
$number == 1 && return $password; |
275
|
|
|
|
|
|
|
|
276
|
40
|
|
|
|
|
43
|
push @passwords, $password; |
277
|
|
|
|
|
|
|
|
278
|
40
|
|
|
|
|
31
|
@char_types = @{$char_types}; |
|
40
|
|
|
|
|
76
|
|
279
|
|
|
|
|
|
|
} |
280
|
|
|
|
|
|
|
|
281
|
4
|
100
|
|
|
|
59
|
return wantarray ? @passwords : \@passwords; |
282
|
|
|
|
|
|
|
} |
283
|
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
1; |
286
|
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
__END__ |