<?php
/**
* LDAPEngine class
* @author Samuel Tran
* @version 04-24-2005
* @package LDAPEngine
*
* Copyright (C) 2005 - 2007 MailZu
* License: GPL, see LICENSE
*/
/**
* Base directory of application
*/
@define('BASE_DIR', dirname(__FILE__) . '/..');
/**
* CmnFns class
*/
include_once('CmnFns.class.php');


class LDAPEngine {

	// The directory server, tested with OpenLDAP and Active Directory
	var $serverType;

	// An array of server IP address(es) or hostname(s)
	var $hosts;
	// SSL support
	var $ssl;
	// The base DN (e.g. "dc=example,dc=org")
	var $basedn;
	// The user identifier (e.g. "uid")
	var $userIdentifier;

	/**
	/* The user to authenticate with when searching
	/* if anonymous binding is not supported
	/* (Active Directory doesn't support anonymous access by default)
	*/
	var $searchUser;
	/**
	/* The password to authenticate with when searching
	/* if anonymous binding is not supported
	/* (Active Directory doesn't support anonymous access by default)
	*/
	var $searchPassword;

	/**
	* Variable specific to Active Directory:
	* Active Directory authenticates using user@domain
	*/
	var $domain;

	/**
	* Variables specific to LDAP:
	* Container where the user records are kept
	*/
	var $userContainer;

	// The login attribute to authenticate with
	var $login;
	// The password attribute to authenticate with
	var $password;
	// The name attribute of the user
	var $name;
	/**
	* The mail attribute used as the recipient final address
	* Could be the actual mail attribute or another attribute
	* (in the latter case look for the "%m" token in the ldap query filter in amavisd.conf)
	*/
	var $mailAttr;

	// The last error code returned by the LDAP server
	var $ldapErrorCode;
	// Text of the error message
	var $ldapErrorText;


	// The internal LDAP connection handle
    	var $connection;
	// Result of any connection
	var $bind;
    	var $connected;
   
	// The user's logon name 
    	var $logonName;
	// The user's first name 
    	var $firstName;
	// The user's mail address ($mailAttr value)
    	var $emailAddress;

    
	/**
	* LDAPEngine constructor to initialize object
	*/
	function LDAPEngine() {
		global $conf;

		$this->serverType = strtolower($conf['auth']['serverType']);

		switch ($this->serverType) {
			case "ldap":
				$this->hosts = $conf['auth']['ldap_hosts'];
				$this->ssl = $conf['auth']['ldap_ssl'];
				$this->basedn = $conf['auth']['ldap_basedn'];
				$this->userIdentifier = $conf['auth']['ldap_user_identifier'];
				$this->userContainer = $conf['auth']['ldap_user_container'];
				$this->login = $conf['auth']['ldap_login'];
				$this->name = $conf['auth']['ldap_name'];
				$this->mailAttr = $conf['auth']['ldap_mailAttr'];
				$this->searchUser = $conf['auth']['ldap_searchUser'];
				$this->searchPassword = $conf['auth']['ldap_searchPassword'];
				break;
			case "ad":
				$this->hosts = $conf['auth']['ad_hosts'];
				$this->ssl = $conf['auth']['ad_ssl'];
				$this->basedn = $conf['auth']['ad_basedn'];
				$this->userIdentifier = $conf['auth']['ad_user_identifier'];
				$this->domain = $conf['auth']['ad_domain'];
				$this->login = $conf['auth']['ad_login'];
				$this->name = $conf['auth']['ad_name'];
				$this->mailAttr = $conf['auth']['ad_mailAttr'];
				$this->searchUser = $conf['auth']['ad_searchUser'];
				$this->searchPassword = $conf['auth']['ad_searchPassword'];
				break;
			default:
				CmnFns::do_error_box(translate('Unknown server type'), '', false);
		}

	}

	// Connection handling methods -------------------------------------------
    
    	/**
	* Makes a connection to the LDAP server.
	* Just creates a connection which is used in all later access.
	* If it can't connect and bind anonymously, it creates an error code of -1.
	* Returns true if connected, false if failed.
	* Takes an array of possible servers - if one doesn't work, it tries the next and so on.
	* @param none
	*/
    	function connect() {

		foreach ($this->hosts as $host) {
			$ldap_url = ( $this->ssl ? "ldaps://".$host : $host );
			$this->connection = ldap_connect($ldap_url);
			if ($this->connection) {
        			ldap_set_option($this->connection, LDAP_OPT_PROTOCOL_VERSION, 3);
				switch ($this->serverType) {
    					case "ad":
           					// Active Directory needs:
						ldap_set_option($this->connection, LDAP_OPT_REFERRALS, 0);
        					break;
    					default:
        					break;
				}
        			$this->connected = true;
                    		return true;
			} else {
				CmnFns::write_log('LDAP connection failed', '');
			}

            	}

		$this->ldapErrorCode = -1;
        	$this->ldapErrorText = "Unable to connect to any server";
        	$this->connected = false;
        	return false;
    	}

	/**
	* Disconnects from the LDAP server
	* @param none
	*/
    	function disconnect() {
        	if( $this->connected ) {
            		if ( ! @ldap_close( $this->connection ) ) {
				$this->ldapErrorCode = ldap_errno( $this->connection);
				$this->ldapErrorText = ldap_error( $this->connection);
				return false;
			} else {
            			$this->connected = false;
				return true;
			}
		}
		return true;
    	}

	/**
	* Anonymously binds to the connection. After this is done,
	* queries and searches can be done - but read-only.
	*/
	function anonBind() {
		if ( ! $this->bind = ldap_bind($this->connection) ) {
            		$this->ldapErrorCode = ldap_errno( $this->connection);
            		$this->ldapErrorText = ldap_error( $this->connection);
			CmnFns::write_log($this->ldapErrorCode . ': ' . $this->ldapErrorText, '');
            		return false;
        	} else {
            		return true;
        	}
	}

	/**
	* Binds to the directory with a specific username and password:
	* @param string $dn full LDAP dn or AD type 'user@domain'
	* @param string $password password
	* @return bool
	*/
	function authBind($username, $password) {
                if ( ! $this->bind = @ldap_bind($this->connection, $username, $password) ) {
			$this->ldapErrorCode = ldap_errno( $this->connection);
			$this->ldapErrorText = ldap_error( $this->connection);
			CmnFns::write_log($this->ldapErrorCode . ': ' . $this->ldapErrorText, '');
			return false;
		} else {
			return true;
		}
        }


	// User methods -------------------------------------------

    	/**
     	* Returns the full user DN to use, based on the ldap server type
	* @param string $userlogin
	* @return string
     	*/
    	function getUserDN($userlogin) {
		switch ($this->serverType) {
			case "ldap":
				if ( $this->userContainer != '' && $this->userIdentifier == $this->login ) { 
					// If a user container is specified and the login attribute is the same
					// as the user identifier attribute, build the user dn
	   				$dn = "$this->userIdentifier=$userlogin," . "$this->userContainer," . $this->basedn;
				} else {
					// Search for user dn
					$searchFilter = $this->login . "=" . $userlogin;
					$dn = $this->searchUserDN($searchFilter);
				}
				break;
			case "ad":
				if ( strtolower($this->login) == 'samaccountname' ) { 
					// If the user login attribute is 'samaccountname', build the user AD login
					$dn = $userlogin . "@" .  $this->domain;
				} else {
					// Search for user dn
					$searchFilter = $this->login . "=" . $userlogin;
					$dn = $this->searchUserDN($searchFilter);
				}
				break;
			default:
				CmnFns::do_error_box(translate('Unknown server type'), '', false);
		}
		return $dn;
	}

    	/**
     	* Returns the correct search base, based on the ldap server type
	* @param none
	* @return string
     	*/
    	function getSearchBase() {
		switch ($this->serverType) {
			case "ldap":
	   			$searchBase = ( $this->userContainer != '' ? "$this->userContainer," . $this->basedn 
						: $this->basedn );
				break;
			case "ad":
				$searchBase = $this->basedn;
				break;
			default:
				CmnFns::do_error_box(translate('Unknown server type'), '', false);
		}
		return $searchBase;
	}

    	/**
     	* Returns the correct user username that matches the search filter (array with single username)
	* If several usernames are found, return the array of usernames.
	* @param string $searchFilter search filter in a standard LDAP query
	* @return array
     	*/
    	function searchUserDN($searchFilter) {

		switch ($this->serverType) {
			case "ldap":
				if ( $this->searchUser != '' ) {
					// If a search user is defined bind with this user
					$this->authBind($this->searchUser, $this->searchPassword);
				} else {
					// Otherwise bind anonymously
					$this->anonBind();
				}
				break;
			case "ad":
				// if the directory is AD, then bind first with the search user
            			$this->authBind($this->searchUser, $this->searchPassword);
				break;
			default:
				CmnFns::do_error_box(translate('Unknown server type'), '', false);
		}

		$sr = ldap_search( $this->connection, $this->getSearchBase(), $searchFilter, array('dn'));
		$entries = ldap_get_entries( $this->connection, $sr);

		if ( $entries["count"] < 1 ) {
            		// If no results returned
            		$this->ldapErrorCode = -1;
            		$this->ldapErrorText = "No users found matching search criteria";
			CmnFns::write_log($this->ldapErrorCode . ': ' . $this->ldapErrorText, '');
        	} else {
			// The search should give an unique dn
			// If several results are found get the first one
			$dn = $entries[0]['dn'];
		}

        	return $dn;
    	}


	/**
	* Queries LDAP for user information
	* @param string $dn
	* @return boolean indicating success or failure
	*/
	function loadUserData($dn) {

		$this->emailAddress = array();

		// We are instered in getting just the user's first name and his/her mail attribute(s)
		$attributes = $this->mailAttr;
		array_push( $attributes, strtolower($this->name) );

		switch ($this->serverType) {
			case "ldap":
        			$result = ldap_search( $this->connection, $dn, "objectclass=*", $attributes );
				break;
			case "ad":
				if ( strtolower($this->login) == 'samaccountname' ) {
					// dn is of the form 'user@domain'
					list($samaccountname, $domain) = explode("@", $dn);
					$result = ldap_search( $this->connection, $this->getSearchBase(),
							$this->login . "=" . $samaccountname, $attributes );
				} else {
					// dn is standard LDAP dn
        				$result = ldap_search( $this->connection, $dn, "objectclass=*", $attributes );
				}
				break;
		}	

        	$entries = ldap_get_entries( $this->connection, $result );

        	if( $result and ( $entries["count"] > 0 ) ) {        
			// The search should give a single entry
			// If several results are found get the first entry
            		$this->firstName = $entries[0][strtolower($this->name)][0];
			foreach ( $this->mailAttr as $value ) {
				// For single value or multiple value attribute
				for ($i=0; $i<$entries[0][strtolower($value)]["count"]; $i++) {
					# AD proxyAddresses attribute values have 'smtp:' string before the actual email address
					if(preg_match("/^smtp:/i", strtolower($entries[0][strtolower($value)][$i])) == 1) {
            					array_push( $this->emailAddress, preg_replace("/^\w+:/", '', strtolower($entries[0][strtolower($value)][$i])) );
					} else {
						array_push( $this->emailAddress, strtolower($entries[0][strtolower($value)][$i]) );
					}
				}
			}
        	} else {    
            		// If no results returned
            		$this->ldapErrorCode = -1;
            		$this->ldapErrorText = "No entry found matching search criteria";
			CmnFns::write_log($this->ldapErrorCode . ': ' . $this->ldapErrorText, '');
            		return false;
       		}
	
	   	return true;	
	}


	// Helper methods -------------------------------------------

    	/**
        * Returns user information
        * @return array containing user information
        */
	function getUserData() {
        	$return = array(
            			'logonName' => $this->logonName,
            			'firstName' => $this->firstName,
            			'emailAddress' => $this->emailAddress
        	);
        	return $return;
    	}

	
}
?>