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/.gitignore +8 -0
- data/CHANGES +148 -0
- data/COPYING +340 -0
- data/Changelog +840 -0
- data/INSTALL +34 -0
- data/README +60 -0
- data/Rakefile +25 -0
- data/VERSION +1 -0
- data/example/example.rb +42 -0
- data/example/pwgen +118 -0
- data/extconf.rb +62 -0
- data/lib/password.rb +432 -0
- data/pwgen.1 +83 -0
- data/rbcrack.c +113 -0
- data/ruby-password.spec +153 -0
- data/test/tc_password.rb +89 -0
- metadata +115 -0
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
|
+
|
data/Rakefile
ADDED
@@ -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
|
data/example/example.rb
ADDED
@@ -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
|
data/example/pwgen
ADDED
@@ -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
|
data/extconf.rb
ADDED
@@ -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
|
data/lib/password.rb
ADDED
@@ -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
|