<?php /** * IML LDAP CONNECTOR FOR USER AUTHENTICATION * * @author axel.hahn@iml.unibe.ch */ class imlldap { private $_aLdap = array( 'server' => false, 'port' => false, 'DnLdapUser' => false, // ldap rdn oder dn 'PwLdapUser' => false, 'DnUserNode' => false, // ou=People... 'DnAppNode' => false, // cn=AppGroup... 'protoVersion' => 3, 'debugLevel' => 0, ); private $_ldapConn = false; private $_ldapBind = false; var $bDebug = false; /** * constructor * @param array $aConfig optional set ldap connection */ public function __construct($aConfig = array()) { if (!function_exists("ldap_connect")){ die(__CLASS__ . " ERROR: php-ldap module is not installed on this server."); } if (count($aConfig)) { $this->setConfig($aConfig); } } public function __destruct() { $this->close(); } // ---------------------------------------------------------------------- // write debug text // ---------------------------------------------------------------------- /** * turn debug messages on; * if this detail level is not enough, set a value with key debugLevel in * ldap config array * @see setConfig() */ public function debugOn(){ $this->bDebug=true; if($this->_aLdap['debugLevel']){ $this->_w(__FUNCTION__ . ' setting debug level ' . $this->_aLdap['debugLevel']); ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, $this->_aLdap['debugLevel']); } } /** * turn debug messages off */ public function debugOff(){ $this->bDebug=false; ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, 0); } private function _w($sText) { if (!$this->bDebug) { return false; } echo __CLASS__ . ' DEBUG: ' . $sText . "<br>\n"; return true; } // ---------------------------------------------------------------------- // setup // ---------------------------------------------------------------------- /** * set a ldap config * * @param array $aConfig new config items * 'server' => 'ldaps://ldap.example.com', * 'port' => 636, * 'DnLdapUser' => 'cn=Lookup,ou=ServiceAccounts,dc=org,dc=example.com', // ldap rdn oder dn * 'PwLdapUser' => 'IkHEFFzlZ...99j0h8WdI0LrLhxU', // password * 'DnUserNode' => 'ou=People,ou=ORG,dc=org,dc=example.com', * 'DnAppNode' => '' optional dn ... if a user must be member of a given group * 'protoVersion' => 3 * 'debugLevel' => 0 // for debugging set higher 0 AND call debugOn() */ public function setConfig($aConfig = array()) { if (is_array($aConfig)) { foreach (array_keys($this->_aLdap) as $sKey) { if (array_key_exists($sKey, $aConfig)) { $this->_w(__FUNCTION__ . ' setting ldap '.$sKey.' = '. $aConfig[$sKey]); $this->_aLdap[$sKey] = $aConfig[$sKey]; } } } } // ---------------------------------------------------------------------- // ldap lowlevel functions // ---------------------------------------------------------------------- /** * close an existing ldap connection */ public function close() { if ($this->_ldapConn) { $this->_w(__FUNCTION__ . ' closing connection.'); ldap_close($this->_ldapConn); } else { $this->_w(__FUNCTION__ . ' SKIP close.'); } $this->_ldapConn = false; } /** * connect to ldap */ public function connect() { if (!array_key_exists('server', $this->_aLdap) || !$this->_aLdap['server']) { die(__CLASS__ . " ERROR: no ldap server was setup set. Use setConfig() first."); } if ($this->_ldapConn) { $this->close(); } $this->_w(__FUNCTION__ . ' connect to ' . $this->_aLdap['server'] . ':' . $this->_aLdap['port']); $this->_ldapConn = ldap_connect($this->_aLdap['server'], $this->_aLdap['port']); if (!$this->_ldapConn) { die(__CLASS__ . " ERROR: ldap connect failed."); } $this->_w(__FUNCTION__ . ' OK, connected.'); if($this->_aLdap['protoVersion']){ $this->_w(__FUNCTION__ . ' setting protocol version .' . $this->_aLdap['protoVersion']); ldap_set_option($this->_ldapConn, LDAP_OPT_PROTOCOL_VERSION, $this->_aLdap['protoVersion']); } ldap_set_option($this->_ldapConn, LDAP_OPT_REFERRALS, 0); // for AD MS Windows } /** * ldap bind connects with a ldap user. * If the ldap connection was not opened yet the connection will be established. * If a binding exists it will be unbind * * @see connect() * @see unbind() * * @param string $sUser username * @param string $sPw password */ public function bind($sUser, $sPw='') { if(!$this->_ldapConn){ $this->connect(); } if($this->_ldapBind){ $this->unbind(); } if(!$sUser){ $this->_w(__FUNCTION__ . ' ERROR: no user was set as first param.'); die("ERROR: no user was given to connect to ldap."); } $this->_w(__FUNCTION__ . ' with user ' . $sUser. ' PW '.$sPw); $this->_ldapBind = @ldap_bind($this->_ldapConn, $sUser, $sPw); if (!$this->_ldapBind) { $this->_w(__FUNCTION__ . ' failed with er error ' . ldap_error($this->_ldapConn)); return false; } $this->_w(__FUNCTION__ . ' OK, successful.'); return true; } /** * ldap unbind ... if a bind exists */ public function unbind() { if ($this->_ldapBind && !is_bool($this->_ldapBind)) { $this->_w(__FUNCTION__ . ' ...'); ldap_unbind($this->_ldapBind); } else { $this->_w(__FUNCTION__ . ' SKIP.'); } $this->_ldapBind = false; } // ---------------------------------------------------------------------- // ldap highlevel functions // ---------------------------------------------------------------------- /** * check if a DN already exists; return is true/ false * @param string $sDn DN to check * @return boolean */ public function DnExists($sDn) { $aData=$this->searchDn($sDn, '(&(objectclass=top))', $aAttributesToGet=array("*")); return is_array($aData); } /** * search in ldap directory and get result as array * * @param string $sSearchFilter filter in ldap filter syntax * @param array $aAttributesToGet flat array of attributes to fetch * @return array */ public function searchDn($sDn, $sSearchFilter, $aAttributesToGet=array("*")) { if (!$this->_ldapBind) { $this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser']); } $this->_w(__FUNCTION__ . ' DN = '.$sDn . ' filter = '.$sSearchFilter); $oLdapSearch = ldap_search( $this->_ldapConn, $sDn, $sSearchFilter, $aAttributesToGet ); $aItems = $oLdapSearch?ldap_get_entries($this->_ldapConn, $oLdapSearch):false; return $aItems; } /** * search in ldap directory and get result as array * * @param string $sSearchFilter filter in ldap filter syntax * @param array $aAttributesToGet flat array of attributes to fetch * @return array */ public function searchUser($sSearchFilter, $aAttributesToGet=array("*")) { if (!$this->_ldapBind) { $this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser']); } $this->_w(__FUNCTION__ . ' DN = '.$this->_aLdap['DnUserNode'] . ' filter = '.$sSearchFilter); $oLdapSearch = ldap_search( $this->_ldapConn, $this->_aLdap['DnUserNode'], $sSearchFilter, $aAttributesToGet ); $aItems = $oLdapSearch?ldap_get_entries($this->_ldapConn, $oLdapSearch):false; return $aItems; } /** * search user by a given username or email address. * It returns false if the user does not exist or is * not member of the group 'DnAppNode' (if it was set). * * @param type $sUser user id or email to search * @param type $aAttributesToGet i.e. array("ou", "sn", "vorname", "mail", "uid", "memberOf") * @return boolean */ public function getUserInfo($sUser, $aAttributesToGet=array("*")) { if (!$this->_ldapBind) { $this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser']); } // generate search filter $sSearchFilter = (strpos($sUser, '@'))?"(mail=$sUser)" : "(uid=$sUser)"; if($this->_aLdap['DnAppNode']){ $sSearchFilter.='(memberof='.$this->_aLdap['DnAppNode'] .')'; } $sSearchFilter='(&'.$sSearchFilter.')'; $aItems = $this->searchUser($sSearchFilter, $aAttributesToGet); if(count($aItems)==2){ $this->_w(__FUNCTION__ . ' OK: I got a single result: ' . print_r($aItems[0],1) ); return $aItems[0]; } return false; } /** * search for a DN entry with the lookup user by a given username or * email address. It returns false if the user does not exist or is * not member of the group 'DnAppNode' (if it was set). * * @param type $sUser * @return string */ public function getUserDn($sUser) { $this->_w(__FUNCTION__ . '('.$sUser.')'); $aItem=$this->getUserInfo($sUser, array("dn")); if(is_array($aItem) && array_key_exists('dn', $aItem)){ $this->_w(__FUNCTION__ . ' OK: dn was found ' . $aItem['dn']); return $aItem['dn']; } return false; } /** * set a password for a given user; * this requires a ldap bind with master/ admin account * * @param string $sUser username or email * @param string $sPW password * @return boolean */ public function setPassword($sUser, $sPW){ if (!$this->_ldapBind) { $this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser']); } $sDn=$this->getUserDn($sUser); if ($sDn){ return ldap_mod_replace ($this->_ldapConn, $sDn, array('userpassword' => "{MD5}".base64_encode(pack("H*",md5($sPW))))); } $this->_w(__FUNCTION__ . ' dn not found (user does not exist in ldap) ' . $sUser); return false; } /** * update an ldap object * this requires a ldap bind with master/ admin account * * @param string $sDn dn to update * @param string $aItem array of new ldap properties * @return boolean */ public function objAdd($sDn, $aItem){ $this->_w(__FUNCTION__ . '("'.$sDn.'", [array])'); if (!$this->_ldapBind) { $this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser']); } if (!ldap_add($this->_ldapConn, $sDn, $aItem)){ $this->_w(__FUNCTION__ . ' failed with er error ' . ldap_error($this->_ldapConn)); return false; } return true; } /** * update an ldap attribute * this requires a ldap bind with master/ admin account * * @param string $sDn dn to update * @param string $aItem array of new ldap properties * @return boolean */ public function objAddAttr($sDn, $aItem){ $this->_w(__FUNCTION__ . '("'.$sDn.'", [array])'); if (!$this->_ldapBind) { $this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser']); } if ($sDn && is_array($aItem)){ $this->_w(__FUNCTION__ . ' ' . $this->_ldapConn ? 'Verbindung da' : 'kein LDAP Connect'); $this->_w(__FUNCTION__ . ' ldap_mod_add($this->_ldapConn, "'.$sDn.'", ' . print_r($aItem,1) . ')'); if (!ldap_mod_add($this->_ldapConn, $sDn, $aItem)){ $this->_w(__FUNCTION__ . ' ERROR: ' . ldap_error($this->_ldapConn)); return false; } return true; } $this->_w(__FUNCTION__ . ' dn not found (item does not exist in ldap) or item was not ann array ' . print_r($aItem,1)); return false; } /** * update an ldap object * this requires a ldap bind with master/ admin account * * @param string $sDn full DN where to update the item * @param array $aItem updated entry * @return boolean */ public function objUpdate($sDn, $aItem){ $this->_w(__FUNCTION__ . '("'.$sDn.'", [array])'); if (!$this->_ldapBind) { $this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser']); } if ($sDn && is_array($aItem)){ return ldap_mod_replace($this->_ldapConn, $sDn, $aItem); } $this->_w(__FUNCTION__ . ' dn not found (item does not exist in ldap) ' . print_r($aItem,1)); return false; } /** * delete an ldap object * this requires a ldap bind with master/ admin account * * @param string $sDn full DN to remove * @return boolean */ public function objDelete($sDn){ $this->_w(__FUNCTION__ . '("'.$sDn.'")'); if (!$this->_ldapBind) { $this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser']); } if ($sDn){ if (!ldap_delete($this->_ldapConn, $sDn)){ $this->_w(__FUNCTION__ . ' ERROR: ' . ldap_error($this->_ldapConn)); return false; } return true; } $this->_w(__FUNCTION__ . ' missing parameter for DN'); return false; } /** * delete attributes of an ldap object * this requires a ldap bind with master/ admin account * * TODO: Test me * * @param string $sDn DN * @param string $aItem item to remove * @return boolean */ public function objDeleteAttr($sDn, $aItem){ $this->_w(__FUNCTION__ . '("'.$sDn.'", [array])'); if (!$this->_ldapBind) { $this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser']); } if ($sDn && is_array($aItem)){ $this->_w(__FUNCTION__ . ' ' . $this->_ldapConn ? 'Verbindung da' : 'kein LDAP Connect'); $this->_w(__FUNCTION__ . ' ldap_mod_del($this->_ldapConn, "'.$sDn.'", ' . print_r($aItem,1) . ')'); if (!ldap_mod_del($this->_ldapConn, $sDn, $aItem)){ $this->_w(__FUNCTION__ . ' ERROR: ' . ldap_error($this->_ldapConn)); return false; } return true; } $this->_w(__FUNCTION__ . ' dn not found (item does not exist in ldap) or item was not ann array ' . print_r($aItem,1)); return false; } /** * create a new user item * this requires a ldap bind with master/ admin account * * @param array $aItem ldap properties * @param string $sDn optional DN where to create the user * @return boolean */ public function userAdd($aItem, $sDn=false){ if (!$sDn){ $sDn='cn='.$aItem['cn'].','.$this->_aLdap['DnUserNode']; } $this->_w(__FUNCTION__ . '([array], "'.$sDn.'")'); if ($sDn){ return $this->objAdd($sDn, $aItem); } $this->_w(__FUNCTION__ . ' node dn where to put the user was not found; set a value DnUserNode in ldap config or set it as 2nd parameter ' . print_r($aItem,1)); return false; } /** * delete a user * this requires a ldap bind with master/ admin account * * @param string $sUser user to update * @param string $sPW new password to set * @return boolean */ public function userDelete($sUserDn){ $this->_w(__FUNCTION__ . '('.$sUserDn.')'); return $this->objDelete($sUserDn); } /** * update an ldap object * this requires a ldap bind with master/ admin account * * @param string $sUser user to update * @param string $sPW new password to set * @return boolean */ public function userUpdate($aItem){ $this->_w(__FUNCTION__ . '([array])'); $sDn=$this->getUserDn($aItem['uid']); if ($sDn){ return $this->objUpdate($sDn, $aItem); } $this->_w(__FUNCTION__ . ' dn not found (user does not exist in ldap) ' . $sDn); return false; } /** * verify user and password * @param string $sUser username or email * @param string $sPW password * @return boolean */ public function verifyPassword($sUser, $sPW){ $sDn=$this->getUserDn($sUser); if ($sDn){ return $this->bind($sDn, $sPW); } $this->_w(__FUNCTION__ . ' dn not found (user does not exist in ldap) ' . $sUser); return false; } }