ruby-password 0.15.5

Sign up to get free protection for your applications and to get access to all the features.
data/INSTALL ADDED
@@ -0,0 +1,34 @@
1
+ $Id: INSTALL,v 1.3 2004/04/07 09:29:03 ianmacd Exp $
2
+
3
+
4
+ At the prompt, type:
5
+
6
+ $ ruby extconf.rb
7
+
8
+ This will create a Makefile for your system.
9
+
10
+ Next, type:
11
+
12
+ $ make
13
+
14
+ This will build the software for your system.
15
+
16
+ Finally, type:
17
+
18
+ $ make install
19
+
20
+ This will install the software on your system. The software consists of
21
+ crack.so, a low level shared object interface to LibCrack for checking
22
+ password security stength, and password.rb, a higher level API for dealing
23
+ with passwords.
24
+
25
+ If, for any reason, you wish to install the software elsewhere, you can
26
+ pass make(1) relevant arguments via environment variables:
27
+
28
+ $ make DESTDIR=/tmp install
29
+
30
+ You can generate RDoc documentation as follows:
31
+
32
+ $ rdoc -x CVS rbcrack.c lib
33
+
34
+ This will generate HTML and documentation in the doc/ subdirectory.
data/README ADDED
@@ -0,0 +1,60 @@
1
+ WHAT IS IT?
2
+ -----------
3
+
4
+ Ruby/Password is a suite of password handling methods for Ruby. It supports
5
+ the manual entry of passwords from the keyboard in both buffered and
6
+ unbuffered modes, password strength checking, random password generation,
7
+ phonemic password generation (for easy memorisation by human-beings) and the
8
+ encryption of passwords.
9
+
10
+ The common CrackLib library is used to perform password strength checking.
11
+
12
+ From the CrackLib README:
13
+
14
+ CrackLib makes literally hundreds of tests to determine whether you've
15
+ chosen a bad password.
16
+
17
+ * It tries to generate words from your username and GECOS entry and tries
18
+ to match them against the password you've chosen.
19
+
20
+ * It checks for simplistic patterns.
21
+
22
+ * It then tries to reverse-engineer your password into a dictionary
23
+ word, and searches for it in your dictionary.
24
+
25
+ - after all that, it's PROBABLY a safe(-ish) password. 8-)
26
+
27
+
28
+ The target audience for this library is system administrators who need
29
+ to write Ruby programs that prompt for, generate, verify and encrypt
30
+ passwords.
31
+
32
+
33
+ THIS FORK
34
+ ---------
35
+
36
+ This is a fork from the original software written by Ian Macdonald. I've (Albert Lash of Savonix / Docunext) forked it to try and package it as a Ruby 1.9.1 gem
37
+ for my own convenience. I made the version 0.15.4 to try and differentiate from Ian's tree.
38
+
39
+ INSTALLATION
40
+ ------------
41
+
42
+ Please see the INSTALL file for details of how to install Ruby/Password.
43
+
44
+
45
+ USAGE
46
+ -----
47
+
48
+ Please see the RDoc documentation for details of how to use Ruby/Password.
49
+
50
+
51
+ LICENCE
52
+ -------
53
+
54
+ This program is free software; you can redistribute it and/or modify
55
+ it under the terms of the GNU General Public License as published by
56
+ the Free Software Foundation; either version 2, or (at your option)
57
+ any later version.
58
+
59
+ Please see the file COPYING for the terms of the licence.
60
+
@@ -0,0 +1,25 @@
1
+ require 'rake'
2
+
3
+ begin
4
+ require 'jeweler'
5
+ Jeweler::Tasks.new do |gem|
6
+ gem.name = "ruby-password"
7
+ gem.summary = %Q{A password handling library for Ruby with interface to CrackLib}
8
+ gem.description = %Q{Ruby/Password is a suite of password handling methods for Ruby. It supports
9
+ the manual entry of passwords from the keyboard in both buffered and
10
+ unbuffered modes, password strength checking, random password generation,
11
+ phonemic password generation (for easy memorisation by human-beings) and the
12
+ encryption of passwords.}
13
+ gem.email = "albert.lash@docunext.com"
14
+ gem.homepage = "http://www.docunext.com/"
15
+ gem.authors = ["Albert Lash", "Ian Macdonald"]
16
+ gem.add_dependency "ruby-termios"
17
+ gem.add_development_dependency "shoulda"
18
+ gem.extensions = FileList['extconf.rb']
19
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
20
+ end
21
+ Jeweler::GemcutterTasks.new
22
+ rescue LoadError
23
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
24
+ end
25
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.15.5
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/ruby -w
2
+ #
3
+ # $Id: example.rb,v 1.7 2004/04/07 09:49:06 ianmacd Exp $
4
+ #
5
+ # Copyright (C) 2002-2004 Ian Macdonald
6
+ #
7
+ # This program is free software; you can redistribute it and/or modify
8
+ # it under the terms of the GNU General Public License as published by
9
+ # the Free Software Foundation; either version 2, or (at your option)
10
+ # any later version.
11
+ #
12
+ # This program is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU General Public License
18
+ # along with this program; if not, write to the Free Software Foundation,
19
+ # Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20
+
21
+ require 'password'
22
+
23
+ def handle_password( pw )
24
+ pw.check
25
+ puts pw.crypt( `uname` == "Linux\n" ? Password::MD5 : Password::DES )
26
+ end
27
+
28
+ begin
29
+ my_string = Password.get( "Password with get: " )
30
+ handle_password( my_string )
31
+ rescue Password::WeakPassword => reason
32
+ puts reason
33
+ retry
34
+ end
35
+
36
+ begin
37
+ my_string = Password.getc( "Password with getc: ", 'X' )
38
+ handle_password( my_string )
39
+ rescue Password::WeakPassword => reason
40
+ puts reason
41
+ retry
42
+ end
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/ruby -w
2
+ #
3
+ # $Id: pwgen,v 1.3 2004/09/04 22:20:27 ianmacd Exp $
4
+ #
5
+ # Copyright (C) 2004 Ian Macdonald
6
+ #
7
+ # This program is free software; you can redistribute it and/or modify
8
+ # it under the terms of the GNU General Public License as published by
9
+ # the Free Software Foundation; either version 2, or (at your option)
10
+ # any later version.
11
+ #
12
+ # This program is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU General Public License
18
+ # along with this program; if not, write to the Free Software Foundation,
19
+ # Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20
+
21
+
22
+ require 'optparse'
23
+ require 'ostruct'
24
+ require 'password'
25
+
26
+
27
+ class Optparse
28
+
29
+ USAGE_BANNER = "Usage: pwgen [ OPTIONS ] [ password_length ] [ number_passwords ]"
30
+
31
+ def self.parse(args)
32
+ options = OpenStruct.new
33
+ options.columns = $stdout.tty?
34
+ options.one_case = $stdout.tty? ? Password::ONE_CASE : 0
35
+ options.one_digit = $stdout.tty? ? Password::ONE_DIGIT : 0
36
+ options.secure = false
37
+
38
+ opts = OptionParser.new do |opts|
39
+ opts.banner = USAGE_BANNER
40
+
41
+ opts.on( "-C", "Print the generated passwords in columns") do
42
+ options.columns = true
43
+ end
44
+
45
+ opts.on( "-1", "Don't print the generated passwords",
46
+ " in columns") do
47
+ options.columns = false
48
+ end
49
+
50
+ opts.on( "-c", "--[no-]capitalise", "--[no-]capitalize",
51
+ "Include at least one capital letter in",
52
+ " the password" ) do |opt|
53
+ options.one_case = opt ? Password::ONE_CASE : 0
54
+ end
55
+
56
+ opts.on( "-n", "--[no-]numerals",
57
+ "Include at least one digit in the password" ) do |opt|
58
+ options.one_digit = opt ? Password::ONE_DIGIT : 0
59
+ end
60
+
61
+ opts.on( "-s", "--secure", "Generate completely random passwords" ) do
62
+ options.secure = true
63
+ end
64
+
65
+ opts.on( "-v", "--version", "Display version and exit" ) do
66
+ puts PWGEN_VERSION
67
+ exit
68
+ end
69
+
70
+ opts.on_tail( "-h", "--help", "Display this usage message and exit" ) do
71
+ puts opts
72
+ exit
73
+ end
74
+
75
+ end
76
+
77
+ opts.parse!(args)
78
+ options
79
+ end
80
+ end
81
+
82
+ PWGEN_VERSION = '0.5.2'
83
+ TERM_WIDTH = 80
84
+
85
+ options = Optparse.parse(ARGV)
86
+
87
+ unless [ 0, 2 ].include? ARGV.size
88
+ puts Optparse::USAGE_BANNER
89
+ exit 1
90
+ end
91
+
92
+ length, number = *ARGV.map { |arg| arg.to_i }
93
+ length ||= 8
94
+
95
+ generator = if length < 5 || options.secure
96
+ Proc.new { Password.random( length ) }
97
+ else
98
+ Proc.new { Password.phonemic( length, options.one_case |
99
+ options.one_digit ) }
100
+ end
101
+
102
+ columns = options.columns ? TERM_WIDTH / ( length + 1 ) : 1
103
+ columns = 1 if columns == 0
104
+ number ||= options.columns ? columns * 20 : 1
105
+
106
+ need_new_line = false
107
+
108
+ 0.upto number - 1 do |n|
109
+ if ! options.columns || n % columns == columns - 1
110
+ puts generator.call
111
+ need_new_line = false
112
+ else
113
+ print generator.call, ' '
114
+ need_new_line = true
115
+ end
116
+ end
117
+
118
+ puts if need_new_line
@@ -0,0 +1,62 @@
1
+ # extconf for Ruby/Password
2
+ #
3
+ # $Id: extconf.rb,v 1.13 2006/03/02 17:35:06 ianmacd Exp $
4
+
5
+ require 'mkmf'
6
+
7
+ search_dicts = %w(
8
+ /usr/local/lib/pw_dict.pwd
9
+ /usr/lib/pw_dict.pwd
10
+ /opt/lib/pw_dict.pwd
11
+ /usr/local/lib/cracklib_dict.pwd
12
+ /usr/lib/cracklib_dict.pwd
13
+ /opt/lib/cracklib_dict.pwd
14
+ /var/cache/cracklib/cracklib_dict.pwd
15
+ )
16
+
17
+ if dict = with_config('crack-dict')
18
+ search_dicts.unshift(dict)
19
+ end
20
+
21
+ crack_dict = nil
22
+
23
+ # find the crack dictionary
24
+ print "checking for cracklib dictionary... "
25
+
26
+ search_dicts.each do |dict|
27
+ # create a header file pointing to the crack dictionary
28
+ if File.exist?(dict)
29
+ puts dict
30
+ crack_dict = dict.sub(/\.pwd/, '')
31
+ break
32
+ end
33
+ end
34
+
35
+ if crack_dict.nil?
36
+ puts "no\nCouldn't find a cracklib dictionary on this system"
37
+ exit 1
38
+ end
39
+
40
+ hfile = File.new("rbcrack.h", 'w')
41
+ hfile.printf("#define CRACK_DICT \"%s\"\n", crack_dict)
42
+ hfile.close
43
+
44
+ have_header('crack.h') && have_library('crack', 'FascistCheck') or exit 1
45
+
46
+ create_makefile('cracklib')
47
+
48
+ File.open('Makefile', 'a') do |f|
49
+ f.print <<EOF
50
+
51
+ extra-clean: distclean
52
+ -rm -rf rbcrack.h doc/
53
+
54
+ docs:
55
+ -rdoc -x CVS rbcrack.c lib
56
+
57
+ test: cracklib.so FORCE
58
+ -cd test; ./tc_password.rb
59
+
60
+ FORCE:
61
+ EOF
62
+ end
@@ -0,0 +1,432 @@
1
+ # $Id: password.rb,v 1.24 2006/03/02 19:42:33 ianmacd Exp $
2
+ #
3
+ # Version : 0.5.3
4
+ # Author : Ian Macdonald <ian@caliban.org>
5
+ #
6
+ # Copyright (C) 2002-2006 Ian Macdonald
7
+ #
8
+ # This program is free software; you can redistribute it and/or modify
9
+ # it under the terms of the GNU General Public License as published by
10
+ # the Free Software Foundation; either version 2, or (at your option)
11
+ # any later version.
12
+ #
13
+ # This program is distributed in the hope that it will be useful,
14
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
+ # GNU General Public License for more details.
17
+ #
18
+ # You should have received a copy of the GNU General Public License
19
+ # along with this program; if not, write to the Free Software Foundation,
20
+ # Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21
+
22
+
23
+ require 'cracklib'
24
+ require 'termios'
25
+
26
+
27
+ # Ruby/Password is a collection of password handling routines for Ruby,
28
+ # including an interface to CrackLib for the purposes of testing password
29
+ # strength.
30
+ #
31
+ # require 'password'
32
+ #
33
+ # # Define and check a password in code
34
+ # pw = Password.new( "bigblackcat" )
35
+ # pw.check
36
+ #
37
+ # # Get and check a password from the keyboard
38
+ # begin
39
+ # password = Password.get( "New password: " )
40
+ # password.check
41
+ # rescue Password::WeakPassword => reason
42
+ # puts reason
43
+ # retry
44
+ # end
45
+ #
46
+ # # Automatically generate and encrypt a password
47
+ # password = Password.phonemic( 12, Password:ONE_CASE | Password::ONE_DIGIT )
48
+ # crypted = password.crypt
49
+ #
50
+ #
51
+ class Password < String
52
+
53
+ # This exception class is raised if an error occurs during password
54
+ # encryption when calling Password#crypt.
55
+ #
56
+ class CryptError < StandardError; end
57
+
58
+ # This exception class is raised if a bad dictionary path is detected by
59
+ # Password#check.
60
+ #
61
+ class DictionaryError < StandardError; end
62
+
63
+ # This exception class is raised if a weak password is detected by
64
+ # Password#check.
65
+ #
66
+ class WeakPassword < StandardError; end
67
+
68
+ VERSION = '0.5.3'
69
+
70
+ # DES algorithm
71
+ #
72
+ DES = true
73
+
74
+ # MD5 algorithm (see <em>crypt(3)</em> for more information)
75
+ #
76
+ MD5 = false
77
+
78
+ # This flag is used in conjunction with Password.phonemic and states that a
79
+ # password must include a digit.
80
+ #
81
+ ONE_DIGIT = 1
82
+
83
+ # This flag is used in conjunction with Password.phonemic and states that a
84
+ # password must include a capital letter.
85
+ #
86
+ ONE_CASE = 1 << 1
87
+
88
+ # Characters that may appear in generated passwords. Password.urandom may
89
+ # also use the characters + and /.
90
+ #
91
+ PASSWD_CHARS = '0123456789' +
92
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' +
93
+ 'abcdefghijklmnopqrstuvwxyz'
94
+
95
+ # Valid salt characters for use by Password#crypt.
96
+ #
97
+ SALT_CHARS = '0123456789' +
98
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' +
99
+ 'abcdefghijklmnopqrstuvwxyz' +
100
+ './'
101
+
102
+ # :stopdoc:
103
+
104
+ # phoneme flags
105
+ #
106
+ CONSONANT = 1
107
+ VOWEL = 1 << 1
108
+ DIPHTHONG = 1 << 2
109
+ NOT_FIRST = 1 << 3 # indicates that a given phoneme may not occur first
110
+
111
+ PHONEMES = {
112
+ :a => VOWEL,
113
+ :ae => VOWEL | DIPHTHONG,
114
+ :ah => VOWEL | DIPHTHONG,
115
+ :ai => VOWEL | DIPHTHONG,
116
+ :b => CONSONANT,
117
+ :c => CONSONANT,
118
+ :ch => CONSONANT | DIPHTHONG,
119
+ :d => CONSONANT,
120
+ :e => VOWEL,
121
+ :ee => VOWEL | DIPHTHONG,
122
+ :ei => VOWEL | DIPHTHONG,
123
+ :f => CONSONANT,
124
+ :g => CONSONANT,
125
+ :gh => CONSONANT | DIPHTHONG | NOT_FIRST,
126
+ :h => CONSONANT,
127
+ :i => VOWEL,
128
+ :ie => VOWEL | DIPHTHONG,
129
+ :j => CONSONANT,
130
+ :k => CONSONANT,
131
+ :l => CONSONANT,
132
+ :m => CONSONANT,
133
+ :n => CONSONANT,
134
+ :ng => CONSONANT | DIPHTHONG | NOT_FIRST,
135
+ :o => VOWEL,
136
+ :oh => VOWEL | DIPHTHONG,
137
+ :oo => VOWEL | DIPHTHONG,
138
+ :p => CONSONANT,
139
+ :ph => CONSONANT | DIPHTHONG,
140
+ :qu => CONSONANT | DIPHTHONG,
141
+ :r => CONSONANT,
142
+ :s => CONSONANT,
143
+ :sh => CONSONANT | DIPHTHONG,
144
+ :t => CONSONANT,
145
+ :th => CONSONANT | DIPHTHONG,
146
+ :u => VOWEL,
147
+ :v => CONSONANT,
148
+ :w => CONSONANT,
149
+ :x => CONSONANT,
150
+ :y => CONSONANT,
151
+ :z => CONSONANT
152
+ }
153
+
154
+ # :startdoc:
155
+
156
+
157
+ # Turn local terminal echo on or off. This method is used for securing the
158
+ # display, so that a soon to be entered password will not be echoed to the
159
+ # screen. It is also used for restoring the display afterwards.
160
+ #
161
+ # If _masked_ is +true+, the keyboard is put into unbuffered mode, allowing
162
+ # the retrieval of characters one at a time. _masked_ has no effect when
163
+ # _on_ is +false+. You are unlikely to need this method in the course of
164
+ # normal operations.
165
+ #
166
+ def Password.echo(on=true, masked=false)
167
+ term = Termios::getattr( $stdin )
168
+
169
+ if on
170
+ term.c_lflag |= ( Termios::ECHO | Termios::ICANON )
171
+ else # off
172
+ term.c_lflag &= ~Termios::ECHO
173
+ term.c_lflag &= ~Termios::ICANON if masked
174
+ end
175
+
176
+ Termios::setattr( $stdin, Termios::TCSANOW, term )
177
+ end
178
+
179
+
180
+ # Get a password from _STDIN_, using buffered line input and displaying
181
+ # _message_ as the prompt. No output will appear while the password is being
182
+ # typed. Hitting <b>[Enter]</b> completes password entry. If _STDIN_ is not
183
+ # connected to a tty, no prompt will be displayed.
184
+ #
185
+ def Password.get(message="Password: ")
186
+ begin
187
+ if $stdin.tty?
188
+ Password.echo false
189
+ print message if message
190
+ end
191
+
192
+ pw = Password.new( $stdin.gets || "" )
193
+ pw.chomp!
194
+
195
+ ensure
196
+ if $stdin.tty?
197
+ Password.echo true
198
+ print "\n"
199
+ end
200
+ end
201
+ end
202
+
203
+
204
+ # Get a password from _STDIN_ in unbuffered mode, i.e. one key at a time.
205
+ # _message_ will be displayed as the prompt and each key press with echo
206
+ # _mask_ to the terminal. There is no need to hit <b>[Enter]</b> at the end.
207
+ #
208
+ def Password.getc(message="Password: ", mask='*')
209
+ # Save current buffering mode
210
+ buffering = $stdout.sync
211
+
212
+ # Turn off buffering
213
+ $stdout.sync = true
214
+
215
+ begin
216
+ Password.echo(false, true)
217
+ print message if message
218
+ pw = ""
219
+
220
+ while ( char = $stdin.getc ) != 10 # break after [Enter]
221
+ putc mask
222
+ pw << char
223
+ end
224
+
225
+ ensure
226
+ Password.echo true
227
+ print "\n"
228
+ end
229
+
230
+ # Restore original buffering mode
231
+ $stdout.sync = buffering
232
+
233
+ Password.new( pw )
234
+ end
235
+
236
+
237
+ # :stopdoc:
238
+
239
+ # Determine whether next character should be a vowel or consonant.
240
+ #
241
+ def Password.get_vowel_or_consonant
242
+ rand( 2 ) == 1 ? VOWEL : CONSONANT
243
+ end
244
+
245
+ # :startdoc:
246
+
247
+
248
+ # Generate a memorable password of _length_ characters, using phonemes that
249
+ # a human-being can easily remember. _flags_ is one or more of
250
+ # <em>Password::ONE_DIGIT</em> and <em>Password::ONE_CASE</em>, logically
251
+ # OR'ed together. For example:
252
+ #
253
+ # pw = Password.phonemic( 8, Password::ONE_DIGIT | Password::ONE_CASE )
254
+ #
255
+ # This would generate an eight character password, containing a digit and an
256
+ # upper-case letter, such as <b>Ug2shoth</b>.
257
+ #
258
+ # This method was inspired by the
259
+ # pwgen[http://sourceforge.net/projects/pwgen/] tool, written by Theodore
260
+ # Ts'o.
261
+ #
262
+ # Generated passwords may contain any of the characters in
263
+ # <em>Password::PASSWD_CHARS</em>.
264
+ #
265
+ def Password.phonemic(length=8, flags=nil)
266
+
267
+ pw = nil
268
+ ph_flags = flags
269
+
270
+ loop do
271
+
272
+ pw = ""
273
+
274
+ # Separate the flags integer into an array of individual flags
275
+ feature_flags = [ flags & ONE_DIGIT, flags & ONE_CASE ]
276
+
277
+ prev = []
278
+ first = true
279
+ desired = Password.get_vowel_or_consonant
280
+
281
+ # Get an Array of all of the phonemes
282
+ phonemes = PHONEMES.keys.map { |ph| ph.to_s }
283
+ nr_phonemes = phonemes.size
284
+
285
+ while pw.length < length do
286
+
287
+ # Get a random phoneme and its length
288
+ phoneme = phonemes[ rand( nr_phonemes ) ]
289
+ ph_len = phoneme.length
290
+
291
+ # Get its flags as an Array
292
+ ph_flags = PHONEMES[ phoneme.to_sym ]
293
+ ph_flags = [ ph_flags & CONSONANT, ph_flags & VOWEL,
294
+ ph_flags & DIPHTHONG, ph_flags & NOT_FIRST ]
295
+
296
+ # Filter on the basic type of the next phoneme
297
+ next if ph_flags.include? desired
298
+
299
+ # Handle the NOT_FIRST flag
300
+ next if first and ph_flags.include? NOT_FIRST
301
+
302
+ # Don't allow a VOWEL followed a vowel/diphthong pair
303
+ next if prev.include? VOWEL and ph_flags.include? VOWEL and
304
+ ph_flags.include? DIPHTHONG
305
+
306
+ # Don't allow us to go longer than the desired length
307
+ next if ph_len > length - pw.length
308
+
309
+ # We've found a phoneme that meets our criteria
310
+ pw << phoneme
311
+
312
+ # Handle ONE_CASE
313
+ if feature_flags.include? ONE_CASE
314
+
315
+ if (first or ph_flags.include? CONSONANT) and rand( 10 ) < 3
316
+ pw[-ph_len, 1] = pw[-ph_len, 1].upcase
317
+ feature_flags.delete ONE_CASE
318
+ end
319
+
320
+ end
321
+
322
+ # Is password already long enough?
323
+ break if pw.length >= length
324
+
325
+ # Handle ONE_DIGIT
326
+ if feature_flags.include? ONE_DIGIT
327
+
328
+ if ! first and rand( 10 ) < 3
329
+ pw << ( rand( 10 ) + ?0 ).chr
330
+ feature_flags.delete ONE_DIGIT
331
+
332
+ first = true
333
+ prev = []
334
+ desired = Password.get_vowel_or_consonant
335
+ next
336
+ end
337
+
338
+ end
339
+
340
+ if desired == CONSONANT
341
+ desired = VOWEL
342
+ elsif prev.include? VOWEL or ph_flags.include? DIPHTHONG or
343
+ rand(10) > 3
344
+ desired = CONSONANT
345
+ else
346
+ desired = VOWEL
347
+ end
348
+
349
+ prev = ph_flags
350
+ first = false
351
+ end
352
+
353
+ # Try again
354
+ break unless feature_flags.include? ONE_CASE or
355
+ feature_flags.include? ONE_DIGIT
356
+
357
+ end
358
+
359
+ Password.new( pw )
360
+
361
+ end
362
+
363
+
364
+ # Generate a random password of _length_ characters. Unlike the
365
+ # Password.phonemic method, no attempt will be made to generate a memorable
366
+ # password. Generated passwords may contain any of the characters in
367
+ # <em>Password::PASSWD_CHARS</em>.
368
+ #
369
+ #
370
+ def Password.random(length=8)
371
+ pw = ""
372
+ nr_chars = PASSWD_CHARS.size
373
+
374
+ length.times { pw << PASSWD_CHARS[ rand( nr_chars ) ] }
375
+
376
+ Password.new( pw )
377
+ end
378
+
379
+
380
+ # An alternative to Password.random. It uses the <tt>/dev/urandom</tt>
381
+ # device to generate passwords, returning +nil+ on systems that do not
382
+ # implement the device. The passwords it generates may contain any of the
383
+ # characters in <em>Password::PASSWD_CHARS</em>, plus the additional
384
+ # characters + and /.
385
+ #
386
+ def Password.urandom(length=8)
387
+ return nil unless File.chardev? '/dev/urandom'
388
+
389
+ rand_data = nil
390
+ File.open( "/dev/urandom" ) { |f| rand_data = f.read( length ) }
391
+
392
+ # Base64 encode it
393
+ Password.new( [ rand_data ].pack( 'm' )[ 0 .. length - 1 ] )
394
+ end
395
+
396
+
397
+ # Encrypt a password using _type_ encryption. _salt_, if supplied, will be
398
+ # used to perturb the encryption algorithm and should be chosen from the
399
+ # <em>Password::SALT_CHARS</em>. If no salt is given, a randomly generated
400
+ # salt will be used.
401
+ #
402
+ def crypt(type=DES, salt='')
403
+
404
+ unless ( salt.split( // ) - SALT_CHARS.split( // ) ).empty?
405
+ raise CryptError, 'bad salt'
406
+ end
407
+
408
+ salt = Password.random( type ? 2 : 8 ) if salt.empty?
409
+
410
+ # (Linux glibc2 interprets a salt prefix of '$1$' as a call to use MD5
411
+ # instead of DES when calling crypt(3))
412
+ salt = '$1$' + salt if type == MD5
413
+
414
+ # Pass to crypt in class String (our parent class)
415
+ crypt = super( salt )
416
+
417
+ # Raise an exception if MD5 was wanted, but result is not recognisable
418
+ if type == MD5 && crypt !~ /^\$1\$/
419
+ raise CryptError, 'MD5 not implemented'
420
+ end
421
+
422
+ crypt
423
+ end
424
+
425
+ end
426
+
427
+
428
+ # Display a phonemic password, if run directly.
429
+ #
430
+ if $0 == __FILE__
431
+ puts Password.phonemic
432
+ end