Handle hostnames with upper-case letters
[webmin.git] / useradmin / md5-lib.pl
1 # Functions for MD5 and SHA1 password encryption
2
3 # check_md5()
4 # Returns a perl module name if the needed perl module(s) for MD5 encryption
5 # are not installed, or undef if they are
6 sub check_md5
7 {
8 # On some systems, the crypt function just works!
9 return undef if (&unix_crypt_supports_md5());
10
11 # Try Perl modules
12 eval "use MD5";
13 if (!$@) {
14         eval "use Digest::MD5";
15         if ($@) {
16                 return "Digest::MD5";
17                 }
18         }
19 return undef;
20 }
21
22 # encrypt_md5(string, [salt])
23 # Returns a string encrypted in MD5 format
24 sub encrypt_md5
25 {
26 local $passwd = $_[0];
27 local $salt = $_[1];
28 local $magic = '$1$';
29 if ($salt =~ /^\$1\$([^\$]+)/) {
30         # Extract actual salt from already encrypted password
31         $salt = $1;
32         }
33 if ($salt !~ /^[a-z0-9\/]{8}$/i) {
34         # Non-MD5 salt
35         $salt = undef;
36         }
37 $salt ||= substr(time(), -8);
38
39 # Use built-in crypt support for MD5, if we can
40 if (&unix_crypt_supports_md5()) {
41         return crypt($passwd, $magic.$salt.'$xxxxxxxxxxxxxxxxxxxxxx');
42         }
43
44 # Add the password, magic and salt
45 local $cls = "MD5";
46 eval "use MD5";
47 if ($@) {
48         $cls = "Digest::MD5";
49         eval "use Digest::MD5";
50         if ($@) {
51                 &error("Missing MD5 or Digest::MD5 perl modules");
52                 }
53         }
54 local $ctx = eval "new $cls";
55 $ctx->add($passwd);
56 $ctx->add($magic);
57 $ctx->add($salt);
58
59 # Add some more stuff from the hash of the password and salt
60 local $ctx1 = eval "new $cls";
61 $ctx1->add($passwd);
62 $ctx1->add($salt);
63 $ctx1->add($passwd);
64 local $final = $ctx1->digest();
65 for($pl=length($passwd); $pl>0; $pl-=16) {
66         $ctx->add($pl > 16 ? $final : substr($final, 0, $pl));
67         }
68
69 # This piece of code seems rather pointless, but it's in the C code that
70 # does MD5 in PAM so it has to go in!
71 local $j = 0;
72 local ($i, $l);
73 for($i=length($passwd); $i; $i >>= 1) {
74         if ($i & 1) {
75                 $ctx->add("\0");
76                 }
77         else {
78                 $ctx->add(substr($passwd, $j, 1));
79                 }
80         }
81 $final = $ctx->digest();
82
83 # This loop exists only to waste time
84 for($i=0; $i<1000; $i++) {
85         $ctx1 = eval "new $cls";
86         $ctx1->add($i & 1 ? $passwd : $final);
87         $ctx1->add($salt) if ($i % 3);
88         $ctx1->add($passwd) if ($i % 7);
89         $ctx1->add($i & 1 ? $final : $passwd);
90         $final = $ctx1->digest();
91         }
92
93 # Convert the 16-byte final string into a readable form
94 local $rv = $magic.$salt.'$';
95 local @final = map { ord($_) } split(//, $final);
96 $l = ($final[ 0]<<16) + ($final[ 6]<<8) + $final[12];
97 $rv .= &to64($l, 4);
98 $l = ($final[ 1]<<16) + ($final[ 7]<<8) + $final[13];
99 $rv .= &to64($l, 4);
100 $l = ($final[ 2]<<16) + ($final[ 8]<<8) + $final[14];
101 $rv .= &to64($l, 4);
102 $l = ($final[ 3]<<16) + ($final[ 9]<<8) + $final[15];
103 $rv .= &to64($l, 4);
104 $l = ($final[ 4]<<16) + ($final[10]<<8) + $final[ 5];
105 $rv .= &to64($l, 4);
106 $l = $final[11];
107 $rv .= &to64($l, 2);
108
109 return $rv;
110 }
111
112 # unix_crypt_supports_md5()
113 # Returns 1 if the built-in crypt() function can already do MD5
114 sub unix_crypt_supports_md5
115 {
116 my $hash = '$1$A9wB3O18$zaZgqrEmb9VNltWTL454R/';
117 my $newhash = eval { crypt('test', $hash) };
118 return $newhash eq $hash;
119 }
120
121 @itoa64 = split(//, "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
122 sub to64
123 {
124 local ($v, $n) = @_;
125 local $r;
126 while(--$n >= 0) {
127         $r .= $itoa64[$v & 0x3f];
128         $v >>= 6;
129         }
130 return $r;
131 }
132
133 sub check_sha1
134 {
135 eval "use Digest::SHA1";
136 return $@ ? "Digest::SHA1" : undef;
137 }
138
139 # encrypt_sha1(password)
140 # Encrypts a password in SHA1 format
141 sub encrypt_sha1
142 {
143 local $pass = $_[0];
144 local $sh = eval "use Digest::SHA1 qw(sha1_base64);return sha1_base64(\$pass);";
145 return "{SHA}$sh=";
146 }
147
148 # encrypt_sha1_hash(password, salt)
149 # Hashes a combined salt+password with SHA1, and returns it in hex. Used on OSX
150 sub encrypt_sha1_hash
151 {
152 local ($pass, $salt) = @_;
153 # XXX not done yet??
154 }
155
156 # check_blowfish()
157 # Returns an missing Perl module if blowfish is not available, undef if OK
158 sub check_blowfish
159 {
160 eval "use Crypt::Eksblowfish::Bcrypt";
161 return $@ ? "Crypt::Eksblowfish::Bcrypt" : undef;
162 }
163
164 # encrypt_blowfish(password, [salt])
165 # Returns a string encrypted in blowfish format, suitable for /etc/shadow
166 sub encrypt_blowfish
167 {
168 local ($passwd, $salt) = @_;
169 local ($plain, $base64);
170 eval "use Crypt::Eksblowfish::Bcrypt";
171 if ($salt !~ /^\$2a\$/) {
172         # Invalid salt for Blowfish
173         $salt = undef;
174         }
175 if (!$salt) {
176         # Generate a 22-character base-64 format salt
177         &seed_random();
178         while(length($base64) < 22) {
179                 $plain .= chr(int(rand()*96)+32);
180                 $base64 = Crypt::Eksblowfish::Bcrypt::en_base64($plain);
181                 }
182         $base64 = substr($base64, 0, 22);
183         $salt = '$2a$'.'08'.'$'.$base64;
184         }
185 return Crypt::Eksblowfish::Bcrypt::bcrypt($passwd, $salt);
186 }
187
188 # unix_crypt_supports_sha512()
189 # Returns 1 if the built-in crypt() function can already do SHA512
190 sub unix_crypt_supports_sha512
191 {
192 my $hash = '$6$Tk5o/GEE$zjvXhYf/dr5M7/jan3pgunkNrAsKmQO9r5O8sr/Cr1hFOLkWmsH4iE9hhqdmHwXd5Pzm4ubBWTEjtMeC.h5qv1';
193 my $newhash = eval { crypt('test', $hash) };
194 return $newhash eq $hash;
195 }
196
197 # check_sha512()
198 # Returns undef if SHA512 hashing is supported, or an error message if not
199 sub check_sha512
200 {
201 return &unix_crypt_supports_sha512() ? undef : 'Crypt::SHA';
202 }
203
204 # encrypt_sha512(password, [salt])
205 # Hashes a password, possibly with the give salt, with SHA512
206 sub encrypt_sha512
207 {
208 local ($passwd, $salt) = @_;
209 $salt ||= '$6$'.substr(time(), -8).'$';
210 return crypt($passwd, $salt);
211 }
212
213 # validate_password(password, hash)
214 # Compares a password with a hash to see if they match, returns 1 if so,
215 # 0 otherwise. Tries all supported hashing schemes.
216 sub validate_password
217 {
218 local ($passwd, $hash) = @_;
219
220 # Classic Unix crypt
221 local $chash = eval {
222         local $main::error_must_die = 1;
223         &unix_crypt($passwd, $hash);
224         };
225 return 1 if ($chash eq $hash);
226
227 # MD5
228 if (!&check_md5()) {
229         local $mhash = &encrypt_md5($passwd, $hash);
230         return 1 if ($mhash eq $hash);
231         }
232
233 # Blowfish
234 if (!&check_blowfish()) {
235         local $mhash = &encrypt_blowfish($passwd, $hash);
236         return 1 if ($mhash eq $hash);
237         }
238
239 # SHA1
240 if (!&check_sha512()) {
241         local $shash = &encrypt_sha512($passwd, $hash);
242         return 1 if ($shash eq $hash);
243         }
244
245 # Some other hashing, maybe supported by crypt
246 local $ohash = eval { crypt($passwd, $hash) };
247 return 1 if ($ohash eq $hash);
248
249 return 0;
250 }
251
252 1;
253