MD5 support for Webmin users using modules
authorJamie Cameron <jcameron@webmin.com>
Tue, 25 Mar 2008 20:57:29 +0000 (20:57 +0000)
committerJamie Cameron <jcameron@webmin.com>
Tue, 25 Mar 2008 20:57:29 +0000 (20:57 +0000)
acl/CHANGELOG
acl/acl-lib.pl
acl/md5-lib.pl [new symlink]
miniserv.pl
useradmin/md5-lib.pl
webmin/change_session.cgi
webmin/lang/en

index 2e7149c..53b7816 100644 (file)
@@ -46,4 +46,4 @@ Updated the user interface to use the Webmin UI library.
 Fixed the display of modules granted to groups.
 Added a per-user option to opt out of forced password changes after a certain number of days.
 A human-readable description of the password restrictions regular expression can be entered, for use in error messages.
-Webmin users can now be given temporary passwords, which they are forced to change at the next login.
+Webmin users can now be given temporary passwords, which they are forced to change at the next login. Thanks to GE Medical Systems for supporting this feature.
index 052d02d..f2c4e69 100644 (file)
@@ -4,6 +4,7 @@
 do '../web-lib.pl';
 &init_config();
 do '../ui-lib.pl';
+do 'md5-lib.pl';
 %access = &get_module_acl();
 $access{'switch'} = 0 if (&is_readonly_mode());
 
@@ -658,14 +659,17 @@ else {
 }
 
 # encrypt_password(password, [salt])
+# Encrypts a Webmin user password
 sub encrypt_password
 {
 local ($pass, $salt) = @_;
 if ($gconfig{'md5pass'}) {
-       $salt ||= '$1$'.substr(time(), -8);
-       return crypt($pass, $salt);
+       # Use MD5 encryption
+       $salt ||= '$1$'.substr(time(), -8).'$xxxxxxxxxxxxxxxxxxxxxx';
+       return &encrypt_md5($pass, $salt);
        }
 else {
+       # Use Unix DES
        &seed_random();
        $salt ||= chr(int(rand(26))+65).chr(int(rand(26))+65);
        return &unix_crypt($pass, $salt);
@@ -797,7 +801,7 @@ local $use_md5 = &md5_perl_module();
 if (!$hash_session_id_cache{$sid}) {
         if ($use_md5) {
                 # Take MD5 hash
-                $hash_session_id_cache{$sid} = &encrypt_md5($sid);
+                $hash_session_id_cache{$sid} = &hash_md5_session($sid);
                 }
         else {
                 # Unix crypt
@@ -807,9 +811,9 @@ if (!$hash_session_id_cache{$sid}) {
 return $hash_session_id_cache{$sid};
 }
 
-# encrypt_md5(string)
+# hash_md5_session(string)
 # Returns a string encrypted in MD5 format
-sub encrypt_md5
+sub hash_md5_session
 {
 local $passwd = $_[0];
 local $use_md5 = &md5_perl_module();
@@ -860,18 +864,6 @@ $rv .= &to64($l, 2);
 return $rv;
 }
 
-sub to64
-{
-local ($v, $n) = @_;
-local @itoa64 = split(//, "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
-local $r;
-while(--$n >= 0) {
-        $r .= $itoa64[$v & 0x3f];
-        $v >>= 6;
-        }
-return $r;
-}
-
 # Returns a Perl module for MD5 hashing, or undef if none
 sub md5_perl_module
 {
diff --git a/acl/md5-lib.pl b/acl/md5-lib.pl
new file mode 120000 (symlink)
index 0000000..8c60ccd
--- /dev/null
@@ -0,0 +1 @@
+../useradmin/md5-lib.pl
\ No newline at end of file
index 6603393..be78b06 100755 (executable)
@@ -2963,7 +2963,8 @@ elsif ($canmode == 0) {
        }
 elsif ($canmode == 1) {
        # Attempt Webmin authentication
-       if ($users{$webminuser} eq &unix_crypt($pass, $users{$webminuser})) {
+       if ($users{$webminuser} eq
+           &password_crypt($pass, $users{$webminuser})) {
                # Password is valid .. but check for expiry
                local $lc = $lastchanges{$user};
                if ($config{'pass_maxdays'} && $lc && !$nochange{$user}) {
@@ -3066,7 +3067,8 @@ elsif ($config{'passwd_file'}) {
                        if (/^\s*(\S+):/ && $1 eq $_[0]) {
                                $_ = <FILE>;
                                if (/^\s*password\s*=\s*(\S+)\s*$/) {
-                                       $rv = $1 eq &unix_crypt($_[1], $1) ? 1 : 0;
+                                       $rv = $1 eq &password_crypt($_[1], $1) ?
+                                               1 : 0;
                                        }
                                last;
                                }
@@ -3079,7 +3081,7 @@ elsif ($config{'passwd_file'}) {
                        local $u = $l[$config{'passwd_uindex'}];
                        local $p = $l[$config{'passwd_pindex'}];
                        if ($u eq $_[0]) {
-                               $rv = $p eq &unix_crypt($_[1], $p) ? 1 : 0;
+                               $rv = $p eq &password_crypt($_[1], $p) ? 1 : 0;
                                if ($config{'passwd_cindex'} ne '' && $rv) {
                                        # Password may have expired!
                                        local $c = $l[$config{'passwd_cindex'}];
@@ -3105,7 +3107,7 @@ elsif ($config{'passwd_file'}) {
 
 # Fallback option - check password returned by getpw*
 local @uinfo = getpwnam($_[0]);
-if ($uinfo[1] ne '' && &unix_crypt($_[1], $uinfo[1]) eq $uinfo[1]) {
+if ($uinfo[1] ne '' && &password_crypt($_[1], $uinfo[1]) eq $uinfo[1]) {
        return 1;
        }
 
@@ -4161,6 +4163,22 @@ if (!defined($logout_time_cache{$user,$sid})) {
 return $logout_time_cache{$user,$sid};
 }
 
+# password_crypt(password, salt)
+# If the salt looks like MD5 and we have a library for it, perform MD5 hashing
+# of a password. Otherwise, do Unix crypt.
+sub password_crypt
+{
+local ($pass, $salt) = @_;
+if ($salt =~ /^\$1\$/ && $use_md5) {
+       return &encrypt_md5($pass, $salt);
+       }
+else {
+       return &unix_crypt($pass, $salt);
+       }
+}
+
+# unix_crypt(password, salt)
+# Performs standard Unix hashing for a password
 sub unix_crypt
 {
 local ($pass, $salt) = @_;
@@ -4591,19 +4609,31 @@ if (!$hash_session_id_cache{$sid}) {
 return $hash_session_id_cache{$sid};
 }
 
-# encrypt_md5(string)
+# encrypt_md5(string, [salt])
 # Returns a string encrypted in MD5 format
 sub encrypt_md5
 {
-local $passwd = $_[0];
+local ($passwd, $salt) = @_;
+local $magic = '$1$';
+if ($salt =~ /^\$1\$(.{8})/) {
+       # Extract actual salt from already encrypted password
+       $salt = $1;
+       }
 
 # Add the password
 local $ctx = eval "new $use_md5";
 $ctx->add($passwd);
+if ($salt) {
+       $ctx->add($magic);
+       $ctx->add($salt);
+       }
 
 # Add some more stuff from the hash of the password and salt
 local $ctx1 = eval "new $use_md5";
 $ctx1->add($passwd);
+if ($salt) {
+       $ctx1->add($salt);
+       }
 $ctx1->add($passwd);
 local $final = $ctx1->digest();
 for($pl=length($passwd); $pl>0; $pl-=16) {
@@ -4624,6 +4654,18 @@ for($i=length($passwd); $i; $i >>= 1) {
        }
 $final = $ctx->digest();
 
+if ($salt) {
+       # This loop exists only to waste time
+       for($i=0; $i<1000; $i++) {
+               $ctx1 = eval "new $use_md5";
+               $ctx1->add($i & 1 ? $passwd : $final);
+               $ctx1->add($salt) if ($i % 3);
+               $ctx1->add($passwd) if ($i % 7);
+               $ctx1->add($i & 1 ? $final : $passwd);
+               $final = $ctx1->digest();
+               }
+       }
+
 # Convert the 16-byte final string into a readable form
 local $rv;
 local @final = map { ord($_) } split(//, $final);
@@ -4640,7 +4682,13 @@ $rv .= &to64($l, 4);
 $l = $final[11];
 $rv .= &to64($l, 2);
 
-return $rv;
+# Add salt if needed
+if ($salt) {
+       return $magic.$salt.'$'.$rv;
+       }
+else {
+       return $rv;
+       }
 }
 
 sub to64
index e8a1ec5..392efc4 100644 (file)
@@ -5,6 +5,10 @@
 # are not installed, or undef if they are
 sub check_md5
 {
+# On some systems, the crypt function just works!
+return undef if (&unix_crypt_supports_md5());
+
+# Try Perl modules
 eval "use MD5";
 if (!$@) {
        eval "use Digest::MD5";
@@ -27,6 +31,11 @@ if ($salt =~ /^\$1\$(.{8})/) {
        $salt = $1;
        }
 
+# Use built-in crypt support for MD5, if we can
+if (&unix_crypt_supports_md5()) {
+       return &unix_crypt($passwd, $magic.$salt.'$xxxxxxxxxxxxxxxxxxxxxx');
+       }
+
 # Add the password, magic and salt
 local $cls = "MD5";
 eval "use MD5";
@@ -95,6 +104,14 @@ $rv .= &to64($l, 2);
 return $rv;
 }
 
+# unix_crypt_supports_md5()
+# Returns 1 if the built-in crypt() function can already do MD5
+sub unix_crypt_supports_md5
+{
+return &unix_crypt('test', '$1$A9wB3O18$zaZgqrEmb9VNltWTL454R/') eq
+       '$1$A9wB3O18$zaZgqrEmb9VNltWTL454R/';
+}
+
 @itoa64 = split(//, "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
 sub to64
 {
index f18c2a1..196a830 100755 (executable)
@@ -111,11 +111,10 @@ else {
        $gconfig{'loginbanner'} = $in{'banner'};
        }
 if ($in{'md5pass'}) {
-       # MD5 enabled .. but is it supported by crypt?
-       if (&unix_crypt('test', '$1$A9wB3O18$zaZgqrEmb9VNltWTL454R/') ne
-           '$1$A9wB3O18$zaZgqrEmb9VNltWTL454R/') {
-               &error($text{'session_emd5'});
-               }
+       # MD5 enabled .. but is it supported by this system?
+       &foreign_require("acl", "acl-lib.pl");
+       $need = &acl::check_md5();
+       $need && &error(&text('session_emd5mod', "<tt>$need</tt>"));
        }
 $gconfig{'md5pass'} = $in{'md5pass'};
 &write_file("$config_directory/config", \%gconfig);
index 16d0bd6..25c81f7 100644 (file)
@@ -543,7 +543,7 @@ session_pmode1=Always allow users with expired passwords
 session_pmode2=Prompt users with expired passwords to enter a new one
 session_md5off=Use standard Unix <tt>crypt</tt> encryption for Webmin passwords
 session_md5on=Use MD5 encryption for Webmin passwords (allows long passwords)
-session_emd5=MD5 encryption cannot be used, as Perl does not have built-in <tt>crypt</tt> MD5 support on your system
+session_emd5mod=MD5 encryption cannot be used, as Perl $1 module is not installed.
 session_blocklock=Also lock users with failed logins
 
 assignment_title=Reassign Modules