ruby-password 0.15.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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