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
|
|
|
* |
22
|
|
|
* @copyright 2018 Tony Murray |
23
|
|
|
* @author Tony Murray <[email protected]> |
24
|
|
|
*/ |
25
|
|
|
|
26
|
|
|
namespace LibreNMS\Authentication; |
27
|
|
|
|
28
|
|
|
use LibreNMS\Config; |
29
|
|
|
|
30
|
|
|
trait ActiveDirectoryCommon |
31
|
|
|
{ |
32
|
|
|
protected function getUseridFromSid($sid) |
33
|
|
|
{ |
34
|
|
|
return preg_replace('/.*-(\d+)$/', '$1', $sid); |
35
|
|
|
} |
36
|
|
|
|
37
|
|
|
protected function sidFromLdap($sid) |
38
|
|
|
{ |
39
|
|
|
$sidUnpacked = unpack('H*hex', $sid); |
40
|
|
|
$sidHex = array_shift($sidUnpacked); |
41
|
|
|
$subAuths = unpack('H2/H2/n/N/V*', $sid); |
42
|
|
|
if (PHP_INT_SIZE <= 4) { |
43
|
|
|
for ($i = 1; $i <= count($subAuths); $i++) { |
|
|
|
|
44
|
|
|
if ($subAuths[$i] < 0) { |
45
|
|
|
$subAuths[$i] = $subAuths[$i] + 0x100000000; |
46
|
|
|
} |
47
|
|
|
} |
48
|
|
|
} |
49
|
|
|
$revLevel = hexdec(substr($sidHex, 0, 2)); |
50
|
|
|
$authIdent = hexdec(substr($sidHex, 4, 12)); |
51
|
|
|
|
52
|
|
|
return 'S-' . $revLevel . '-' . $authIdent . '-' . implode('-', $subAuths); |
53
|
|
|
} |
54
|
|
|
|
55
|
|
|
protected function getCn($dn) |
56
|
|
|
{ |
57
|
|
|
$dn = str_replace('\\,', '~C0mmA~', $dn); |
58
|
|
|
preg_match('/[^,]*/', $dn, $matches, PREG_OFFSET_CAPTURE, 3); |
59
|
|
|
|
60
|
|
|
return str_replace('~C0mmA~', ',', $matches[0][0]); |
61
|
|
|
} |
62
|
|
|
|
63
|
|
|
protected function getDn($samaccountname) |
64
|
|
|
{ |
65
|
|
|
$link_identifier = $this->getConnection(); |
66
|
|
|
$attributes = ['dn']; |
67
|
|
|
$result = ldap_search( |
68
|
|
|
$link_identifier, |
69
|
|
|
Config::get('auth_ad_base_dn'), |
|
|
|
|
70
|
|
|
$this->groupFilter($samaccountname), |
71
|
|
|
$attributes |
72
|
|
|
); |
73
|
|
|
$entries = ldap_get_entries($link_identifier, $result); |
74
|
|
|
if ($entries['count'] > 0) { |
75
|
|
|
return $entries[0]['dn']; |
76
|
|
|
} else { |
77
|
|
|
return ''; |
78
|
|
|
} |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
protected function userFilter($username) |
82
|
|
|
{ |
83
|
|
|
// don't return disabled users |
84
|
|
|
$user_filter = "(&(samaccountname=$username)(!(useraccountcontrol:1.2.840.113556.1.4.803:=2))"; |
85
|
|
|
|
86
|
|
|
$extra = Config::get('auth_ad_user_filter'); |
87
|
|
|
if ($extra) { |
88
|
|
|
$user_filter .= $extra; |
89
|
|
|
} |
90
|
|
|
$user_filter .= ')'; |
91
|
|
|
|
92
|
|
|
return $user_filter; |
93
|
|
|
} |
94
|
|
|
|
95
|
|
|
protected function groupFilter($groupname) |
96
|
|
|
{ |
97
|
|
|
$group_filter = "(samaccountname=$groupname)"; |
98
|
|
|
|
99
|
|
|
$extra = Config::get('auth_ad_group_filter'); |
100
|
|
|
if ($extra) { |
101
|
|
|
$group_filter = "(&$extra$group_filter)"; |
102
|
|
|
} |
103
|
|
|
|
104
|
|
|
return $group_filter; |
105
|
|
|
} |
106
|
|
|
|
107
|
|
|
protected function getFullname($username) |
108
|
|
|
{ |
109
|
|
|
$connection = $this->getConnection(); |
110
|
|
|
$attributes = ['name']; |
111
|
|
|
$result = ldap_search( |
112
|
|
|
$connection, |
113
|
|
|
Config::get('auth_ad_base_dn'), |
|
|
|
|
114
|
|
|
$this->userFilter($username), |
115
|
|
|
$attributes |
116
|
|
|
); |
117
|
|
|
$entries = ldap_get_entries($connection, $result); |
118
|
|
|
if ($entries['count'] > 0) { |
119
|
|
|
$membername = $entries[0]['name'][0]; |
120
|
|
|
} else { |
121
|
|
|
$membername = $username; |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
return $membername; |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
public function getGroupList() |
128
|
|
|
{ |
129
|
|
|
$ldap_groups = []; |
130
|
|
|
|
131
|
|
|
// show all Active Directory Users by default |
132
|
|
|
$default_group = 'Users'; |
133
|
|
|
|
134
|
|
|
if (Config::has('auth_ad_group')) { |
135
|
|
|
if (Config::get('auth_ad_group') !== $default_group) { |
136
|
|
|
$ldap_groups[] = Config::get('auth_ad_group'); |
137
|
|
|
} |
138
|
|
|
} |
139
|
|
|
|
140
|
|
|
if (! Config::has('auth_ad_groups') && ! Config::has('auth_ad_group')) { |
141
|
|
|
$ldap_groups[] = $this->getDn($default_group); |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
foreach (Config::get('auth_ad_groups') as $key => $value) { |
145
|
|
|
$ldap_groups[] = $this->getDn($key); |
146
|
|
|
} |
147
|
|
|
|
148
|
|
|
return $ldap_groups; |
149
|
|
|
} |
150
|
|
|
|
151
|
|
|
public function getUserlist() |
152
|
|
|
{ |
153
|
|
|
$connection = $this->getConnection(); |
154
|
|
|
|
155
|
|
|
$userlist = []; |
156
|
|
|
$ldap_groups = $this->getGroupList(); |
157
|
|
|
|
158
|
|
|
foreach ($ldap_groups as $ldap_group) { |
159
|
|
|
$search_filter = "(&(memberOf:1.2.840.113556.1.4.1941:=$ldap_group)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))"; |
160
|
|
|
if (Config::get('auth_ad_user_filter')) { |
161
|
|
|
$search_filter = '(&' . Config::get('auth_ad_user_filter') . $search_filter . ')'; |
162
|
|
|
} |
163
|
|
|
$attributes = ['samaccountname', 'displayname', 'objectsid', 'mail']; |
164
|
|
|
$search = ldap_search($connection, Config::get('auth_ad_base_dn'), $search_filter, $attributes); |
|
|
|
|
165
|
|
|
$results = ldap_get_entries($connection, $search); |
166
|
|
|
|
167
|
|
|
foreach ($results as $result) { |
168
|
|
|
if (isset($result['samaccountname'][0])) { |
169
|
|
|
$userlist[$result['samaccountname'][0]] = $this->userFromAd($result); |
170
|
|
|
} |
171
|
|
|
} |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
return array_values($userlist); |
175
|
|
|
} |
176
|
|
|
|
177
|
|
|
/** |
178
|
|
|
* Generate a user array from an AD LDAP entry |
179
|
|
|
* Must have the attributes: objectsid, samaccountname, displayname, mail |
180
|
|
|
* |
181
|
|
|
* @internal |
182
|
|
|
* |
183
|
|
|
* @param array $entry |
184
|
|
|
* @return array |
185
|
|
|
*/ |
186
|
|
|
protected function userFromAd($entry) |
187
|
|
|
{ |
188
|
|
|
return [ |
189
|
|
|
'user_id' => $this->getUseridFromSid($this->sidFromLdap($entry['objectsid'][0])), |
190
|
|
|
'username' => $entry['samaccountname'][0], |
191
|
|
|
'realname' => $entry['displayname'][0], |
192
|
|
|
'email' => isset($entry['mail'][0]) ? $entry['mail'][0] : null, |
193
|
|
|
'descr' => '', |
194
|
|
|
'level' => $this->getUserlevel($entry['samaccountname'][0]), |
|
|
|
|
195
|
|
|
'can_modify_passwd' => 0, |
196
|
|
|
]; |
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
public function getUser($user_id) |
200
|
|
|
{ |
201
|
|
|
$connection = $this->getConnection(); |
202
|
|
|
$domain_sid = $this->getDomainSid(); |
203
|
|
|
|
204
|
|
|
$search_filter = "(&(objectcategory=person)(objectclass=user)(objectsid=$domain_sid-$user_id))"; |
205
|
|
|
$attributes = ['samaccountname', 'displayname', 'objectsid', 'mail']; |
206
|
|
|
$search = ldap_search($connection, Config::get('auth_ad_base_dn'), $search_filter, $attributes); |
|
|
|
|
207
|
|
|
$entry = ldap_get_entries($connection, $search); |
208
|
|
|
|
209
|
|
|
if (isset($entry[0]['samaccountname'][0])) { |
210
|
|
|
return $this->userFromAd($entry[0]); |
211
|
|
|
} |
212
|
|
|
|
213
|
|
|
return []; |
214
|
|
|
} |
215
|
|
|
|
216
|
|
|
protected function getDomainSid() |
217
|
|
|
{ |
218
|
|
|
$connection = $this->getConnection(); |
219
|
|
|
|
220
|
|
|
// Extract only the domain components |
221
|
|
|
$dn_candidate = preg_replace('/^.*?DC=/i', 'DC=', Config::get('auth_ad_base_dn')); |
222
|
|
|
|
223
|
|
|
$search = ldap_read( |
224
|
|
|
$connection, |
225
|
|
|
$dn_candidate, |
226
|
|
|
'(objectClass=*)', |
227
|
|
|
['objectsid'] |
228
|
|
|
); |
229
|
|
|
$entry = ldap_get_entries($connection, $search); |
230
|
|
|
|
231
|
|
|
return substr($this->sidFromLdap($entry[0]['objectsid'][0]), 0, 41); |
232
|
|
|
} |
233
|
|
|
|
234
|
|
|
/** |
235
|
|
|
* Provide a connected and bound ldap connection resource |
236
|
|
|
* |
237
|
|
|
* @return resource |
238
|
|
|
*/ |
239
|
|
|
abstract protected function getConnection(); |
240
|
|
|
} |
241
|
|
|
|
If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration: