From f050f12663a046feafca8a75dbd6a4e40d687796 Mon Sep 17 00:00:00 2001
From: prasad <prasad@vtiger.com>
Date: Fri, 17 Jun 2016 12:14:11 +0530
Subject: [PATCH] Adopting php crypt mode for portal password instead of md5.

---
 modules/Contacts/ContactsHandler.php    |  6 ++---
 modules/Migration/schema/640_to_650.php | 15 +++++++++++
 schema/DatabaseSchema.xml               |  2 +-
 soap/customerportal.php                 | 34 +++++++++++++++++------
 vtlib/Vtiger/Functions.php              | 36 +++++++++++++++++++++++--
 5 files changed, 79 insertions(+), 14 deletions(-)

diff --git a/modules/Contacts/ContactsHandler.php b/modules/Contacts/ContactsHandler.php
index 000f807e5..815c09b2e 100644
--- a/modules/Contacts/ContactsHandler.php
+++ b/modules/Contacts/ContactsHandler.php
@@ -40,15 +40,15 @@ function Contacts_sendCustomerPortalLoginDetails($entityData){
 			}
 		}
 		$password = makeRandomPassword();
-		$md5_password = md5($password);
+		$enc_password = Vtiger_Functions::generateEncryptedPassword($password);
 		if ($insert == true) {
 			$sql = "INSERT INTO vtiger_portalinfo(id,user_name,user_password,cryptmode,type,isactive) VALUES(?,?,?,?,?,?)";
-			$params = array($entityId, $email, $md5_password, 'MD5', 'C', 1);
+			$params = array($entityId, $email, $enc_password, 'CRYPT', 'C', 1);
 			$adb->pquery($sql, $params);
 		}
 		if ($update == true && $portalChanged == true) {
 			$sql = "UPDATE vtiger_portalinfo SET user_password=?, cryptmode=? WHERE id=?";
-			$params = array($md5_password, 'MD5', $entityId);
+			$params = array($enc_password, 'CRYPT', $entityId);
 			$adb->pquery($sql, $params);
 		}
 		if (($insert == true || ($update = true && $portalChanged == true)) && $entityData->get('emailoptout') == 0) {
diff --git a/modules/Migration/schema/640_to_650.php b/modules/Migration/schema/640_to_650.php
index 206bd8cf9..fe46fb268 100644
--- a/modules/Migration/schema/640_to_650.php
+++ b/modules/Migration/schema/640_to_650.php
@@ -14,8 +14,23 @@ if(defined('VTIGER_UPGRADE')) {
 global $adb;
 
 Vtiger_Utils::AddColumn('vtiger_portalinfo', 'cryptmode', 'varchar(20)');
+$adb->pquery("ALTER TABLE vtiger_portalinfo MODIFY COLUMN user_password varchar(255)", array());
 
 //Updating existing users password to thier md5 hash
+$portalinfo_hasmore = true;
+do {
+	$result = $adb->pquery('SELECT id, user_password FROM vtiger_portalinfo WHERE cryptmode is null limit 1000', array());
+	
+	$portalinfo_hasmore = false; // assume we are done.
+	while ($row = $adb->fetch_array($result)) {
+		$portalinfo_hasmore = true; // we found at-least one so there could be more.
+		
+		$enc_password = Vtiger_Functions::generateEncryptedPassword(decode_html($row['user_password']));
+		$adb->pquery('UPDATE vtiger_portalinfo SET user_password=?, cryptmode = ? WHERE id=?', array($enc_password, 'CRYPT', $row['id']));
+	}
+	
+} while ($portalinfo_hasmore);
+
 $updateQuery = "UPDATE vtiger_portalinfo SET user_password=MD5(user_password),cryptmode='MD5' WHERE cryptmode is null";
 $adb->pquery($updateQuery, array());
 
diff --git a/schema/DatabaseSchema.xml b/schema/DatabaseSchema.xml
index f9a3b22c0..2aff13975 100644
--- a/schema/DatabaseSchema.xml
+++ b/schema/DatabaseSchema.xml
@@ -588,7 +588,7 @@
 			<key />
 		</field>
 		<field name="user_name" type="C" size="50" />
-		<field name="user_password" type="C" size="30" />
+		<field name="user_password" type="C" size="255" />
 		<field name="type" type="C" size="5" />
 		<field name="cryptmode" type="C" size="20" />
 		<field name="last_login_time" type="T" />
diff --git a/soap/customerportal.php b/soap/customerportal.php
index 77f640a5b..12d8f47fe 100755
--- a/soap/customerportal.php
+++ b/soap/customerportal.php
@@ -18,6 +18,7 @@ if (file_exists('config_override.php')) {
 }
 
 include_once 'vtlib/Vtiger/Module.php';
+include_once 'vtlib/Vtiger/Functions.php';
 include_once 'includes/main/WebUI.php';
 
 require_once('libraries/nusoap/nusoap.php');
@@ -996,23 +997,33 @@ function authenticate_user($username,$password,$version,$login = 'true')
 	$password = $adb->sql_escape_string($password);
 
 	$current_date = date("Y-m-d");
-	$sql = "select id, user_name, user_password,last_login_time, support_start_date, support_end_date
+	$sql = "select id, user_name, user_password,last_login_time, support_start_date, support_end_date, cryptmode
 				from vtiger_portalinfo
 					inner join vtiger_customerdetails on vtiger_portalinfo.id=vtiger_customerdetails.customerid
 					inner join vtiger_crmentity on vtiger_crmentity.crmid=vtiger_portalinfo.id
-				where vtiger_crmentity.deleted=0 and user_name=? and user_password = ?
+				where vtiger_crmentity.deleted=0 and user_name=?
 					and isactive=1 and vtiger_customerdetails.portal=1
 					and vtiger_customerdetails.support_start_date <= ? and vtiger_customerdetails.support_end_date >= ?";
-	$result = $adb->pquery($sql, array($username, $password, $current_date, $current_date));
+	$result = $adb->pquery($sql, array($username, $current_date, $current_date));
 	$err[0]['err1'] = "MORE_THAN_ONE_USER";
 	$err[1]['err1'] = "INVALID_USERNAME_OR_PASSWORD";
 
 	$num_rows = $adb->num_rows($result);
 
-	if($num_rows > 1)		return $err[0];//More than one user
-	elseif($num_rows <= 0)		return $err[1];//No user
+	if($num_rows <= 0)		return $err[1];//No user
 
-	$customerid = $adb->query_result($result,0,'id');
+	// Match password against multiple user and decide.
+	$customerid = null;
+	for ($i = 0; $i < $num_rows; ++$i) {
+		$customerid = $adb->query_result($result, $i,'id');
+		if (Vtiger_Function::compareEncryptedPassword($password, $adb->query_result($result, $i, 'id'), $adb->query_result($result, $i, 'cryptmode'))) {
+			break;
+		} else {
+			$customerid = null;
+		}
+	}
+
+	if (!$customerid) return $err[1];//No user again.
 
 	$list[0]['id'] = $customerid;
 	$list[0]['user_name'] = $adb->query_result($result,0,'user_name');
@@ -1064,8 +1075,8 @@ function change_password($input_array)
 	if(!empty($list[0]['id'])){
 		return array('MORE_THAN_ONE_USER');
 	}
-	$sql = "update vtiger_portalinfo set user_password=? where id=? and user_name=?";
-	$result = $adb->pquery($sql, array(md5($password), $id, $username));
+	$sql = "update vtiger_portalinfo set user_password=?, cryptmode=? where id=? and user_name=?";
+	$result = $adb->pquery($sql, array(Vtiger_Functions::generateEncryptedPassword($password), 'CRYPT', $id, $username));
 
 	$log->debug("Exiting customer portal function change_password");
 	return $list;
@@ -1123,6 +1134,13 @@ function send_mail_for_password($mailid)
 	$password = $adb->query_result($res,0,'user_password');
 	$isactive = $adb->query_result($res,0,'isactive');
 
+	// We no longer have the original password!
+	if (!empty($adb->query_result($res, 0, 'cryptmode'))) {
+		$password = '*****';
+		// TODO - we need to send link to reset the password
+		// For now CRM user can do the same.
+	}
+
 	$fromquery = "select vtiger_users.user_name, vtiger_users.email1 from vtiger_users inner join vtiger_crmentity on vtiger_users.id = vtiger_crmentity.smownerid inner join vtiger_contactdetails on vtiger_contactdetails.contactid=vtiger_crmentity.crmid where vtiger_contactdetails.email =?";
 	$from_res = $adb->pquery($fromquery, array($mailid));
 	$initialfrom = $adb->query_result($from_res,0,'user_name');
diff --git a/vtlib/Vtiger/Functions.php b/vtlib/Vtiger/Functions.php
index a3e5f8eb2..f0b1c6602 100644
--- a/vtlib/Vtiger/Functions.php
+++ b/vtlib/Vtiger/Functions.php
@@ -969,6 +969,38 @@ class Vtiger_Functions {
      return false; 
     }
     return true;
-    } 
-    
+	}
+
+	/*
+	 * Function to generate encrypted password.
+	 */
+	static function generateEncryptedPassword($password, $mode='CRYPT') {
+
+		if ($mode == 'MD5') return md5($password);
+
+		if ($mode == 'CRYPT') {
+			$salt = null;
+			if (function_exists('password_hash')) { // php 5.5+
+				$salt = password_hash();
+			} else {
+				$salt = '$2y$11$'.str_replace("+",".",substr(base64_encode(openssl_random_pseudo_bytes(17)),0,22));
+			}
+			return crypt($password, $salt);
+		}
+
+		throw new Exception('Invalid encryption mode: '.$mode);
+	}
+
+	/*
+	 * Function to compare encrypted password.
+	 */
+	static function compareEncryptedPassword($plainText, $encryptedPassword, $mode='CRYPT') {
+		$reEncryptedPassword = null;
+		switch ($mode) {
+			case 'CRYPT': $reEncryptedPassword = crypt($plainText, $encryptedPassword); break;
+			case 'MD5'  : $reEncryptedPassword = md5($plainText);
+			default     : $reEncryptedPassword = $plainText;
+		}
+		return ($reEncryptedPassword == $encryptedPassword);
+	}
 }
-- 
GitLab