htauth 1.0.1 → 1.0.2
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/{CHANGES → HISTORY} +4 -0
- data/README +5 -4
- data/bin/htdigest-ruby +13 -6
- data/bin/htpasswd-ruby +8 -1
- data/gemspec.rb +43 -0
- data/lib/htauth.rb +29 -7
- data/lib/htauth/algorithm.rb +51 -50
- data/lib/htauth/crypt.rb +11 -11
- data/lib/htauth/digest.rb +107 -108
- data/lib/htauth/digest_entry.rb +53 -53
- data/lib/htauth/digest_file.rb +61 -66
- data/lib/htauth/entry.rb +5 -5
- data/lib/htauth/file.rb +84 -84
- data/lib/htauth/md5.rb +72 -72
- data/lib/htauth/passwd.rb +149 -150
- data/lib/htauth/passwd_entry.rb +77 -77
- data/lib/htauth/passwd_file.rb +0 -4
- data/lib/htauth/version.rb +16 -13
- data/tasks/announce.rake +38 -0
- data/tasks/config.rb +98 -0
- data/tasks/distribution.rake +46 -0
- data/tasks/documentation.rake +31 -0
- data/tasks/rspec.rake +29 -0
- data/tasks/rubyforge.rake +59 -0
- data/tasks/utils.rb +80 -0
- metadata +79 -89
- data/lib/htauth/gemspec.rb +0 -53
- data/lib/htauth/specification.rb +0 -129
- data/spec/test.add.digest +0 -3
- data/spec/test.add.passwd +0 -3
- data/spec/test.delete.digest +0 -1
- data/spec/test.delete.passwd +0 -1
- data/spec/test.original.digest +0 -2
- data/spec/test.original.passwd +0 -2
- data/spec/test.update.digest +0 -2
- data/spec/test.update.passwd +0 -2
data/lib/htauth/md5.rb
CHANGED
@@ -3,80 +3,80 @@ require 'digest/md5'
|
|
3
3
|
|
4
4
|
module HTAuth
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
DIGEST_LENGTH = 16
|
11
|
-
|
12
|
-
def initialize(params = {})
|
13
|
-
@salt = params['salt'] || params[:salt] || gen_salt
|
14
|
-
end
|
6
|
+
# an implementation of the MD5 based encoding algorithm
|
7
|
+
# as used in the apache htpasswd -m option
|
8
|
+
class Md5 < Algorithm
|
15
9
|
|
16
|
-
|
17
|
-
|
18
|
-
|
10
|
+
DIGEST_LENGTH = 16
|
11
|
+
|
12
|
+
def initialize(params = {})
|
13
|
+
@salt = params['salt'] || params[:salt] || gen_salt
|
14
|
+
end
|
19
15
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
pd = primary.digest
|
49
|
-
|
50
|
-
encoded_password = "#{prefix}#{@salt}$"
|
51
|
-
|
52
|
-
# apr_md5_encode has this comment about a 60Mhz Pentium above this loop.
|
53
|
-
1000.times do |x|
|
54
|
-
ctx = ::Digest::MD5.new
|
55
|
-
ctx << (( ( x & 1 ) == 1 ) ? password : pd[0,DIGEST_LENGTH])
|
56
|
-
(ctx << @salt) unless ( x % 3 ) == 0
|
57
|
-
(ctx << password) unless ( x % 7 ) == 0
|
58
|
-
ctx << (( ( x & 1 ) == 0 ) ? password : pd[0,DIGEST_LENGTH])
|
59
|
-
pd = ctx.digest
|
60
|
-
end
|
61
|
-
|
62
|
-
|
63
|
-
l = (pd[ 0]<<16) | (pd[ 6]<<8) | pd[12]
|
64
|
-
encoded_password << to_64(l, 4)
|
65
|
-
|
66
|
-
l = (pd[ 1]<<16) | (pd[ 7]<<8) | pd[13]
|
67
|
-
encoded_password << to_64(l, 4)
|
68
|
-
|
69
|
-
l = (pd[ 2]<<16) | (pd[ 8]<<8) | pd[14]
|
70
|
-
encoded_password << to_64(l, 4)
|
71
|
-
|
72
|
-
l = (pd[ 3]<<16) | (pd[ 9]<<8) | pd[15]
|
73
|
-
encoded_password << to_64(l, 4)
|
74
|
-
|
75
|
-
l = (pd[ 4]<<16) | (pd[10]<<8) | pd[ 5]
|
76
|
-
encoded_password << to_64(l, 4)
|
77
|
-
encoded_password << to_64(pd[11],2)
|
78
|
-
|
79
|
-
return encoded_password
|
16
|
+
def prefix
|
17
|
+
"$apr1$"
|
18
|
+
end
|
19
|
+
|
20
|
+
# this algorigthm pulled straight from apr_md5_encode() and converted to ruby syntax
|
21
|
+
def encode(password)
|
22
|
+
primary = ::Digest::MD5.new
|
23
|
+
primary << password
|
24
|
+
primary << prefix
|
25
|
+
primary << @salt
|
26
|
+
|
27
|
+
md5_t = ::Digest::MD5.digest("#{password}#{@salt}#{password}")
|
28
|
+
|
29
|
+
l = password.length
|
30
|
+
while l > 0 do
|
31
|
+
slice_size = ( l > DIGEST_LENGTH ) ? DIGEST_LENGTH : l
|
32
|
+
primary << md5_t[0, slice_size]
|
33
|
+
l -= DIGEST_LENGTH
|
34
|
+
end
|
35
|
+
|
36
|
+
# weirdness
|
37
|
+
l = password.length
|
38
|
+
while l != 0
|
39
|
+
case (l & 1)
|
40
|
+
when 1
|
41
|
+
primary << 0.chr
|
42
|
+
when 0
|
43
|
+
primary << password[0,1]
|
80
44
|
end
|
45
|
+
l >>= 1
|
46
|
+
end
|
47
|
+
|
48
|
+
pd = primary.digest
|
49
|
+
|
50
|
+
encoded_password = "#{prefix}#{@salt}$"
|
51
|
+
|
52
|
+
# apr_md5_encode has this comment about a 60Mhz Pentium above this loop.
|
53
|
+
1000.times do |x|
|
54
|
+
ctx = ::Digest::MD5.new
|
55
|
+
ctx << (( ( x & 1 ) == 1 ) ? password : pd[0,DIGEST_LENGTH])
|
56
|
+
(ctx << @salt) unless ( x % 3 ) == 0
|
57
|
+
(ctx << password) unless ( x % 7 ) == 0
|
58
|
+
ctx << (( ( x & 1 ) == 0 ) ? password : pd[0,DIGEST_LENGTH])
|
59
|
+
pd = ctx.digest
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
l = (pd[ 0]<<16) | (pd[ 6]<<8) | pd[12]
|
64
|
+
encoded_password << to_64(l, 4)
|
65
|
+
|
66
|
+
l = (pd[ 1]<<16) | (pd[ 7]<<8) | pd[13]
|
67
|
+
encoded_password << to_64(l, 4)
|
68
|
+
|
69
|
+
l = (pd[ 2]<<16) | (pd[ 8]<<8) | pd[14]
|
70
|
+
encoded_password << to_64(l, 4)
|
71
|
+
|
72
|
+
l = (pd[ 3]<<16) | (pd[ 9]<<8) | pd[15]
|
73
|
+
encoded_password << to_64(l, 4)
|
74
|
+
|
75
|
+
l = (pd[ 4]<<16) | (pd[10]<<8) | pd[ 5]
|
76
|
+
encoded_password << to_64(l, 4)
|
77
|
+
encoded_password << to_64(pd[11],2)
|
78
|
+
|
79
|
+
return encoded_password
|
81
80
|
end
|
81
|
+
end
|
82
82
|
end
|
data/lib/htauth/passwd.rb
CHANGED
@@ -4,173 +4,172 @@ require 'htauth/passwd_file'
|
|
4
4
|
require 'ostruct'
|
5
5
|
require 'optparse'
|
6
6
|
|
7
|
-
require 'rubygems'
|
8
7
|
require 'highline'
|
9
8
|
|
10
9
|
module HTAuth
|
11
|
-
|
10
|
+
class Passwd
|
12
11
|
|
13
|
-
|
12
|
+
MAX_PASSWD_LENGTH = 255
|
14
13
|
|
15
|
-
|
14
|
+
attr_accessor :passwd_file
|
16
15
|
|
17
|
-
|
18
|
-
|
19
|
-
|
16
|
+
def initialize
|
17
|
+
@passwd_file = nil
|
18
|
+
end
|
20
19
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
20
|
+
def options
|
21
|
+
if @options.nil? then
|
22
|
+
@options = ::OpenStruct.new
|
23
|
+
@options.batch_mode = false
|
24
|
+
@options.file_mode = File::ALTER
|
25
|
+
@options.passwdfile = nil
|
26
|
+
@options.algorithm = Algorithm::EXISTING
|
27
|
+
@options.send_to_stdout = false
|
28
|
+
@options.show_version = false
|
29
|
+
@options.show_help = false
|
30
|
+
@options.username = nil
|
31
|
+
@options.delete_entry = false
|
32
|
+
@options.password = ""
|
33
|
+
end
|
34
|
+
@options
|
35
|
+
end
|
37
36
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
37
|
+
def option_parser
|
38
|
+
if not @option_parser then
|
39
|
+
@option_parser = OptionParser.new do |op|
|
40
|
+
op.banner = <<EOB
|
42
41
|
Usage:
|
43
|
-
|
44
|
-
|
42
|
+
#{op.program_name} [-cmdpsD] passwordfile username
|
43
|
+
#{op.program_name} -b[cmdpsD] passwordfile username password
|
45
44
|
|
46
|
-
|
47
|
-
|
45
|
+
#{op.program_name} -n[mdps] username
|
46
|
+
#{op.program_name} -nb[mdps] username password
|
48
47
|
EOB
|
49
48
|
|
50
|
-
|
51
|
-
|
52
|
-
op.on("-b", "--batch", "Batch mode, get the password from the command line, rather than prompt") do |b|
|
53
|
-
options.batch_mode = b
|
54
|
-
end
|
55
|
-
|
56
|
-
op.on("-c", "--create", "Create a new file; this overwrites an existing file.") do |c|
|
57
|
-
options.file_mode = HTAuth::File::CREATE
|
58
|
-
end
|
59
|
-
|
60
|
-
op.on("-d", "--crypt", "Force CRYPT encryption of the password (default).") do |c|
|
61
|
-
options.algorithm = "crypt"
|
62
|
-
end
|
63
|
-
|
64
|
-
op.on("-D", "--delete", "Delete the specified user.") do |d|
|
65
|
-
options.delete_entry = d
|
66
|
-
end
|
67
|
-
|
68
|
-
op.on("-h", "--help", "Display this help.") do |h|
|
69
|
-
options.show_help = h
|
70
|
-
end
|
71
|
-
|
72
|
-
op.on("-m", "--md5", "Force MD5 encryption of the password (default on Windows).") do |m|
|
73
|
-
options.algorithm = "md5"
|
74
|
-
end
|
75
|
-
|
76
|
-
op.on("-n", "--stdout", "Do not update the file; Display the results on stdout instead.") do |n|
|
77
|
-
options.send_to_stdout = true
|
78
|
-
options.passwdfile = HTAuth::File::STDOUT_FLAG
|
79
|
-
end
|
80
|
-
|
81
|
-
op.on("-p", "--plaintext", "Do not encrypt the password (plaintext).") do |p|
|
82
|
-
options.algorithm = "plaintext"
|
83
|
-
end
|
84
|
-
|
85
|
-
op.on("-s", "--sha1", "Force SHA encryption of the password.") do |s|
|
86
|
-
options.algorithm = "sha1"
|
87
|
-
end
|
88
|
-
|
89
|
-
op.on("-v", "--version", "Show version info.") do |v|
|
90
|
-
options.show_version = v
|
91
|
-
end
|
92
|
-
end
|
93
|
-
end
|
94
|
-
@option_parser
|
95
|
-
end
|
49
|
+
op.separator ""
|
96
50
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
end
|
51
|
+
op.on("-b", "--batch", "Batch mode, get the password from the command line, rather than prompt") do |b|
|
52
|
+
options.batch_mode = b
|
53
|
+
end
|
101
54
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
55
|
+
op.on("-c", "--create", "Create a new file; this overwrites an existing file.") do |c|
|
56
|
+
options.file_mode = HTAuth::File::CREATE
|
57
|
+
end
|
58
|
+
|
59
|
+
op.on("-d", "--crypt", "Force CRYPT encryption of the password (default).") do |c|
|
60
|
+
options.algorithm = "crypt"
|
61
|
+
end
|
62
|
+
|
63
|
+
op.on("-D", "--delete", "Delete the specified user.") do |d|
|
64
|
+
options.delete_entry = d
|
65
|
+
end
|
66
|
+
|
67
|
+
op.on("-h", "--help", "Display this help.") do |h|
|
68
|
+
options.show_help = h
|
69
|
+
end
|
70
|
+
|
71
|
+
op.on("-m", "--md5", "Force MD5 encryption of the password (default on Windows).") do |m|
|
72
|
+
options.algorithm = "md5"
|
73
|
+
end
|
74
|
+
|
75
|
+
op.on("-n", "--stdout", "Do not update the file; Display the results on stdout instead.") do |n|
|
76
|
+
options.send_to_stdout = true
|
77
|
+
options.passwdfile = HTAuth::File::STDOUT_FLAG
|
78
|
+
end
|
79
|
+
|
80
|
+
op.on("-p", "--plaintext", "Do not encrypt the password (plaintext).") do |p|
|
81
|
+
options.algorithm = "plaintext"
|
82
|
+
end
|
83
|
+
|
84
|
+
op.on("-s", "--sha1", "Force SHA encryption of the password.") do |s|
|
85
|
+
options.algorithm = "sha1"
|
86
|
+
end
|
106
87
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
show_version if options.show_version
|
111
|
-
show_help if options.show_help
|
112
|
-
|
113
|
-
raise ::OptionParser::ParseError, "Unable to send to stdout AND create a new file" if options.send_to_stdout and (options.file_mode == File::CREATE)
|
114
|
-
raise ::OptionParser::ParseError, "a username is needed" if options.send_to_stdout and argv.size < 1
|
115
|
-
raise ::OptionParser::ParseError, "a username and password are needed" if options.send_to_stdout and options.batch_mode and ( argv.size < 2 )
|
116
|
-
raise ::OptionParser::ParseError, "a passwordfile, username and password are needed " if not options.send_to_stdout and options.batch_mode and ( argv.size < 3 )
|
117
|
-
raise ::OptionParser::ParseError, "a passwordfile and username are needed" if argv.size < 2
|
118
|
-
|
119
|
-
options.passwdfile = argv.shift unless options.send_to_stdout
|
120
|
-
options.username = argv.shift
|
121
|
-
options.password = argv.shift if options.batch_mode
|
122
|
-
|
123
|
-
rescue ::OptionParser::ParseError => pe
|
124
|
-
$stderr.puts "ERROR: #{option_parser.program_name} - #{pe}"
|
125
|
-
show_help
|
126
|
-
exit 1
|
127
|
-
end
|
88
|
+
op.on("-v", "--version", "Show version info.") do |v|
|
89
|
+
options.show_version = v
|
90
|
+
end
|
128
91
|
end
|
92
|
+
end
|
93
|
+
@option_parser
|
94
|
+
end
|
95
|
+
|
96
|
+
def show_help
|
97
|
+
$stdout.puts option_parser
|
98
|
+
exit 1
|
99
|
+
end
|
100
|
+
|
101
|
+
def show_version
|
102
|
+
$stdout.puts "#{option_parser.program_name}: version #{HTAuth::VERSION}"
|
103
|
+
exit 1
|
104
|
+
end
|
105
|
+
|
106
|
+
def parse_options(argv)
|
107
|
+
begin
|
108
|
+
option_parser.parse!(argv)
|
109
|
+
show_version if options.show_version
|
110
|
+
show_help if options.show_help
|
111
|
+
|
112
|
+
raise ::OptionParser::ParseError, "Unable to send to stdout AND create a new file" if options.send_to_stdout and (options.file_mode == File::CREATE)
|
113
|
+
raise ::OptionParser::ParseError, "a username is needed" if options.send_to_stdout and argv.size < 1
|
114
|
+
raise ::OptionParser::ParseError, "a username and password are needed" if options.send_to_stdout and options.batch_mode and ( argv.size < 2 )
|
115
|
+
raise ::OptionParser::ParseError, "a passwordfile, username and password are needed " if not options.send_to_stdout and options.batch_mode and ( argv.size < 3 )
|
116
|
+
raise ::OptionParser::ParseError, "a passwordfile and username are needed" if argv.size < 2
|
117
|
+
|
118
|
+
options.passwdfile = argv.shift unless options.send_to_stdout
|
119
|
+
options.username = argv.shift
|
120
|
+
options.password = argv.shift if options.batch_mode
|
121
|
+
|
122
|
+
rescue ::OptionParser::ParseError => pe
|
123
|
+
$stderr.puts "ERROR: #{option_parser.program_name} - #{pe}"
|
124
|
+
show_help
|
125
|
+
exit 1
|
126
|
+
end
|
127
|
+
end
|
129
128
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
end
|
155
|
-
|
156
|
-
passwd_file.save!
|
157
|
-
|
158
|
-
rescue HTAuth::FileAccessError => fae
|
159
|
-
msg = "Password file failure (#{options.passwdfile}) "
|
160
|
-
$stderr.puts "#{msg}: #{fae.message}"
|
161
|
-
exit 1
|
162
|
-
rescue HTAuth::PasswordError => pe
|
163
|
-
$stderr.puts "#{pe.message}"
|
164
|
-
exit 1
|
165
|
-
rescue HTAuth::PasswdFileError => fe
|
166
|
-
$stderr.puts "#{fe.message}"
|
167
|
-
exit 1
|
168
|
-
rescue SignalException => se
|
169
|
-
$stderr.puts
|
170
|
-
$stderr.puts "Interrupted"
|
171
|
-
exit 1
|
172
|
-
end
|
173
|
-
exit 0
|
129
|
+
def run(argv, env = ENV)
|
130
|
+
begin
|
131
|
+
parse_options(argv)
|
132
|
+
passwd_file = PasswdFile.new(options.passwdfile, options.file_mode)
|
133
|
+
|
134
|
+
if options.delete_entry then
|
135
|
+
passwd_file.delete(options.username)
|
136
|
+
else
|
137
|
+
unless options.batch_mode
|
138
|
+
# initialize here so that if $stdin is overwritten it gest picked up
|
139
|
+
hl = ::HighLine.new
|
140
|
+
|
141
|
+
action = passwd_file.has_entry?(options.username) ? "Changing" : "Adding"
|
142
|
+
|
143
|
+
$stdout.puts "#{action} password for #{options.username}."
|
144
|
+
|
145
|
+
pw_in = hl.ask(" New password: ") { |q| q.echo = '*' }
|
146
|
+
raise PasswordError, "password '#{pw_in}' too long" if pw_in.length >= MAX_PASSWD_LENGTH
|
147
|
+
|
148
|
+
pw_validate = hl.ask("Re-type new password: ") { |q| q.echo = '*' }
|
149
|
+
raise PasswordError, "They don't match, sorry." unless pw_in == pw_validate
|
150
|
+
options.password = pw_in
|
151
|
+
end
|
152
|
+
passwd_file.add_or_update(options.username, options.password, options.algorithm)
|
174
153
|
end
|
154
|
+
|
155
|
+
passwd_file.save!
|
156
|
+
|
157
|
+
rescue HTAuth::FileAccessError => fae
|
158
|
+
msg = "Password file failure (#{options.passwdfile}) "
|
159
|
+
$stderr.puts "#{msg}: #{fae.message}"
|
160
|
+
exit 1
|
161
|
+
rescue HTAuth::PasswordError => pe
|
162
|
+
$stderr.puts "#{pe.message}"
|
163
|
+
exit 1
|
164
|
+
rescue HTAuth::PasswdFileError => fe
|
165
|
+
$stderr.puts "#{fe.message}"
|
166
|
+
exit 1
|
167
|
+
rescue SignalException => se
|
168
|
+
$stderr.puts
|
169
|
+
$stderr.puts "Interrupted"
|
170
|
+
exit 1
|
171
|
+
end
|
172
|
+
exit 0
|
175
173
|
end
|
174
|
+
end
|
176
175
|
end
|