1<?php 2/** 3 * ActiveDirectoryCommonirectoryCommon.php 4 * 5 * Common code from AD auth modules 6 * 7 * This program is free software: you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License as published by 9 * the Free Software Foundation, either version 3 of the License, or 10 * (at your option) any later version. 11 * 12 * This program is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the 15 * GNU General Public License for more details. 16 * 17 * You should have received a copy of the GNU General Public License 18 * along with this program. If not, see <https://www.gnu.org/licenses/>. 19 * 20 * @link https://www.librenms.org 21 * @copyright 2018 Tony Murray 22 * @author Tony Murray <murraytony@gmail.com> 23 */ 24 25namespace LibreNMS\Authentication; 26 27use LibreNMS\Config; 28 29trait ActiveDirectoryCommon 30{ 31 protected function getUseridFromSid($sid) 32 { 33 return preg_replace('/.*-(\d+)$/', '$1', $sid); 34 } 35 36 protected function sidFromLdap($sid) 37 { 38 $sidUnpacked = unpack('H*hex', $sid); 39 $sidHex = array_shift($sidUnpacked); 40 $subAuths = unpack('H2/H2/n/N/V*', $sid); 41 if (PHP_INT_SIZE <= 4) { 42 for ($i = 1; $i <= count($subAuths); $i++) { 43 if ($subAuths[$i] < 0) { 44 $subAuths[$i] = $subAuths[$i] + 0x100000000; 45 } 46 } 47 } 48 $revLevel = hexdec(substr($sidHex, 0, 2)); 49 $authIdent = hexdec(substr($sidHex, 4, 12)); 50 51 return 'S-' . $revLevel . '-' . $authIdent . '-' . implode('-', $subAuths); 52 } 53 54 protected function getCn($dn) 55 { 56 $dn = str_replace('\\,', '~C0mmA~', $dn); 57 preg_match('/[^,]*/', $dn, $matches, PREG_OFFSET_CAPTURE, 3); 58 59 return str_replace('~C0mmA~', ',', $matches[0][0]); 60 } 61 62 protected function getDn($samaccountname) 63 { 64 $link_identifier = $this->getConnection(); 65 $attributes = ['dn']; 66 $result = ldap_search( 67 $link_identifier, 68 Config::get('auth_ad_base_dn'), 69 $this->groupFilter($samaccountname), 70 $attributes 71 ); 72 $entries = ldap_get_entries($link_identifier, $result); 73 if ($entries['count'] > 0) { 74 return $entries[0]['dn']; 75 } else { 76 return ''; 77 } 78 } 79 80 protected function userFilter($username) 81 { 82 // don't return disabled users 83 $user_filter = "(&(samaccountname=$username)(!(useraccountcontrol:1.2.840.113556.1.4.803:=2))"; 84 85 $extra = Config::get('auth_ad_user_filter'); 86 if ($extra) { 87 $user_filter .= $extra; 88 } 89 $user_filter .= ')'; 90 91 return $user_filter; 92 } 93 94 protected function groupFilter($groupname) 95 { 96 $group_filter = "(samaccountname=$groupname)"; 97 98 $extra = Config::get('auth_ad_group_filter'); 99 if ($extra) { 100 $group_filter = "(&$extra$group_filter)"; 101 } 102 103 return $group_filter; 104 } 105 106 protected function getFullname($username) 107 { 108 $connection = $this->getConnection(); 109 $attributes = ['name']; 110 $result = ldap_search( 111 $connection, 112 Config::get('auth_ad_base_dn'), 113 $this->userFilter($username), 114 $attributes 115 ); 116 $entries = ldap_get_entries($connection, $result); 117 if ($entries['count'] > 0) { 118 $membername = $entries[0]['name'][0]; 119 } else { 120 $membername = $username; 121 } 122 123 return $membername; 124 } 125 126 public function getGroupList() 127 { 128 $ldap_groups = []; 129 130 // show all Active Directory Users by default 131 $default_group = 'Users'; 132 133 if (Config::has('auth_ad_group')) { 134 if (Config::get('auth_ad_group') !== $default_group) { 135 $ldap_groups[] = Config::get('auth_ad_group'); 136 } 137 } 138 139 if (! Config::has('auth_ad_groups') && ! Config::has('auth_ad_group')) { 140 $ldap_groups[] = $this->getDn($default_group); 141 } 142 143 foreach (Config::get('auth_ad_groups') as $key => $value) { 144 $ldap_groups[] = $this->getDn($key); 145 } 146 147 return $ldap_groups; 148 } 149 150 public function getUserlist() 151 { 152 $connection = $this->getConnection(); 153 154 $userlist = []; 155 $ldap_groups = $this->getGroupList(); 156 157 foreach ($ldap_groups as $ldap_group) { 158 $search_filter = "(&(memberOf:1.2.840.113556.1.4.1941:=$ldap_group)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))"; 159 if (Config::get('auth_ad_user_filter')) { 160 $search_filter = '(&' . Config::get('auth_ad_user_filter') . $search_filter . ')'; 161 } 162 $attributes = ['samaccountname', 'displayname', 'objectsid', 'mail']; 163 $search = ldap_search($connection, Config::get('auth_ad_base_dn'), $search_filter, $attributes); 164 $results = ldap_get_entries($connection, $search); 165 166 foreach ($results as $result) { 167 if (isset($result['samaccountname'][0])) { 168 $userlist[$result['samaccountname'][0]] = $this->userFromAd($result); 169 } 170 } 171 } 172 173 return array_values($userlist); 174 } 175 176 /** 177 * Generate a user array from an AD LDAP entry 178 * Must have the attributes: objectsid, samaccountname, displayname, mail 179 * @internal 180 * 181 * @param array $entry 182 * @return array 183 */ 184 protected function userFromAd($entry) 185 { 186 return [ 187 'user_id' => $this->getUseridFromSid($this->sidFromLdap($entry['objectsid'][0])), 188 'username' => $entry['samaccountname'][0], 189 'realname' => $entry['displayname'][0], 190 'email' => isset($entry['mail'][0]) ? $entry['mail'][0] : null, 191 'descr' => '', 192 'level' => $this->getUserlevel($entry['samaccountname'][0]), 193 'can_modify_passwd' => 0, 194 ]; 195 } 196 197 public function getUser($user_id) 198 { 199 $connection = $this->getConnection(); 200 $domain_sid = $this->getDomainSid(); 201 202 $search_filter = "(&(objectcategory=person)(objectclass=user)(objectsid=$domain_sid-$user_id))"; 203 $attributes = ['samaccountname', 'displayname', 'objectsid', 'mail']; 204 $search = ldap_search($connection, Config::get('auth_ad_base_dn'), $search_filter, $attributes); 205 $entry = ldap_get_entries($connection, $search); 206 207 if (isset($entry[0]['samaccountname'][0])) { 208 return $this->userFromAd($entry[0]); 209 } 210 211 return []; 212 } 213 214 protected function getDomainSid() 215 { 216 $connection = $this->getConnection(); 217 218 // Extract only the domain components 219 $dn_candidate = preg_replace('/^.*?DC=/i', 'DC=', Config::get('auth_ad_base_dn')); 220 221 $search = ldap_read( 222 $connection, 223 $dn_candidate, 224 '(objectClass=*)', 225 ['objectsid'] 226 ); 227 $entry = ldap_get_entries($connection, $search); 228 229 return substr($this->sidFromLdap($entry[0]['objectsid'][0]), 0, 41); 230 } 231 232 /** 233 * Provide a connected and bound ldap connection resource 234 * 235 * @return resource 236 */ 237 abstract protected function getConnection(); 238} 239