From 172f26c45365bd2c115f9c2e13b6d76eca6ad2e7 Mon Sep 17 00:00:00 2001
From: prasad <prasad@vtiger.com>
Date: Thu, 19 Apr 2018 17:26:56 +0530
Subject: [PATCH] Fixes #28: Default password encryption changed to PHASH mode.

---
 modules/Users/Users.php    | 115 +++++++++++++++++--------------------
 vtlib/Vtiger/Functions.php |   8 ++-
 2 files changed, 59 insertions(+), 64 deletions(-)

diff --git a/modules/Users/Users.php b/modules/Users/Users.php
index 0a94f946b..cf70a282d 100755
--- a/modules/Users/Users.php
+++ b/modules/Users/Users.php
@@ -137,8 +137,10 @@ class Users extends CRMEntity {
 		$this->log = LoggerManager::getLogger('user');
 		$this->log->debug("Entering Users() method ...");
 		$this->db = PearDatabase::getInstance();
-		$this->DEFAULT_PASSWORD_CRYPT_TYPE = (version_compare(PHP_VERSION, '5.3.0') >= 0)?
-				'PHP5.3MD5': 'MD5';
+		$this->DEFAULT_PASSWORD_CRYPT_TYPE = (version_compare(PHP_VERSION, '5.3.0') >= 0)? 'PHP5.3MD5': 'MD5';
+		if (version_compare(PHP_VERSION, '5.5.0') >= 0) {
+			$this->DEFAULT_PASSWORD_CRYPT_TYPE = 'PHASH';
+		}
 		$this->column_fields = getColumnFields('Users');
 		$this->column_fields['currency_name'] = '';
 		$this->column_fields['currency_code'] = '';
@@ -259,9 +261,7 @@ class Users extends CRMEntity {
 	 * Contributor(s): ______________________________________..
 	 */
 	function encrypt_password($user_password, $crypt_type='') {
-		// encrypt the password.
-		$salt = substr($this->column_fields["user_name"], 0, 2);
-		//TODO : remove untill here in the next udpate
+		$salt = null; /* Recommended */
 
 		// Fix for: http://trac.vtiger.com/cgi-bin/trac.cgi/ticket/4923
 		if($crypt_type == '') {
@@ -269,19 +269,27 @@ class Users extends CRMEntity {
 			$crypt_type = $this->get_user_crypt_type();
 		}
 
-		// For more details on salt format look at: http://in.php.net/crypt
-		if($crypt_type == 'MD5') {
-			$salt = '$1$' . $salt . '$';
-		} elseif($crypt_type == 'BLOWFISH') {
-			$salt = '$2$' . $salt . '$';
-		} elseif($crypt_type == 'PHP5.3MD5') {
-			//only change salt for php 5.3 or higher version for backward
-			//compactibility.
-			//crypt API is lot stricter in taking the value for salt.
-			$salt = '$1$' . str_pad($salt, 9, '0');
+		if ($crypt_type != 'PHASH') {
+			/* Backward compatible for PHP < 5.5.0 */
+			// encrypt the password.
+			$salt = substr($this->column_fields["user_name"], 0, 2);
+			// For more details on salt format look at: http://in.php.net/crypt
+			if($crypt_type == 'MD5') {
+				$salt = '$1$' . $salt . '$';
+			} elseif($crypt_type == 'BLOWFISH') {
+				$salt = '$2$' . $salt . '$';
+			} elseif($crypt_type == 'PHP5.3MD5') {
+				//only change salt for php 5.3 or higher version for backward
+				//compactibility.
+				//crypt API is lot stricter in taking the value for salt.
+				$salt = '$1$' . str_pad($salt, 9, '0');
+			}
 		}
 
-		$encrypted_password = crypt($user_password, $salt);
+		$encrypted_password = ($crypt_type == 'PHASH') ?
+				password_hash($user_password, PASSWORD_DEFAULT) : /* recommended */
+				crypt($user_password, $salt); /* backward compatibility */
+
 		return $encrypted_password;
 	}
 
@@ -326,52 +334,28 @@ class Users extends CRMEntity {
 	 * @return true if the user is authenticated, false otherwise
 	 */
 	function doLogin($user_password) {
-		global $AUTHCFG;
 		$usr_name = $this->column_fields["user_name"];
 
-		switch (strtoupper($AUTHCFG['authType'])) {
-			case 'LDAP':
-				$this->log->debug("Using LDAP authentication");
-				require_once('modules/Users/authTypes/LDAP.php');
-				$result = ldapAuthenticate($this->column_fields["user_name"], $user_password);
-				if ($result == NULL) {
-					return false;
-				} else {
-					return true;
-				}
-				break;
-
-			case 'AD':
-				$this->log->debug("Using Active Directory authentication");
-				require_once('modules/Users/authTypes/adLDAP.php');
-				$adldap = new adLDAP();
-				if ($adldap->authenticate($this->column_fields["user_name"],$user_password)) {
-					return true;
-				} else {
-					return false;
-				}
-				break;
+		$query = "SELECT crypt_type, user_password, status, user_name FROM $this->table_name WHERE user_name=?";
+		$result = $this->db->requirePsSingleResult($query, array($usr_name), false);
+		if (empty($result)) {
+			return false;
+		}
+		$this->column_fields["user_name"] = $this->db->query_result($result, 0, 'user_name');
+		$crypt_type = $this->db->query_result($result, 0, 'crypt_type');
+		$user_status = $this->db->query_result($result, 0, 'status');
+		$dbuser_password = $this->db->query_result($result, 0, 'user_password');
 
-			default:
-				$this->log->debug("Using integrated/SQL authentication");
-				$query = "SELECT crypt_type, user_name FROM $this->table_name WHERE user_name=?";
-				$result = $this->db->requirePsSingleResult($query, array($usr_name), false);
-				if (empty($result)) {
-					return false;
-				}
-				$crypt_type = $this->db->query_result($result, 0, 'crypt_type');
-				$this->column_fields["user_name"] = $this->db->query_result($result, 0, 'user_name');
+		$ok = false;
+		if ($user_status == 'Active') {
+			if ($crypt_type == 'PHASH') {
+				$ok = password_verify($user_password, $dbuser_password);
+			} else {
 				$encrypted_password = $this->encrypt_password($user_password, $crypt_type);
-				$query = "SELECT 1 from $this->table_name where user_name=? AND user_password=? AND status = ?";
-				$result = $this->db->requirePsSingleResult($query, array($usr_name, $encrypted_password, 'Active'), false);
-				if (empty($result)) {
-					return false;
-				} else {
-					return true;
-				}
-				break;
+				$ok = ($dbuser_password == $encrypted_password);
+			}
 		}
-		return false;
+		return $ok;
 	}
 
 
@@ -406,8 +390,8 @@ class Users extends CRMEntity {
 		}
 
 		// Get the fields for the user
-		$query = "SELECT * from $this->table_name where user_name='$usr_name'";
-		$result = $this->db->requireSingleResult($query, false);
+		$query = "SELECT * from $this->table_name where user_name=?";
+		$result = $this->db->requireSingleResult($query, array($usr_name), false);
 
 		$row = $this->db->fetchByAssoc($result);
 		$this->column_fields = $row;
@@ -548,11 +532,16 @@ class Users extends CRMEntity {
 		$row = $this->db->fetchByAssoc($result);
 		$this->log->debug("select old password query: $query");
 		$this->log->debug("return result of $row");
-		$encryptedPassword = $this->encrypt_password($password, $row['crypt_type']);
-		if($encryptedPassword != $row['user_password']) {
-			return false;
+		switch ($row['crypt_type']) {
+			case 'PHASH': return password_verify($password, $row['user_password']);
+			default:
+				$encryptedPassword = $this->encrypt_password($password, $row['crypt_type']);
+				if($encryptedPassword == $row['user_password']) {
+					return true;
+				}
+				break;
 		}
-		return true;
+		return false;
 	}
 
 	function is_authenticated() {
diff --git a/vtlib/Vtiger/Functions.php b/vtlib/Vtiger/Functions.php
index 01788619a..3a03fe184 100644
--- a/vtlib/Vtiger/Functions.php
+++ b/vtlib/Vtiger/Functions.php
@@ -1062,7 +1062,12 @@ class Vtiger_Functions {
 	/*
 	 * Function to generate encrypted password.
 	 */
-	static function generateEncryptedPassword($password, $mode='CRYPT') {
+	static function generateEncryptedPassword($password, $mode='') {
+		if ($mode == '') {
+			$mode = (version_compare(PHP_VERSION, '5.5.0') >= 0)? 'PHASH' : 'CRYPT';
+		}
+
+		if ($mode == 'PHASH') return password_hash($password, PASSWORD_DEFAULT);
 
 		if ($mode == 'MD5') return md5($password);
 
@@ -1085,6 +1090,7 @@ class Vtiger_Functions {
 	static function compareEncryptedPassword($plainText, $encryptedPassword, $mode='CRYPT') {
 		$reEncryptedPassword = null;
 		switch ($mode) {
+			case 'PHASH': return password_verify($plainText, $encryptedPassword);
 			case 'CRYPT': $reEncryptedPassword = crypt($plainText, $encryptedPassword); break;
 			case 'MD5'  : $reEncryptedPassword = md5($plainText);	break;
 			default     : $reEncryptedPassword = $plainText;		break;
-- 
GitLab