connect(); if ($this->ldap_connection) { // bind with sAMAccountName instead of full LDAP DN if ($username && $password && ldap_bind($this->ldap_connection, $username . '@' . Config::get('auth_ad_domain'), $password)) { $this->is_bound = true; // group membership in one of the configured groups is required if (Config::get('auth_ad_require_groupmembership', true)) { // cycle through defined groups, test for memberOf-ship foreach (Config::get('auth_ad_groups', array()) as $group => $level) { if ($this->userInGroup($username, $group)) { return true; } } // failed to find user if (Config::get('auth_ad_debug', false)) { throw new AuthenticationException('User is not in one of the required groups or user/group is outside the base dn'); } throw new AuthenticationException(); } else { // group membership is not required and user is valid return true; } } } if (!isset($password) || $password == '') { throw new AuthenticationException('A password is required'); } elseif (Config::get('auth_ad_debug', false)) { ldap_get_option($this->ldap_connection, LDAP_OPT_DIAGNOSTIC_MESSAGE, $extended_error); throw new AuthenticationException(ldap_error($this->ldap_connection).'
'.$extended_error); } throw new AuthenticationException(ldap_error($this->ldap_connection)); } public function reauthenticate($sess_id, $token) { if ($this->bind(false, true)) { $sess_id = clean($sess_id); $token = clean($token); list($username, $hash) = explode('|', $token); if (!$this->userExists($username)) { if (Config::get('auth_ad_debug', false)) { throw new AuthenticationException("$username is not a valid AD user"); } throw new AuthenticationException(); } return $this->checkRememberMe($sess_id, $token); } return false; } protected function userInGroup($username, $groupname) { // check if user is member of the given group or nested groups $search_filter = "(&(objectClass=group)(cn=$groupname))"; // get DN for auth_ad_group $search = ldap_search( $this->ldap_connection, Config::get('auth_ad_base_dn'), $search_filter, array("cn") ); $result = ldap_get_entries($this->ldap_connection, $search); if ($result == false || $result['count'] !== 1) { if (Config::get('auth_ad_debug', false)) { if ($result == false) { // FIXME: what went wrong? throw new AuthenticationException("LDAP query failed for group '$groupname' using filter '$search_filter'"); } elseif ($result['count'] == 0) { throw new AuthenticationException("Failed to find group matching '$groupname' using filter '$search_filter'"); } elseif ($result['count'] > 1) { throw new AuthenticationException("Multiple groups returned for '$groupname' using filter '$search_filter'"); } } throw new AuthenticationException(); } $group_dn = $result[0]["dn"]; $search = ldap_search( $this->ldap_connection, Config::get('auth_ad_base_dn'), // add 'LDAP_MATCHING_RULE_IN_CHAIN to the user filter to search for $username in nested $group_dn // limiting to "DN" for shorter array "(&" . get_auth_ad_user_filter($username) . "(memberOf:1.2.840.113556.1.4.1941:=$group_dn))", array("DN") ); $entries = ldap_get_entries($this->ldap_connection, $search); return ($entries["count"] > 0); } protected function userExistsInDb($username) { $return = dbFetchCell('SELECT COUNT(*) FROM users WHERE username = ?', array($username)); return $return; } public function userExists($username, $throw_exception = false) { $this->bind(); // make sure we called bind $search = ldap_search( $this->ldap_connection, Config::get('auth_ad_base_dn'), get_auth_ad_user_filter($username), array('samaccountname') ); $entries = ldap_get_entries($this->ldap_connection, $search); if ($entries['count']) { return 1; } return 0; } public function getUserlevel($username) { $this->bind(); // make sure we called bind $userlevel = 0; if (!Config::get('auth_ad_require_groupmembership', true)) { if (Config::get('auth_ad_global_read', false)) { $userlevel = 5; } } // cycle through defined groups, test for memberOf-ship foreach (Config::get('auth_ad_groups', array()) as $group => $level) { try { if ($this->userInGroup($username, $group)) { $userlevel = max($userlevel, $level['level']); } } catch (AuthenticationException $e) { } } return $userlevel; } public function getUserid($username) { $this->bind(); // make sure we called bind $attributes = array('objectsid'); $search = ldap_search( $this->ldap_connection, Config::get('auth_ad_base_dn'), get_auth_ad_user_filter($username), $attributes ); $entries = ldap_get_entries($this->ldap_connection, $search); if ($entries['count']) { return $this->getUseridFromSid($this->sidFromLdap($entries[0]['objectsid'][0])); } return -1; } protected function getDomainSid() { $this->bind(); // make sure we called bind // Extract only the domain components $dn_candidate = preg_replace('/^.*?DC=/i', 'DC=', Config::get('auth_ad_base_dn')); $search = ldap_read( $this->ldap_connection, $dn_candidate, '(objectClass=*)', array('objectsid') ); $entry = ldap_get_entries($this->ldap_connection, $search); return substr($this->sidFromLdap($entry[0]['objectsid'][0]), 0, 41); } public function getUser($user_id) { $this->bind(); // make sure we called bind $domain_sid = $this->getDomainSid(); $search_filter = "(&(objectcategory=person)(objectclass=user)(objectsid=$domain_sid-$user_id))"; $attributes = array('samaccountname', 'displayname', 'objectsid', 'mail'); $search = ldap_search($this->ldap_connection, Config::get('auth_ad_base_dn'), $search_filter, $attributes); $entry = ldap_get_entries($this->ldap_connection, $search); if (isset($entry[0]['samaccountname'][0])) { return $this->userFromAd($entry[0]); } return array(); } public function deleteUser($userid) { dbDelete('bill_perms', '`user_id` = ?', array($userid)); dbDelete('devices_perms', '`user_id` = ?', array($userid)); dbDelete('ports_perms', '`user_id` = ?', array($userid)); dbDelete('users_prefs', '`user_id` = ?', array($userid)); return 0; } public function getUserlist() { $this->bind(); // make sure we called bind $userlist = array(); $ldap_groups = $this->getGroupList(); foreach ($ldap_groups as $ldap_group) { $search_filter = "(memberOf=$ldap_group)"; if (Config::get('auth_ad_user_filter')) { $search_filter = "(&" . Config::get('auth_ad_user_filter') . $search_filter .")"; } $attributes = array('samaccountname', 'displayname', 'objectsid', 'mail'); $search = ldap_search($this->ldap_connection, Config::get('auth_ad_base_dn'), $search_filter, $attributes); $results = ldap_get_entries($this->ldap_connection, $search); foreach ($results as $result) { if (isset($result['samaccountname'][0])) { $userlist[$result['samaccountname'][0]] = $this->userFromAd($result); } } } return array_values($userlist); } /** * Generate a user array from an AD LDAP entry * Must have the attributes: objectsid, samaccountname, displayname, mail * @internal * * @param $entry * @return array */ protected function userFromAd($entry) { return array( 'user_id' => $this->getUseridFromSid($this->sidFromLdap($entry['objectsid'][0])), 'username' => $entry['samaccountname'][0], 'realname' => $entry['displayname'][0], 'email' => $entry['mail'][0], 'descr' => '', 'level' => $this->getUserlevel($entry['samaccountname'][0]), 'can_modify_passwd' => 0, ); } protected function getEmail($username) { $this->bind(); // make sure we called bind $attributes = array('mail'); $search = ldap_search( $this->ldap_connection, Config::get('auth_ad_base_dn'), get_auth_ad_user_filter($username), $attributes ); $result = ldap_get_entries($this->ldap_connection, $search); unset($result[0]['mail']['count']); return current($result[0]['mail']); } protected function getFullname($username) { $this->bind(); // make sure we called bind $attributes = array('name'); $result = ldap_search( $this->ldap_connection, Config::get('auth_ad_base_dn'), get_auth_ad_user_filter($username), $attributes ); $entries = ldap_get_entries($this->ldap_connection, $result); if ($entries['count'] > 0) { $membername = $entries[0]['name'][0]; } else { $membername = $username; } return $membername; } public function getGroupList() { $ldap_groups = array(); // show all Active Directory Users by default $default_group = 'Users'; if (Config::has('auth_ad_group')) { if (Config::get('auth_ad_group') !== $default_group) { $ldap_groups[] = Config::get('auth_ad_group'); } } if (!Config::has('auth_ad_groups') && !Config::has('auth_ad_group')) { $ldap_groups[] = $this->getDn($default_group); } foreach (Config::get('auth_ad_groups') as $key => $value) { $ldap_groups[] = $this->getDn($key); } return $ldap_groups; } protected function getDn($samaccountname) { $this->bind(); // make sure we called bind $attributes = array('dn'); $result = ldap_search( $this->ldap_connection, Config::get('auth_ad_base_dn'), get_auth_ad_group_filter($samaccountname), $attributes ); $entries = ldap_get_entries($this->ldap_connection, $result); if ($entries['count'] > 0) { return $entries[0]['dn']; } else { return ''; } } protected function getCn($dn) { $dn = str_replace('\\,', '~C0mmA~', $dn); preg_match('/[^,]*/', $dn, $matches, PREG_OFFSET_CAPTURE, 3); return str_replace('~C0mmA~', ',', $matches[0][0]); } protected function getUseridFromSid($sid) { return preg_replace('/.*-(\d+)$/', '$1', $sid); } protected function sidFromLdap($sid) { $sidUnpacked = unpack('H*hex', $sid); $sidHex = array_shift($sidUnpacked); $subAuths = unpack('H2/H2/n/N/V*', $sid); $revLevel = hexdec(substr($sidHex, 0, 2)); $authIdent = hexdec(substr($sidHex, 4, 12)); return 'S-'.$revLevel.'-'.$authIdent.'-'.implode('-', $subAuths); } /** * Bind to AD with the bind user if available, otherwise anonymous bind * @internal * * @param bool $allow_anonymous attempt anonymous bind if bind user isn't available * @param bool $force force rebind * @return bool success or failure */ protected function bind($allow_anonymous = true, $force = false) { if ($this->is_bound && !$force) { return true; // bind already attempted } $this->connect(); // make sure we are connected // set timeout ldap_set_option( $this->ldap_connection, LDAP_OPT_NETWORK_TIMEOUT, Config::has('auth_ad_timeout') ? Config::has('auth_ad_timeout') : 5 ); // With specified bind user if (Config::has('auth_ad_binduser') && Config::has('auth_ad_bindpassword')) { $this->is_bound = true; $bind = ldap_bind( $this->ldap_connection, Config::get('auth_ad_binduser') . '@' . Config::get('auth_ad_domain'), Config::get('auth_ad_bindpassword') ); ldap_set_option($this->ldap_connection, LDAP_OPT_NETWORK_TIMEOUT, -1); // restore timeout return $bind; } $bind = false; // Anonymous if ($allow_anonymous) { $this->is_bound = true; $bind = ldap_bind($this->ldap_connection); } ldap_set_option($this->ldap_connection, LDAP_OPT_NETWORK_TIMEOUT, -1); // restore timeout return $bind; } protected function connect() { if ($this->ldap_connection) { // no need to re-connect return; } if (!function_exists('ldap_connect')) { throw new AuthenticationException("PHP does not support LDAP, please install or enable the PHP LDAP extension."); } if (Config::has('auth_ad_check_certificates') && !Config::get('auth_ad_check_certificates')) { putenv('LDAPTLS_REQCERT=never'); }; if (Config::has('auth_ad_check_certificates') && Config::get('auth_ad_debug')) { ldap_set_option(null, LDAP_OPT_DEBUG_LEVEL, 7); } $this->ldap_connection = @ldap_connect(Config::get('auth_ad_url')); // disable referrals and force ldap version to 3 ldap_set_option($this->ldap_connection, LDAP_OPT_REFERRALS, 0); ldap_set_option($this->ldap_connection, LDAP_OPT_PROTOCOL_VERSION, 3); } }