diff --git a/functions.inc.php b/functions.inc.php index d1c6be7a..42ad665a 100644 --- a/functions.inc.php +++ b/functions.inc.php @@ -892,152 +892,151 @@ function validate_password($password) { } function _pacrypt_md5crypt($pw, $pw_db) { - $split_salt = preg_split('/\$/', $pw_db); - if (isset($split_salt[2])) { - $salt = $split_salt[2]; - return md5crypt($pw, $salt); - } + $split_salt = preg_split('/\$/', $pw_db); + if (isset($split_salt[2])) { + $salt = $split_salt[2]; + return md5crypt($pw, $salt); + } - return md5crypt($pw); + return md5crypt($pw); } function _pacrypt_crypt($pw, $pw_db) { - - if ($pw_db) { - return crypt($pw, $pw_db); - } - return crypt($pw); + if ($pw_db) { + return crypt($pw, $pw_db); + } + return crypt($pw); } function _pacrypt_mysql_encrypt($pw, $pw_db) { // See https://sourceforge.net/tracker/?func=detail&atid=937966&aid=1793352&group_id=191583 // this is apparently useful for pam_mysql etc. - $pw = escape_string($pw); - if ($pw_db!="") { - $salt=escape_string(substr($pw_db, 0, 2)); - $res=db_query("SELECT ENCRYPT('".$pw."','".$salt."');"); - } else { - $res=db_query("SELECT ENCRYPT('".$pw."');"); - } - $l = db_row($res["result"]); - $password = $l[0]; - return $password; + $pw = escape_string($pw); + if ($pw_db!="") { + $salt=escape_string(substr($pw_db, 0, 2)); + $res=db_query("SELECT ENCRYPT('".$pw."','".$salt."');"); + } else { + $res=db_query("SELECT ENCRYPT('".$pw."');"); + } + $l = db_row($res["result"]); + $password = $l[0]; + return $password; } function _pacrypt_authlib($pw, $pw_db) { global $CONF; $flavor = $CONF['authlib_default_flavor']; - $salt = substr(create_salt(), 0, 2); # courier-authlib supports only two-character salts - if (preg_match('/^{.*}/', $pw_db)) { - // we have a flavor in the db -> use it instead of default flavor - $result = preg_split('/[{}]/', $pw_db, 3); # split at { and/or } - $flavor = $result[1]; - $salt = substr($result[2], 0, 2); - } - - if (stripos($flavor, 'md5raw') === 0) { - $password = '{' . $flavor . '}' . md5($pw); - } elseif (stripos($flavor, 'md5') === 0) { - $password = '{' . $flavor . '}' . base64_encode(md5($pw, true)); - } elseif (stripos($flavor, 'crypt') === 0) { - $password = '{' . $flavor . '}' . crypt($pw, $salt); - } elseif (stripos($flavor, 'SHA') === 0) { - $password = '{' . $flavor . '}' . base64_encode(sha1($pw, true)); - } else { - die("authlib_default_flavor '" . $flavor . "' unknown. Valid flavors are 'md5raw', 'md5', 'SHA' and 'crypt'"); - } - return $password; + $salt = substr(create_salt(), 0, 2); # courier-authlib supports only two-character salts + if (preg_match('/^{.*}/', $pw_db)) { + // we have a flavor in the db -> use it instead of default flavor + $result = preg_split('/[{}]/', $pw_db, 3); # split at { and/or } + $flavor = $result[1]; + $salt = substr($result[2], 0, 2); + } + + if (stripos($flavor, 'md5raw') === 0) { + $password = '{' . $flavor . '}' . md5($pw); + } elseif (stripos($flavor, 'md5') === 0) { + $password = '{' . $flavor . '}' . base64_encode(md5($pw, true)); + } elseif (stripos($flavor, 'crypt') === 0) { + $password = '{' . $flavor . '}' . crypt($pw, $salt); + } elseif (stripos($flavor, 'SHA') === 0) { + $password = '{' . $flavor . '}' . base64_encode(sha1($pw, true)); + } else { + die("authlib_default_flavor '" . $flavor . "' unknown. Valid flavors are 'md5raw', 'md5', 'SHA' and 'crypt'"); + } + return $password; } function _pacrypt_dovecot($pw, $pw_db) { global $CONF; - $split_method = preg_split('/:/', $CONF['encrypt']); - $method = strtoupper($split_method[1]); - # If $pw_db starts with {method}, change $method accordingly - if (!empty($pw_db) && preg_match('/^\{([A-Z0-9.-]+)\}.+/', $pw_db, $method_matches)) { - $method = $method_matches[1]; - } - if (! preg_match("/^[A-Z0-9.-]+$/", $method)) { - die("invalid dovecot encryption method"); - } - - # TODO: check against a fixed list? - # if (strtolower($method) == 'md5-crypt') die("\$CONF['encrypt'] = 'dovecot:md5-crypt' will not work because dovecotpw generates a random salt each time. Please use \$CONF['encrypt'] = 'md5crypt' instead."); - # $crypt_method = preg_match ("/.*-CRYPT$/", $method); - - # digest-md5 and SCRAM-SHA-1 hashes include the username - until someone implements it, let's declare it as unsupported - if (strtolower($method) == 'digest-md5') { - die("Sorry, \$CONF['encrypt'] = 'dovecot:digest-md5' is not supported by PostfixAdmin."); - } - if (strtoupper($method) == 'SCRAM-SHA-1') { - die("Sorry, \$CONF['encrypt'] = 'dovecot:scram-sha-1' is not supported by PostfixAdmin."); - } - # TODO: add -u option for those hashes, or for everything that is salted (-u was available before dovecot 2.1 -> no problem with backward compability) - - $dovecotpw = "doveadm pw"; - if (!empty($CONF['dovecotpw'])) { - $dovecotpw = $CONF['dovecotpw']; - } - - # Use proc_open call to avoid safe_mode problems and to prevent showing plain password in process table - $spec = array( - 0 => array("pipe", "r"), // stdin - 1 => array("pipe", "w"), // stdout - 2 => array("pipe", "w"), // stderr - ); - - $nonsaltedtypes = "SHA|SHA1|SHA256|SHA512|CLEAR|CLEARTEXT|PLAIN|PLAIN-TRUNC|CRAM-MD5|HMAC-MD5|PLAIN-MD4|PLAIN-MD5|LDAP-MD5|LANMAN|NTLM|RPA"; - $salted = ! preg_match("/^($nonsaltedtypes)(\.B64|\.BASE64|\.HEX)?$/", strtoupper($method)); - - $dovepasstest = ''; - if ($salted && (!empty($pw_db))) { - # only use -t for salted passwords to be backward compatible with dovecot < 2.1 - $dovepasstest = " -t " . escapeshellarg($pw_db); - } - $pipe = proc_open("$dovecotpw '-s' $method$dovepasstest", $spec, $pipes); - - if (!$pipe) { - die("can't proc_open $dovecotpw"); - } - - // use dovecot's stdin, it uses getpass() twice (except when using -t) - // Write pass in pipe stdin - if (empty($dovepasstest)) { - fwrite($pipes[0], $pw . "\n", 1+strlen($pw)); - usleep(1000); - } - fwrite($pipes[0], $pw . "\n", 1+strlen($pw)); - fclose($pipes[0]); - - // Read hash from pipe stdout - $password = fread($pipes[1], "200"); - - if (empty($dovepasstest)) { - if (!preg_match('/^\{' . $method . '\}/', $password)) { - $stderr_output = stream_get_contents($pipes[2]); - error_log('dovecotpw password encryption failed.'); - error_log('STDERR output: ' . $stderr_output); - die("can't encrypt password with dovecotpw, see error log for details"); - } - } else { - if (!preg_match('(verified)', $password)) { - $password="Thepasswordcannotbeverified"; - } else { - $password = rtrim(str_replace('(verified)', '', $password)); - } - } - - fclose($pipes[1]); - fclose($pipes[2]); - proc_close($pipe); - - if ((!empty($pw_db)) && (substr($pw_db, 0, 1) != '{')) { - # for backward compability with "old" dovecot passwords that don't have the {method} prefix - $password = str_replace('{' . $method . '}', '', $password); - } - - return rtrim($password); + $split_method = preg_split('/:/', $CONF['encrypt']); + $method = strtoupper($split_method[1]); + # If $pw_db starts with {method}, change $method accordingly + if (!empty($pw_db) && preg_match('/^\{([A-Z0-9.-]+)\}.+/', $pw_db, $method_matches)) { + $method = $method_matches[1]; + } + if (! preg_match("/^[A-Z0-9.-]+$/", $method)) { + die("invalid dovecot encryption method"); + } + + # TODO: check against a fixed list? + # if (strtolower($method) == 'md5-crypt') die("\$CONF['encrypt'] = 'dovecot:md5-crypt' will not work because dovecotpw generates a random salt each time. Please use \$CONF['encrypt'] = 'md5crypt' instead."); + # $crypt_method = preg_match ("/.*-CRYPT$/", $method); + + # digest-md5 and SCRAM-SHA-1 hashes include the username - until someone implements it, let's declare it as unsupported + if (strtolower($method) == 'digest-md5') { + die("Sorry, \$CONF['encrypt'] = 'dovecot:digest-md5' is not supported by PostfixAdmin."); + } + if (strtoupper($method) == 'SCRAM-SHA-1') { + die("Sorry, \$CONF['encrypt'] = 'dovecot:scram-sha-1' is not supported by PostfixAdmin."); + } + # TODO: add -u option for those hashes, or for everything that is salted (-u was available before dovecot 2.1 -> no problem with backward compability) + + $dovecotpw = "doveadm pw"; + if (!empty($CONF['dovecotpw'])) { + $dovecotpw = $CONF['dovecotpw']; + } + + # Use proc_open call to avoid safe_mode problems and to prevent showing plain password in process table + $spec = array( + 0 => array("pipe", "r"), // stdin + 1 => array("pipe", "w"), // stdout + 2 => array("pipe", "w"), // stderr + ); + + $nonsaltedtypes = "SHA|SHA1|SHA256|SHA512|CLEAR|CLEARTEXT|PLAIN|PLAIN-TRUNC|CRAM-MD5|HMAC-MD5|PLAIN-MD4|PLAIN-MD5|LDAP-MD5|LANMAN|NTLM|RPA"; + $salted = ! preg_match("/^($nonsaltedtypes)(\.B64|\.BASE64|\.HEX)?$/", strtoupper($method)); + + $dovepasstest = ''; + if ($salted && (!empty($pw_db))) { + # only use -t for salted passwords to be backward compatible with dovecot < 2.1 + $dovepasstest = " -t " . escapeshellarg($pw_db); + } + $pipe = proc_open("$dovecotpw '-s' $method$dovepasstest", $spec, $pipes); + + if (!$pipe) { + die("can't proc_open $dovecotpw"); + } + + // use dovecot's stdin, it uses getpass() twice (except when using -t) + // Write pass in pipe stdin + if (empty($dovepasstest)) { + fwrite($pipes[0], $pw . "\n", 1+strlen($pw)); + usleep(1000); + } + fwrite($pipes[0], $pw . "\n", 1+strlen($pw)); + fclose($pipes[0]); + + // Read hash from pipe stdout + $password = fread($pipes[1], "200"); + + if (empty($dovepasstest)) { + if (!preg_match('/^\{' . $method . '\}/', $password)) { + $stderr_output = stream_get_contents($pipes[2]); + error_log('dovecotpw password encryption failed.'); + error_log('STDERR output: ' . $stderr_output); + die("can't encrypt password with dovecotpw, see error log for details"); + } + } else { + if (!preg_match('(verified)', $password)) { + $password="Thepasswordcannotbeverified"; + } else { + $password = rtrim(str_replace('(verified)', '', $password)); + } + } + + fclose($pipes[1]); + fclose($pipes[2]); + proc_close($pipe); + + if ((!empty($pw_db)) && (substr($pw_db, 0, 1) != '{')) { + # for backward compability with "old" dovecot passwords that don't have the {method} prefix + $password = str_replace('{' . $method . '}', '', $password); + } + + return rtrim($password); } @@ -1059,24 +1058,23 @@ function pacrypt($pw, $pw_db="") { switch ($CONF['encrypt']) { case 'md5crypt': return _pacrypt_md5crypt($pw, $pw_db); - case 'md5': + case 'md5': return md5($pw); - case 'system' : + case 'system': return _pacrypt_crypt($pw, $pw_db); - case 'cleartext' : + case 'cleartext': return $pw; - case 'mysql_encrypt' : + case 'mysql_encrypt': return _pacrypt_mysql_encrypt($pw, $pw_db); - case 'authlib': - return _pacrypt_authlib($pw, $pw_db); + case 'authlib': + return _pacrypt_authlib($pw, $pw_db); } if (preg_match("/^dovecot:/", $CONF['encrypt'])) { - return _pacrypt_dovecot($pw, $pw_db); - } + return _pacrypt_dovecot($pw, $pw_db); + } die('unknown/invalid $CONF["encrypt"] setting: ' . $CONF['encrypt']); - } // diff --git a/tests/PacryptTest.php b/tests/PacryptTest.php index bf4defea..55bb86e2 100644 --- a/tests/PacryptTest.php +++ b/tests/PacryptTest.php @@ -3,7 +3,6 @@ require_once('common.php'); class PaCryptTest extends PHPUnit_Framework_TestCase { - public function testMd5Crypt() { $hash = _pacrypt_md5crypt('test', ''); @@ -23,11 +22,10 @@ class PaCryptTest extends PHPUnit_Framework_TestCase { $this->assertNotEquals('test', $hash); $this->assertEquals($hash, _pacrypt_crypt('test', $hash)); - } public function testMySQLEncrypt() { - if(!db_mysql()) { + if (!db_mysql()) { $this->markTestSkipped('Not using MySQL'); } @@ -48,26 +46,25 @@ class PaCryptTest extends PHPUnit_Framework_TestCase { public function testAuthlib() { - // too many options! - foreach( - ['md5raw' => '098f6bcd4621d373cade4e832627b4f6', - 'md5' => 'CY9rzUYh03PK3k6DJie09g==', - // crypt requires salt ... + // too many options! + foreach ( + ['md5raw' => '098f6bcd4621d373cade4e832627b4f6', + 'md5' => 'CY9rzUYh03PK3k6DJie09g==', + // crypt requires salt ... 'SHA' => 'qUqP5cyxm6YcTAhz05Hph5gvu9M='] as $flavour => $hash) { - $CONF['authlib_default_flavour'] = $flavour; $stored = "{" . $flavour . "}$hash"; $hash = _pacrypt_authlib('test', $stored); - $this->assertEquals($hash, $stored, "Hash: $hash vs Stored: $stored" ); + $this->assertEquals($hash, $stored, "Hash: $hash vs Stored: $stored"); //var_dump("Hash: $hash from $flavour"); } } public function testPacryptDovecot() { global $CONF; - if(!file_exists('/usr/bin/doveadm')) { + if (!file_exists('/usr/bin/doveadm')) { $this->markTestSkipped("No /usr/bin/doveadm"); } @@ -79,10 +76,7 @@ class PaCryptTest extends PHPUnit_Framework_TestCase { $this->assertEquals($expected_hash, _pacrypt_dovecot('test', '')); $this->assertEquals($expected_hash, _pacrypt_dovecot('test', $expected_hash)); - - } - } /* vim: set expandtab softtabstop=4 tabstop=4 shiftwidth=4: */ diff --git a/tests/RemoteUserTest.php b/tests/RemoteUserTest.php index 66077407..84c758e0 100644 --- a/tests/RemoteUserTest.php +++ b/tests/RemoteUserTest.php @@ -8,8 +8,6 @@ require_once('RemoteTest.php'); class RemoteUserTest extends RemoteTest { - - public function testChangePassword() { try { $this->assertTrue($this->user->login($this->username, $this->password));