From 49c852df0cffe2f8bbef7483f1be79b60b5d3693 Mon Sep 17 00:00:00 2001 From: Jamie Cameron Date: Tue, 25 Mar 2008 20:57:29 +0000 Subject: [PATCH] MD5 support for Webmin users using modules --- acl/CHANGELOG | 2 +- acl/acl-lib.pl | 26 ++++++---------- acl/md5-lib.pl | 1 + miniserv.pl | 62 ++++++++++++++++++++++++++++++++++----- useradmin/md5-lib.pl | 17 +++++++++++ webmin/change_session.cgi | 9 +++--- webmin/lang/en | 2 +- 7 files changed, 88 insertions(+), 31 deletions(-) create mode 120000 acl/md5-lib.pl diff --git a/acl/CHANGELOG b/acl/CHANGELOG index 2e7149c31..53b781692 100644 --- a/acl/CHANGELOG +++ b/acl/CHANGELOG @@ -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. diff --git a/acl/acl-lib.pl b/acl/acl-lib.pl index 052d02d9e..f2c4e6928 100644 --- a/acl/acl-lib.pl +++ b/acl/acl-lib.pl @@ -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 index 000000000..8c60ccdc0 --- /dev/null +++ b/acl/md5-lib.pl @@ -0,0 +1 @@ +../useradmin/md5-lib.pl \ No newline at end of file diff --git a/miniserv.pl b/miniserv.pl index 6603393c1..be78b0669 100755 --- a/miniserv.pl +++ b/miniserv.pl @@ -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]) { $_ = ; 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 diff --git a/useradmin/md5-lib.pl b/useradmin/md5-lib.pl index e8a1ec5c3..392efc41d 100644 --- a/useradmin/md5-lib.pl +++ b/useradmin/md5-lib.pl @@ -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 { diff --git a/webmin/change_session.cgi b/webmin/change_session.cgi index f18c2a1b8..196a83063 100755 --- a/webmin/change_session.cgi +++ b/webmin/change_session.cgi @@ -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', "$need")); } $gconfig{'md5pass'} = $in{'md5pass'}; &write_file("$config_directory/config", \%gconfig); diff --git a/webmin/lang/en b/webmin/lang/en index 16d0bd6ba..25c81f720 100644 --- a/webmin/lang/en +++ b/webmin/lang/en @@ -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 crypt 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 crypt 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