<?php 
/**
* @version		$Id: ldap.auth.php 2569 2024-05-02 18:34:42Z IOS $
* @package		Elxis
* @subpackage	Component User / Authentication
* @copyright	Copyright (c) 2006-2024 Elxis CMS (https://www.elxis.org). All rights reserved.
* @license		Elxis Public License ( https://www.elxis.org/elxis-public-license.html )
* @author		Elxis Team ( https://www.elxis.org )
* @description 	Elxis CMS is free software. Read the license for copyright notices and details
*/

defined('_ELXIS_') or die ('Direct access to this location is not allowed');


class ldapAuthentication {

	private $host = '';
	private $port = 389;
	private $ldap_version = 3;
	private $tls = 0;
	private $referrals = 0;
	private $base_dn = '';
	private $user_dn = '';
	private $ldap_filter = '';
	private $ldap_firstname = '';
	private $ldap_lastname = '';
	private $ldap_fullname = '';
	private $ldap_email = '';
	private $ldap_username = '';
	private $ad_errormsg = '';


	/********************/
	/* MAGIC CONTRUCTOR */
	/********************/
	public function __construct($params) {
		$this->host = trim($params->get('host', ''));
		$this->port = (int)$params->get('port', 389);
		$this->ldap_version = (int)$params->get('ldap_version', 3);
		$this->tls = (int)$params->get('tls', 0);
		$this->referrals = (int)$params->get('referrals', 0);
		$this->base_dn = trim($params->get('base_dn', ''));
		$this->user_dn = trim($params->get('user_dn', ''));
		$this->ldap_filter = trim($params->get('ldap_filter', ''));
		$this->ldap_firstname = trim($params->get('ldap_firstname', ''));
		$this->ldap_lastname = trim($params->get('ldap_lastname', ''));
		$this->ldap_fullname = trim($params->get('ldap_fullname', ''));
		$this->ldap_email = trim($params->get('ldap_email', ''));
		$this->ldap_username = trim($params->get('ldap_username', ''));
	}


	/*****************************/
	/* AUTHENTICATE AN LDAP USER */
	/*****************************/
	public function authenticate(&$response, $options=array()) {
		$elxis = eFactory::getElxis();

		$uname = (isset($options['uname'])) ? trim($options['uname']) : '';
		$pword = (isset($options['pword'])) ? trim($options['pword']) : '';
		if ($uname == '') {
			$eLang = eFactory::getLang();
			$response->errormsg = sprintf($eLang->get('FIELDNOEMPTY'), $eLang->get('USERNAME'));
			return false;
		}
		if ($pword == '') {
			$eLang = eFactory::getLang();
			$response->errormsg = sprintf($eLang->get('FIELDNOEMPTY'), $eLang->get('PASSWORD'));
			return false;
		}

		if (strlen($pword) < 3) {
			$response->errormsg = eFactory::getLang()->get('PASSTOOSHORT');
			return false;
		}

		if (!function_exists('ldap_connect')) {
			$response->errormsg = 'LDAP is not supported by the web server!';
			return false;
		}

		$uname_san = $elxis->sanitizeVar($uname, true, false);
		$pword_san = $elxis->sanitizeVar($pword, true, false);
		if ($uname !== $uname_san) {
			$eLang = eFactory::getLang();
			$response->errormsg = sprintf($eLang->get('FIELDNOACCCHAR'), $eLang->get('USERNAME'));
			return false;
		}
		if ($pword !== $pword_san) {
			$eLang = eFactory::getLang();
			$response->errormsg = sprintf($eLang->get('FIELDNOACCCHAR'), $eLang->get('PASSWORD'));
			return false;
		}

		if ($elxis->getConfig('TWOFA') != '') {
			$response->errormsg = '2 Factor authentication is not supported for LDAP login!';
			return false;
		}

		$udata = $this->ldapUser($uname, $pword);
		if (($udata === false) || (!is_object($udata))) {
			$response->errormsg = ($this->ad_errormsg != '') ? $this->ad_errormsg : eFactory::getLang()->get('AUTHFAILED');
			return false;
		}

		$response->uname = (trim($udata->uname) != '') ? $udata->uname : $uname;
		$response->firstname = $udata->firstname;
		$response->lastname = $udata->lastname;
		$response->email = $udata->email;
		return true;
	}


	/**********************************************/
	/* MAKE ONE TIME PASSWORD (VERIFICATION CODE) */
	/**********************************************/
	public function makeOTP($options=array()) {
		$iresponse = array('success' => false, 'errormsg' => '', 'firstname' => '', 'lastname' => '', 'uname' => '', 'gid' => '', 'email' => '', 'mobile' => '', 'otp' => '');

		$iresponse['errormsg'] = '2 factor authentication is not supported by LDAP login!';
		return $iresponse;
	}


	/************************/
	/* SHOW LDAP LOGIN FORM */
	/************************/
	public function loginForm() {
		$eLang = eFactory::getLang();
		$elxis = eFactory::getElxis();
		$eDoc = eFactory::getDocument();
		$eAuth = eRegistry::get('eAuth');

		elxisLoader::loadFile('includes/libraries/elxis/form5.class.php');

		$action = $elxis->makeURL('user:login/ldap.html', '', true, false);
		$return = base64_encode($elxis->makeURL('user:/'));

		$data = $eAuth->getAuthData('ldap');
		$title = sprintf($eLang->get('LOGIN_WITH'), $data['title']);
		$eDoc->setTitle($eLang->get('LOGIN').' - '.$data['title']);
		$eDoc->setDescription($title);

		$form = new elxis5Form(array('idprefix' => 'ulog', 'labelclass' => 'elx5_labelblock', 'sideclass' => 'elx5_zero'));
		$form->openForm(array('name' => 'fmuserlogin', 'method' => 'post', 'action' => $action, 'id' => 'fmuserlogin'));
		$form->openFieldset($title);
		$form->addText('uname', '', $eLang->get('USERNAME'), array('required' => 'required', 'autocomplete' => 'off'));
		$form->addPassword('pword', '', $eLang->get('PASSWORD'), array('required' => 'required', 'maxlength' => 60, 'autocomplete' => 'off'));
		$form->addHidden('return', $return);
		$form->addHidden('auth_method', 'ldap');
		$form->addToken('fmuserlogin');
		$form->addHTML('<div class="elx5_dspace">');
		$form->addButton('sbmlog', $eLang->get('LOGIN'), 'submit');
		$form->addHTML('</div>');
		$form->closeFieldset();
		$form->closeForm();
		unset($form);
	}


	/***********************/
	/* EXECUTE CUSTOM TASK */
	/***********************/
	public function runTask($etask) {
		if (ob_get_length() > 0) { @ob_end_clean(); }
		header('content-type:text/plain; charset=utf-8');
		echo 'Invalid request';
		exit;
	}


	/***************************************/
	/* CUSTOM ACTIONS TO PERFORM ON LOGOUT */
	/***************************************/
	public function logout() {
	}


	/*******************************************************************/
	/* AUTHENTICATE USER TO LDAP HOST AND GET ACCOUNT INFORMATION DATA */
	/*******************************************************************/
	private function ldapUser($username, $password) {
		if ($this->host == '') {
			$this->ad_errormsg = 'LDAP host can not be empty!';
			return false;
		}

        if ($this->port < 1) {
        	$this->port = (preg_match('/^(ldaps)/i', $this->host)) ? 636 : 389;
		}

		$this->user_dn = str_replace('{username}', $username, $this->user_dn);
		$this->user_dn = str_replace('{password}', $password, $this->user_dn);

		$adconn = @ldap_connect($this->host, $this->port);
		if (!$adconn) {
			$this->ad_errormsg = 'Could not connect to LDAP host!';
			return false;
		}
		
		if ($this->ldap_version !== 2) {
			if (!@ldap_set_option($adconn, LDAP_OPT_PROTOCOL_VERSION, 3)) {
				ldap_close($adconn);
				$this->ad_errormsg = 'Could not set LDAP protocol!';
				return false;
			}
		}

        @ldap_set_option($adconn, LDAP_OPT_REFERRALS, $this->referrals);
  
        if ($this->tls == 1) {
            if (!@ldap_start_tls($adconn)) {
            	ldap_close($adconn);
            	$this->ad_errormsg = 'Could not start TLS';
            	return false;
            }
        }

		if ($this->base_dn == '') { $this->base_dn = $this->getBaseDN($adconn, $this->host); }
		if ($this->user_dn == '') { $this->user_dn = $this->getUserDN($adconn, $this->base_dn, $username, $this->ldap_username); }

		$bind = @ldap_bind($adconn, $this->user_dn, $password);
        if (!$bind) {
        	$err = @ldap_error($adconn);
        	$this->ad_errormsg = eFactory::getLang()->get('AUTHFAILED').'. '.$err;
            ldap_close($adconn);
			return false;
		}

		if ($this->ldap_filter != '') {
			$this->ldap_filter = str_replace('{username}', $username, $this->ldap_filter);
		} else if ($this->ldap_username != '') {
			$this->ldap_filter = '(|(cn='.$username.')('.$this->ldap_username.'='.$username.'))';
		} else {
			$this->ldap_filter = '(|(samaccountname='.$username.')(uid='.$username.')(mail='.$username.'))';
		}

		$attributes = array();
		if ($this->ldap_username != '') { $attributes[] = $this->ldap_username; }
		if ($this->ldap_firstname != '') { $attributes[] = $this->ldap_firstname; }
		if ($this->ldap_lastname != '') { $attributes[] = $this->ldap_lastname; }
		if ($this->ldap_fullname != '') { $attributes[] = $this->ldap_fullname; }
		if ($this->ldap_email != '') {
			if (strpos($this->ldap_email, '{') === false) { $attributes[] = $this->ldap_email; }
		}

		if (!isset($attributes['firstname'])) { $attributes[] = 'firstname'; }
		if (!isset($attributes['lastname'])) { $attributes[] = 'lastname'; }
		if (!isset($attributes['fullname'])) { $attributes[] = 'fullname'; }
		if (!isset($attributes['givenname'])) { $attributes[] = 'givenname'; }
		if (!isset($attributes['mail'])) { $attributes[] = 'mail'; }
		if (!isset($attributes['email'])) { $attributes[] = 'email'; }
		if (!isset($attributes['sn'])) { $attributes[] = 'sn'; }
		if (!isset($attributes['cn'])) { $attributes[] = 'cn'; }
		if (!isset($attributes['samaccountname'])) { $attributes[] = 'samaccountname'; }

		$result = ldap_search($adconn, $this->base_dn, $this->ldap_filter, $attributes);
		$entries = ldap_get_entries($adconn, $result);
		ldap_close($adconn);

		$udata = new stdClass;
		$udata->uname = null;
		$udata->firstname = null;
		$udata->lastname = null;
		$udata->fullname = null;
		$udata->email = null;

		if (strpos($this->ldap_email, '{') !== false) {
			$udata->email = str_replace('{username}', $username, $this->ldap_email);
		} elseif ($entries && ($entries["count"] > 0)) {
			if (($this->ldap_email != '') && isset($entries[0][ $this->ldap_email ][0])) {
				$udata->email = $entries[0][ $this->ldap_email ][0];
			} elseif (isset($entries[0]['mail'][0])) {
				$udata->email = $entries[0]['mail'][0];
			} elseif (isset($entries[0]['email'][0])) {
				$udata->email = $entries[0]['email'][0];
			}
		}

		if ($entries && ($entries["count"] > 0)) {
			if (($this->ldap_firstname != '') && isset($entries[0][ $this->ldap_firstname ][0])) {
				$udata->firstname = $entries[0][ $this->ldap_firstname ][0];
			} elseif (isset($entries[0]['firstname'][0])) {
				$udata->firstname = $entries[0]['firstname'][0];
			} elseif (isset($entries[0]['givenname'][0])) {
				$udata->firstname = $entries[0]['givenname'][0];
			}
		}

		if ($entries && ($entries["count"] > 0)) {
			if (($this->ldap_lastname != '') && isset($entries[0][ $this->ldap_lastname ][0])) {
				$udata->lastname = $entries[0][ $this->ldap_lastname ][0];
			} elseif (isset($entries[0]['lastname'][0])) {
				$udata->lastname = $entries[0]['lastname'][0];
			} elseif (isset($entries[0]['sn'][0])) {
				$udata->lastname = $entries[0]['sn'][0];
			}
		}

		if ($entries && ($entries["count"] > 0)) {
			if (($this->ldap_fullname != '') && isset($entries[0][ $this->ldap_fullname ][0])) {
				$udata->fullname = $entries[0][ $this->ldap_fullname ][0];
			} elseif (isset($entries[0]['fullname'][0])) {
				$udata->fullname = $entries[0]['fullname'][0];
			} elseif (isset($entries[0]['cn'][0])) {
				$udata->fullname = $entries[0]['cn'][0];
			}
		}

		if ($entries && ($entries["count"] > 0)) {
			if (($this->ldap_username != '') && isset($entries[0][ $this->ldap_username ][0])) {
				$udata->uname = $entries[0][ $this->ldap_username ][0];
			} elseif (isset($entries[0]['samaccountname'][0])) {
				$udata->uname = $entries[0]['samaccountname'][0];
			}
		}

		if (trim($udata->uname) == '') {
			$parts = preg_split('/\@/', $username, 2, PREG_SPLIT_NO_EMPTY);
			$udata->uname = $parts[0];
		}

		if ((trim($udata->firstname) == '') && (trim($udata->fullname) != '')) {
			$parts = preg_split('/[\s]/', $udata->fullname, 2, PREG_SPLIT_NO_EMPTY);
			if ($parts) {
				$udata->firstname = $parts[0];
				if (trim($udata->lastname) == '') {
					if (isset($parts[1]) && (trim($parts[1]) != '')) {
						$udata->lastname = $parts[1];
					}
				}
			}
		}

		if (trim($udata->firstname) == '') { $udata->firstname = $udata->uname; }

		return $udata;
	}


	/****************/
	/* FIND USER DN */
	/****************/
	private function getUserDN($adconn, $base_dn, $username, $ldap_username) {
		if ($ldap_username == '') { $ldap_username = 'uid'; }
		if (!@ldap_bind($adconn)) { return false; }
		$res_id = @ldap_search($adconn, $base_dn, $ldap_username.'='.$username);
		if (!$res_id) { return false; }
		if (ldap_count_entries($adconn, $res_id) <> 1) { return false; }
		$entry_id = @ldap_first_entry($adconn, $res_id);
		if (!$entry_id) { return false; }
		$user_dn = ldap_get_dn($adconn, $entry_id);
		return $user_dn;
	}


	/*************************************/
	/* FIND BASE DN OF DOMAIN CONTROLLER */
	/*************************************/
    private function getBaseDN($adconn, $host) {
        $sr = @ldap_read($adconn, NULL, 'objectClass=*', array('defaultnamingcontext'));
        if (!$sr) {	return $this->getBaseDNfromHost($host); }
        $namingContext = @ldap_get_entries($adconn, $sr);
		if (isset($namingContext[0]['defaultnamingcontext'][0])) {
			return $namingContext[0]['defaultnamingcontext'][0];
		} else if (isset($namingContext[0]['dn'])) {
			return (is_array($namingContext[0]['dn'])) ? $namingContext[0]['dn'][0] : $namingContext[0]['dn'];
		} else {
			return $this->getBaseDNfromHost($host);
		}
    }


	/************************************/
	/* GUESS BASE DN FROM LDAP HOSTNAME */
	/************************************/
	private function getBaseDNfromHost($host) {
		$parts = preg_split('/\./', $host, -1, PREG_SPLIT_NO_EMPTY);
		if ($parts) {
			$c = count($parts) - 1;
			$out = 'dc='.$parts[$c];
			$c = $c - 1;
			if (isset($parts[$c])) {
				$out = 'dc='.$parts[$c].','.$out;
			}
			return $out;
		}
		return null;
	}

}

?>