]>
Commit | Line | Data |
---|---|---|
9e6e0372 | 1 | /* |
c152a447 | 2 | * ext_ad_group_acl: lookup group membership in a Windows |
9e6e0372 GS |
3 | * Active Directory domain |
4 | * | |
9fa76e99 | 5 | * (C)2008-2009 Guido Serassio - Acme Consulting S.r.l. |
9e6e0372 GS |
6 | * |
7 | * Authors: | |
8 | * Guido Serassio <guido.serassio@acmeconsulting.it> | |
9 | * Acme Consulting S.r.l., Italy <http://www.acmeconsulting.it> | |
10 | * | |
11 | * With contributions from others mentioned in the change history section | |
12 | * below. | |
13 | * | |
14 | * Based on mswin_check_lm_group by Guido Serassio. | |
15 | * | |
16 | * Dependencies: Windows 2000 SP4 and later. | |
17 | * | |
18 | * This program is free software; you can redistribute it and/or modify | |
19 | * it under the terms of the GNU General Public License as published by | |
20 | * the Free Software Foundation; either version 2 of the License, or | |
21 | * (at your option) any later version. | |
22 | * | |
23 | * This program is distributed in the hope that it will be useful, | |
24 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
25 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
26 | * GNU General Public License for more details. | |
27 | * | |
28 | * You should have received a copy of the GNU General Public License | |
29 | * along with this program; if not, write to the Free Software | |
30 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. | |
31 | * | |
32 | * History: | |
33 | * | |
9fa76e99 AJ |
34 | * Version 2.1 |
35 | * 20-09-2009 Guido Serassio | |
36 | * Added explicit Global Catalog query | |
37 | * | |
38 | * Version 2.0 | |
39 | * 20-07-2009 Guido Serassio | |
40 | * Global groups support rewritten, now is based on ADSI. | |
41 | * New Features: | |
cb0cec99 | 42 | * - support for Domain Local, Domain Global ad Universal |
9fa76e99 AJ |
43 | * groups |
44 | * - full group nesting support | |
9e6e0372 GS |
45 | * Version 1.0 |
46 | * 02-05-2008 Guido Serassio | |
47 | * First release, based on mswin_check_lm_group. | |
48 | * | |
49 | * This is a helper for the external ACL interface for Squid Cache | |
26ac0430 | 50 | * |
9e6e0372 GS |
51 | * It reads from the standard input the domain username and a list of |
52 | * groups and tries to match it against the groups membership of the | |
53 | * specified username. | |
54 | * | |
55 | * Returns `OK' if the user belongs to a group or `ERR' otherwise, as | |
56 | * described on http://devel.squid-cache.org/external_acl/config.html | |
57 | * | |
58 | */ | |
59 | ||
f7f3304a | 60 | #include "squid.h" |
c152a447 AJ |
61 | #include "helpers/defines.h" |
62 | #include "include/util.h" | |
63 | ||
be266cb2 | 64 | #if _SQUID_CYGWIN_ |
9e6e0372 GS |
65 | #include <wchar.h> |
66 | int _wcsicmp(const wchar_t *, const wchar_t *); | |
67 | #endif | |
c152a447 | 68 | |
074d6a40 AJ |
69 | #undef assert |
70 | #include <cassert> | |
71 | #include <cctype> | |
72 | #include <cstring> | |
9e6e0372 GS |
73 | #if HAVE_GETOPT_H |
74 | #include <getopt.h> | |
75 | #endif | |
9e6e0372 | 76 | #include <windows.h> |
9fa76e99 AJ |
77 | #include <objbase.h> |
78 | #include <initguid.h> | |
79 | #include <adsiid.h> | |
80 | #include <iads.h> | |
81 | #include <adshlp.h> | |
82 | #include <adserr.h> | |
9e6e0372 | 83 | #include <lm.h> |
9e6e0372 | 84 | #include <dsrole.h> |
9fa76e99 | 85 | #include <sddl.h> |
9e6e0372 | 86 | |
9fa76e99 AJ |
87 | enum ADSI_PATH { |
88 | LDAP_MODE, | |
89 | GC_MODE | |
90 | } ADSI_Path; | |
91 | ||
9e6e0372 | 92 | int use_global = 0; |
c152a447 | 93 | char *program_name; |
9e6e0372 GS |
94 | pid_t mypid; |
95 | char *machinedomain; | |
96 | int use_case_insensitive_compare = 0; | |
97 | char *DefaultDomain = NULL; | |
98 | const char NTV_VALID_DOMAIN_SEPARATOR[] = "\\/"; | |
9fa76e99 AJ |
99 | int numberofgroups = 0; |
100 | int WIN32_COM_initialized = 0; | |
101 | char *WIN32_ErrorMessage = NULL; | |
102 | wchar_t **User_Groups; | |
103 | int User_Groups_Count = 0; | |
9e6e0372 | 104 | |
9fa76e99 AJ |
105 | wchar_t *My_NameTranslate(wchar_t *, int, int); |
106 | char *Get_WIN32_ErrorMessage(HRESULT); | |
107 | ||
9fa76e99 AJ |
108 | void |
109 | CloseCOM(void) | |
110 | { | |
111 | if (WIN32_COM_initialized == 1) | |
cb0cec99 | 112 | CoUninitialize(); |
9fa76e99 AJ |
113 | } |
114 | ||
9fa76e99 AJ |
115 | HRESULT |
116 | GetLPBYTEtoOctetString(VARIANT * pVar, LPBYTE * ppByte) | |
117 | { | |
118 | HRESULT hr = E_FAIL; | |
119 | void HUGEP *pArray; | |
120 | long lLBound, lUBound, cElements; | |
121 | ||
122 | if ((!pVar) || (!ppByte)) | |
cb0cec99 | 123 | return E_INVALIDARG; |
9fa76e99 | 124 | if ((pVar->n1.n2.vt) != (VT_UI1 | VT_ARRAY)) |
cb0cec99 | 125 | return E_INVALIDARG; |
9fa76e99 AJ |
126 | |
127 | hr = SafeArrayGetLBound(V_ARRAY(pVar), 1, &lLBound); | |
128 | hr = SafeArrayGetUBound(V_ARRAY(pVar), 1, &lUBound); | |
129 | ||
130 | cElements = lUBound - lLBound + 1; | |
131 | hr = SafeArrayAccessData(V_ARRAY(pVar), &pArray); | |
132 | if (SUCCEEDED(hr)) { | |
cb0cec99 A |
133 | LPBYTE pTemp = (LPBYTE) pArray; |
134 | *ppByte = (LPBYTE) CoTaskMemAlloc(cElements); | |
135 | if (*ppByte) | |
136 | memcpy(*ppByte, pTemp, cElements); | |
137 | else | |
138 | hr = E_OUTOFMEMORY; | |
9fa76e99 AJ |
139 | } |
140 | SafeArrayUnaccessData(V_ARRAY(pVar)); | |
141 | ||
142 | return hr; | |
143 | } | |
144 | ||
9fa76e99 AJ |
145 | wchar_t * |
146 | Get_primaryGroup(IADs * pUser) | |
147 | { | |
148 | HRESULT hr; | |
149 | VARIANT var; | |
150 | unsigned User_primaryGroupID; | |
151 | char tmpSID[SECURITY_MAX_SID_SIZE * 2]; | |
152 | wchar_t *wc = NULL, *result = NULL; | |
153 | int wcsize; | |
154 | ||
155 | VariantInit(&var); | |
156 | ||
157 | /* Get the primaryGroupID property */ | |
158 | hr = pUser->lpVtbl->Get(pUser, L"primaryGroupID", &var); | |
159 | if (SUCCEEDED(hr)) { | |
cb0cec99 | 160 | User_primaryGroupID = var.n1.n2.n3.uintVal; |
9fa76e99 | 161 | } else { |
cb0cec99 A |
162 | debug("Get_primaryGroup: cannot get primaryGroupID, ERROR: %s\n", Get_WIN32_ErrorMessage(hr)); |
163 | VariantClear(&var); | |
164 | return result; | |
9fa76e99 AJ |
165 | } |
166 | VariantClear(&var); | |
167 | ||
168 | /*Get the objectSid property */ | |
169 | hr = pUser->lpVtbl->Get(pUser, L"objectSid", &var); | |
170 | if (SUCCEEDED(hr)) { | |
cb0cec99 A |
171 | PSID pObjectSID; |
172 | LPBYTE pByte = NULL; | |
173 | char *szSID = NULL; | |
174 | hr = GetLPBYTEtoOctetString(&var, &pByte); | |
9fa76e99 | 175 | |
cb0cec99 | 176 | pObjectSID = (PSID) pByte; |
9fa76e99 | 177 | |
cb0cec99 A |
178 | /* Convert SID to string. */ |
179 | ConvertSidToStringSid(pObjectSID, &szSID); | |
180 | CoTaskMemFree(pByte); | |
9fa76e99 | 181 | |
cb0cec99 | 182 | *(strrchr(szSID, '-') + 1) = '\0'; |
86c63190 | 183 | snprintf(tmpSID, sizeof(tmpSID)-1, "%s%u", szSID, User_primaryGroupID); |
9fa76e99 | 184 | |
cb0cec99 A |
185 | wcsize = MultiByteToWideChar(CP_ACP, 0, tmpSID, -1, wc, 0); |
186 | wc = (wchar_t *) xmalloc(wcsize * sizeof(wchar_t)); | |
187 | MultiByteToWideChar(CP_ACP, 0, tmpSID, -1, wc, wcsize); | |
188 | LocalFree(szSID); | |
9fa76e99 | 189 | |
cb0cec99 A |
190 | result = My_NameTranslate(wc, ADS_NAME_TYPE_SID_OR_SID_HISTORY_NAME, ADS_NAME_TYPE_1779); |
191 | safe_free(wc); | |
9fa76e99 | 192 | |
cb0cec99 A |
193 | if (result == NULL) |
194 | debug("Get_primaryGroup: cannot get DN for %s.\n", tmpSID); | |
195 | else | |
196 | debug("Get_primaryGroup: Primary group DN: %S.\n", result); | |
9fa76e99 | 197 | } else |
cb0cec99 | 198 | debug("Get_primaryGroup: cannot get objectSid, ERROR: %s\n", Get_WIN32_ErrorMessage(hr)); |
9fa76e99 AJ |
199 | VariantClear(&var); |
200 | return result; | |
201 | } | |
202 | ||
9fa76e99 AJ |
203 | char * |
204 | Get_WIN32_ErrorMessage(HRESULT hr) | |
205 | { | |
206 | FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | | |
cb0cec99 A |
207 | FORMAT_MESSAGE_IGNORE_INSERTS, |
208 | NULL, | |
209 | hr, | |
210 | MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), | |
211 | (LPTSTR) & WIN32_ErrorMessage, | |
212 | 0, | |
213 | NULL); | |
9fa76e99 AJ |
214 | return WIN32_ErrorMessage; |
215 | } | |
216 | ||
9fa76e99 AJ |
217 | wchar_t * |
218 | My_NameTranslate(wchar_t * name, int in_format, int out_format) | |
219 | { | |
220 | IADsNameTranslate *pNto; | |
221 | HRESULT hr; | |
222 | BSTR bstr; | |
223 | wchar_t *wc; | |
224 | ||
225 | if (WIN32_COM_initialized == 0) { | |
cb0cec99 A |
226 | hr = CoInitialize(NULL); |
227 | if (FAILED(hr)) { | |
228 | debug("My_NameTranslate: cannot initialize COM interface, ERROR: %s\n", Get_WIN32_ErrorMessage(hr)); | |
229 | /* This is a fatal error */ | |
230 | exit(1); | |
231 | } | |
232 | WIN32_COM_initialized = 1; | |
9fa76e99 AJ |
233 | } |
234 | hr = CoCreateInstance(&CLSID_NameTranslate, | |
cb0cec99 A |
235 | NULL, |
236 | CLSCTX_INPROC_SERVER, | |
237 | &IID_IADsNameTranslate, | |
238 | (void **) &pNto); | |
9fa76e99 | 239 | if (FAILED(hr)) { |
cb0cec99 A |
240 | debug("My_NameTranslate: cannot create COM instance, ERROR: %s\n", Get_WIN32_ErrorMessage(hr)); |
241 | /* This is a fatal error */ | |
242 | exit(1); | |
9fa76e99 AJ |
243 | } |
244 | hr = pNto->lpVtbl->Init(pNto, ADS_NAME_INITTYPE_GC, L""); | |
245 | if (FAILED(hr)) { | |
cb0cec99 A |
246 | debug("My_NameTranslate: cannot initialise NameTranslate API, ERROR: %s\n", Get_WIN32_ErrorMessage(hr)); |
247 | pNto->lpVtbl->Release(pNto); | |
248 | /* This is a fatal error */ | |
249 | exit(1); | |
9fa76e99 AJ |
250 | } |
251 | hr = pNto->lpVtbl->Set(pNto, in_format, name); | |
252 | if (FAILED(hr)) { | |
cb0cec99 A |
253 | debug("My_NameTranslate: cannot set translate of %S, ERROR: %s\n", name, Get_WIN32_ErrorMessage(hr)); |
254 | pNto->lpVtbl->Release(pNto); | |
255 | return NULL; | |
9fa76e99 AJ |
256 | } |
257 | hr = pNto->lpVtbl->Get(pNto, out_format, &bstr); | |
258 | if (FAILED(hr)) { | |
cb0cec99 A |
259 | debug("My_NameTranslate: cannot get translate of %S, ERROR: %s\n", name, Get_WIN32_ErrorMessage(hr)); |
260 | pNto->lpVtbl->Release(pNto); | |
261 | return NULL; | |
9fa76e99 AJ |
262 | } |
263 | debug("My_NameTranslate: %S translated to %S\n", name, bstr); | |
264 | ||
265 | wc = (wchar_t *) xmalloc((wcslen(bstr) + 1) * sizeof(wchar_t)); | |
266 | wcscpy(wc, bstr); | |
267 | SysFreeString(bstr); | |
268 | pNto->lpVtbl->Release(pNto); | |
269 | return wc; | |
270 | } | |
271 | ||
9fa76e99 AJ |
272 | wchar_t * |
273 | GetLDAPPath(wchar_t * Base_DN, int query_mode) | |
274 | { | |
275 | wchar_t *wc; | |
276 | ||
277 | wc = (wchar_t *) xmalloc((wcslen(Base_DN) + 8) * sizeof(wchar_t)); | |
278 | ||
279 | if (query_mode == LDAP_MODE) | |
cb0cec99 | 280 | wcscpy(wc, L"LDAP://"); |
9fa76e99 | 281 | else |
cb0cec99 | 282 | wcscpy(wc, L"GC://"); |
9fa76e99 AJ |
283 | wcscat(wc, Base_DN); |
284 | ||
285 | return wc; | |
286 | } | |
287 | ||
9e6e0372 GS |
288 | char * |
289 | GetDomainName(void) | |
290 | { | |
291 | static char *DomainName = NULL; | |
292 | PDSROLE_PRIMARY_DOMAIN_INFO_BASIC pDSRoleInfo; | |
293 | DWORD netret; | |
294 | ||
295 | if ((netret = DsRoleGetPrimaryDomainInformation(NULL, DsRolePrimaryDomainInfoBasic, (PBYTE *) & pDSRoleInfo) == ERROR_SUCCESS)) { | |
26ac0430 AJ |
296 | /* |
297 | * Check the machine role. | |
298 | */ | |
299 | ||
300 | if ((pDSRoleInfo->MachineRole == DsRole_RoleMemberWorkstation) || | |
301 | (pDSRoleInfo->MachineRole == DsRole_RoleMemberServer) || | |
302 | (pDSRoleInfo->MachineRole == DsRole_RoleBackupDomainController) || | |
303 | (pDSRoleInfo->MachineRole == DsRole_RolePrimaryDomainController)) { | |
304 | ||
305 | size_t len = wcslen(pDSRoleInfo->DomainNameFlat); | |
306 | ||
307 | /* allocate buffer for str + null termination */ | |
308 | safe_free(DomainName); | |
309 | DomainName = (char *) xmalloc(len + 1); | |
26ac0430 AJ |
310 | |
311 | /* copy unicode buffer */ | |
312 | WideCharToMultiByte(CP_ACP, 0, pDSRoleInfo->DomainNameFlat, -1, DomainName, len, NULL, NULL); | |
313 | ||
314 | /* add null termination */ | |
315 | DomainName[len] = '\0'; | |
316 | ||
317 | /* | |
318 | * Member of a domain. Display it in debug mode. | |
319 | */ | |
320 | debug("Member of Domain %s\n", DomainName); | |
321 | debug("Into forest %S\n", pDSRoleInfo->DomainForestName); | |
322 | ||
323 | } else { | |
324 | debug("Not a Domain member\n"); | |
325 | } | |
9e6e0372 | 326 | } else |
cb0cec99 | 327 | debug("GetDomainName: ERROR DsRoleGetPrimaryDomainInformation returned: %s\n", Get_WIN32_ErrorMessage(netret)); |
9e6e0372 GS |
328 | |
329 | /* | |
330 | * Free the allocated memory. | |
331 | */ | |
332 | if (pDSRoleInfo != NULL) | |
26ac0430 | 333 | DsRoleFreeMemory(pDSRoleInfo); |
9e6e0372 GS |
334 | |
335 | return DomainName; | |
336 | } | |
337 | ||
9fa76e99 AJ |
338 | int |
339 | add_User_Group(wchar_t * Group) | |
340 | { | |
341 | wchar_t **array; | |
342 | ||
343 | if (User_Groups_Count == 0) { | |
cb0cec99 A |
344 | User_Groups = (wchar_t **) xmalloc(sizeof(wchar_t *)); |
345 | *User_Groups = NULL; | |
755494da | 346 | ++User_Groups_Count; |
9fa76e99 AJ |
347 | } |
348 | array = User_Groups; | |
349 | while (*array) { | |
cb0cec99 A |
350 | if (wcscmp(Group, *array) == 0) |
351 | return 0; | |
755494da | 352 | ++array; |
9fa76e99 AJ |
353 | } |
354 | User_Groups = (wchar_t **) xrealloc(User_Groups, sizeof(wchar_t *) * (User_Groups_Count + 1)); | |
355 | User_Groups[User_Groups_Count] = NULL; | |
356 | User_Groups[User_Groups_Count - 1] = (wchar_t *) xmalloc((wcslen(Group) + 1) * sizeof(wchar_t)); | |
357 | wcscpy(User_Groups[User_Groups_Count - 1], Group); | |
755494da | 358 | ++User_Groups_Count; |
9fa76e99 AJ |
359 | |
360 | return 1; | |
361 | } | |
362 | ||
9fa76e99 AJ |
363 | /* returns 0 on match, -1 if no match */ |
364 | static int | |
365 | wccmparray(const wchar_t * str, const wchar_t ** array) | |
366 | { | |
367 | while (*array) { | |
cb0cec99 A |
368 | debug("Windows group: %S, Squid group: %S\n", str, *array); |
369 | if (wcscmp(str, *array) == 0) | |
370 | return 0; | |
755494da | 371 | ++array; |
9fa76e99 AJ |
372 | } |
373 | return -1; | |
374 | } | |
375 | ||
9e6e0372 GS |
376 | /* returns 0 on match, -1 if no match */ |
377 | static int | |
378 | wcstrcmparray(const wchar_t * str, const char **array) | |
379 | { | |
380 | WCHAR wszGroup[GNLEN + 1]; // Unicode Group | |
381 | ||
382 | while (*array) { | |
26ac0430 AJ |
383 | MultiByteToWideChar(CP_ACP, 0, *array, |
384 | strlen(*array) + 1, wszGroup, sizeof(wszGroup) / sizeof(wszGroup[0])); | |
385 | debug("Windows group: %S, Squid group: %S\n", str, wszGroup); | |
386 | if ((use_case_insensitive_compare ? _wcsicmp(str, wszGroup) : wcscmp(str, wszGroup)) == 0) | |
387 | return 0; | |
755494da | 388 | ++array; |
9e6e0372 GS |
389 | } |
390 | return -1; | |
391 | } | |
392 | ||
9fa76e99 AJ |
393 | HRESULT |
394 | Recursive_Memberof(IADs * pObj) | |
395 | { | |
396 | VARIANT var; | |
397 | long lBound, uBound; | |
398 | HRESULT hr; | |
399 | ||
400 | VariantInit(&var); | |
401 | hr = pObj->lpVtbl->Get(pObj, L"memberOf", &var); | |
402 | if (SUCCEEDED(hr)) { | |
cb0cec99 A |
403 | if (VT_BSTR == var.n1.n2.vt) { |
404 | if (add_User_Group(var.n1.n2.n3.bstrVal)) { | |
405 | wchar_t *Group_Path; | |
406 | IADs *pGrp; | |
407 | ||
408 | Group_Path = GetLDAPPath(var.n1.n2.n3.bstrVal, GC_MODE); | |
409 | hr = ADsGetObject(Group_Path, &IID_IADs, (void **) &pGrp); | |
410 | if (SUCCEEDED(hr)) { | |
411 | hr = Recursive_Memberof(pGrp); | |
412 | pGrp->lpVtbl->Release(pGrp); | |
413 | safe_free(Group_Path); | |
414 | Group_Path = GetLDAPPath(var.n1.n2.n3.bstrVal, LDAP_MODE); | |
415 | hr = ADsGetObject(Group_Path, &IID_IADs, (void **) &pGrp); | |
416 | if (SUCCEEDED(hr)) { | |
417 | hr = Recursive_Memberof(pGrp); | |
418 | pGrp->lpVtbl->Release(pGrp); | |
419 | } else | |
420 | debug("Recursive_Memberof: ERROR ADsGetObject for %S failed: %s\n", Group_Path, Get_WIN32_ErrorMessage(hr)); | |
421 | } else | |
422 | debug("Recursive_Memberof: ERROR ADsGetObject for %S failed: %s\n", Group_Path, Get_WIN32_ErrorMessage(hr)); | |
423 | safe_free(Group_Path); | |
424 | } | |
425 | } else { | |
426 | if (SUCCEEDED(SafeArrayGetLBound(V_ARRAY(&var), 1, &lBound)) && | |
427 | SUCCEEDED(SafeArrayGetUBound(V_ARRAY(&var), 1, &uBound))) { | |
428 | VARIANT elem; | |
429 | while (lBound <= uBound) { | |
430 | hr = SafeArrayGetElement(V_ARRAY(&var), &lBound, &elem); | |
431 | if (SUCCEEDED(hr)) { | |
432 | if (add_User_Group(elem.n1.n2.n3.bstrVal)) { | |
433 | wchar_t *Group_Path; | |
434 | IADs *pGrp; | |
435 | ||
436 | Group_Path = GetLDAPPath(elem.n1.n2.n3.bstrVal, GC_MODE); | |
437 | hr = ADsGetObject(Group_Path, &IID_IADs, (void **) &pGrp); | |
438 | if (SUCCEEDED(hr)) { | |
439 | hr = Recursive_Memberof(pGrp); | |
440 | pGrp->lpVtbl->Release(pGrp); | |
441 | safe_free(Group_Path); | |
442 | Group_Path = GetLDAPPath(elem.n1.n2.n3.bstrVal, LDAP_MODE); | |
443 | hr = ADsGetObject(Group_Path, &IID_IADs, (void **) &pGrp); | |
444 | if (SUCCEEDED(hr)) { | |
445 | hr = Recursive_Memberof(pGrp); | |
446 | pGrp->lpVtbl->Release(pGrp); | |
447 | safe_free(Group_Path); | |
448 | } else | |
449 | debug("Recursive_Memberof: ERROR ADsGetObject for %S failed: %s\n", Group_Path, Get_WIN32_ErrorMessage(hr)); | |
450 | } else | |
451 | debug("Recursive_Memberof: ERROR ADsGetObject for %S failed: %s\n", Group_Path, Get_WIN32_ErrorMessage(hr)); | |
452 | safe_free(Group_Path); | |
453 | } | |
454 | VariantClear(&elem); | |
455 | } else { | |
456 | debug("Recursive_Memberof: ERROR SafeArrayGetElement failed: %s\n", Get_WIN32_ErrorMessage(hr)); | |
457 | VariantClear(&elem); | |
458 | } | |
459 | ++lBound; | |
460 | } | |
461 | } else | |
462 | debug("Recursive_Memberof: ERROR SafeArrayGetxBound failed: %s\n", Get_WIN32_ErrorMessage(hr)); | |
463 | } | |
464 | VariantClear(&var); | |
9fa76e99 | 465 | } else { |
cb0cec99 A |
466 | if (hr != E_ADS_PROPERTY_NOT_FOUND) |
467 | debug("Recursive_Memberof: ERROR getting memberof attribute: %s\n", Get_WIN32_ErrorMessage(hr)); | |
9fa76e99 AJ |
468 | } |
469 | return hr; | |
470 | } | |
471 | ||
9fa76e99 AJ |
472 | static wchar_t ** |
473 | build_groups_DN_array(const char **array, char *userdomain) | |
474 | { | |
475 | wchar_t *wc = NULL; | |
476 | int wcsize; | |
477 | int source_group_format; | |
478 | char Group[GNLEN + 1]; | |
479 | ||
480 | wchar_t **wc_array, **entry; | |
481 | ||
482 | entry = wc_array = (wchar_t **) xmalloc((numberofgroups + 1) * sizeof(wchar_t *)); | |
483 | ||
484 | while (*array) { | |
cb0cec99 A |
485 | if (strchr(*array, '/') != NULL) { |
486 | strncpy(Group, *array, GNLEN); | |
487 | source_group_format = ADS_NAME_TYPE_CANONICAL; | |
488 | } else { | |
489 | source_group_format = ADS_NAME_TYPE_NT4; | |
490 | if (strchr(*array, '\\') == NULL) { | |
491 | strcpy(Group, userdomain); | |
492 | strcat(Group, "\\"); | |
493 | strncat(Group, *array, GNLEN - sizeof(userdomain) - 1); | |
494 | } else | |
495 | strncpy(Group, *array, GNLEN); | |
496 | } | |
497 | ||
498 | wcsize = MultiByteToWideChar(CP_ACP, 0, Group, -1, wc, 0); | |
499 | wc = (wchar_t *) xmalloc(wcsize * sizeof(wchar_t)); | |
500 | MultiByteToWideChar(CP_ACP, 0, Group, -1, wc, wcsize); | |
501 | *entry = My_NameTranslate(wc, source_group_format, ADS_NAME_TYPE_1779); | |
502 | safe_free(wc); | |
755494da | 503 | ++array; |
cb0cec99 A |
504 | if (*entry == NULL) { |
505 | debug("build_groups_DN_array: cannot get DN for '%s'.\n", Group); | |
506 | continue; | |
507 | } | |
755494da | 508 | ++entry; |
9fa76e99 AJ |
509 | } |
510 | *entry = NULL; | |
511 | return wc_array; | |
512 | } | |
513 | ||
9e6e0372 GS |
514 | /* returns 1 on success, 0 on failure */ |
515 | int | |
516 | Valid_Local_Groups(char *UserName, const char **Groups) | |
517 | { | |
518 | int result = 0; | |
519 | char *Domain_Separator; | |
9fa76e99 | 520 | WCHAR wszUserName[UNLEN + 1]; /* Unicode user name */ |
9e6e0372 GS |
521 | |
522 | LPLOCALGROUP_USERS_INFO_0 pBuf; | |
523 | LPLOCALGROUP_USERS_INFO_0 pTmpBuf; | |
524 | DWORD dwLevel = 0; | |
525 | DWORD dwFlags = LG_INCLUDE_INDIRECT; | |
526 | DWORD dwPrefMaxLen = -1; | |
527 | DWORD dwEntriesRead = 0; | |
528 | DWORD dwTotalEntries = 0; | |
529 | NET_API_STATUS nStatus; | |
530 | DWORD i; | |
531 | DWORD dwTotalCount = 0; | |
532 | LPBYTE pBufTmp = NULL; | |
533 | ||
534 | if ((Domain_Separator = strchr(UserName, '/')) != NULL) | |
26ac0430 | 535 | *Domain_Separator = '\\'; |
9e6e0372 GS |
536 | |
537 | debug("Valid_Local_Groups: checking group membership of '%s'.\n", UserName); | |
538 | ||
26ac0430 | 539 | /* Convert ANSI User Name and Group to Unicode */ |
9e6e0372 GS |
540 | |
541 | MultiByteToWideChar(CP_ACP, 0, UserName, | |
26ac0430 | 542 | strlen(UserName) + 1, wszUserName, sizeof(wszUserName) / sizeof(wszUserName[0])); |
9e6e0372 GS |
543 | |
544 | /* | |
26ac0430 | 545 | * Call the NetUserGetLocalGroups function |
9e6e0372 | 546 | * specifying information level 0. |
26ac0430 AJ |
547 | * |
548 | * The LG_INCLUDE_INDIRECT flag specifies that the | |
549 | * function should also return the names of the local | |
9e6e0372 GS |
550 | * groups in which the user is indirectly a member. |
551 | */ | |
552 | nStatus = NetUserGetLocalGroups(NULL, | |
26ac0430 AJ |
553 | wszUserName, |
554 | dwLevel, | |
555 | dwFlags, | |
556 | &pBufTmp, | |
557 | dwPrefMaxLen, | |
558 | &dwEntriesRead, | |
559 | &dwTotalEntries); | |
9e6e0372 GS |
560 | pBuf = (LPLOCALGROUP_USERS_INFO_0) pBufTmp; |
561 | /* | |
562 | * If the call succeeds, | |
563 | */ | |
564 | if (nStatus == NERR_Success) { | |
26ac0430 | 565 | if ((pTmpBuf = pBuf) != NULL) { |
eb62585f | 566 | for (i = 0; i < dwEntriesRead; ++i) { |
26ac0430 AJ |
567 | assert(pTmpBuf != NULL); |
568 | if (pTmpBuf == NULL) { | |
569 | result = 0; | |
570 | break; | |
571 | } | |
572 | if (wcstrcmparray(pTmpBuf->lgrui0_name, Groups) == 0) { | |
573 | result = 1; | |
574 | break; | |
575 | } | |
755494da FC |
576 | ++pTmpBuf; |
577 | ++dwTotalCount; | |
26ac0430 AJ |
578 | } |
579 | } | |
9fa76e99 | 580 | } else { |
cb0cec99 | 581 | debug("Valid_Local_Groups: ERROR NetUserGetLocalGroups returned: %s\n", Get_WIN32_ErrorMessage(nStatus)); |
26ac0430 | 582 | result = 0; |
9fa76e99 | 583 | } |
26ac0430 AJ |
584 | /* |
585 | * Free the allocated memory. | |
586 | */ | |
9e6e0372 | 587 | if (pBuf != NULL) |
26ac0430 | 588 | NetApiBufferFree(pBuf); |
9e6e0372 GS |
589 | return result; |
590 | } | |
591 | ||
9e6e0372 GS |
592 | /* returns 1 on success, 0 on failure */ |
593 | int | |
594 | Valid_Global_Groups(char *UserName, const char **Groups) | |
595 | { | |
596 | int result = 0; | |
9fa76e99 | 597 | WCHAR wszUser[DNLEN + UNLEN + 2]; /* Unicode user name */ |
9e6e0372 | 598 | char NTDomain[DNLEN + UNLEN + 2]; |
9fa76e99 | 599 | |
9e6e0372 | 600 | char *domain_qualify = NULL; |
9fa76e99 | 601 | char User[DNLEN + UNLEN + 2]; |
9e6e0372 GS |
602 | size_t j; |
603 | ||
9fa76e99 AJ |
604 | wchar_t *User_DN, *User_LDAP_path, *User_PrimaryGroup; |
605 | wchar_t **wszGroups, **tmp; | |
606 | IADs *pUser; | |
607 | HRESULT hr; | |
9e6e0372 GS |
608 | |
609 | strncpy(NTDomain, UserName, sizeof(NTDomain)); | |
610 | ||
eb62585f | 611 | for (j = 0; j < strlen(NTV_VALID_DOMAIN_SEPARATOR); ++j) { |
26ac0430 AJ |
612 | if ((domain_qualify = strchr(NTDomain, NTV_VALID_DOMAIN_SEPARATOR[j])) != NULL) |
613 | break; | |
9e6e0372 GS |
614 | } |
615 | if (domain_qualify == NULL) { | |
cb0cec99 A |
616 | strncpy(User, DefaultDomain, DNLEN); |
617 | strcat(User, "\\"); | |
618 | strncat(User, UserName, UNLEN); | |
619 | strncpy(NTDomain, DefaultDomain, DNLEN); | |
9e6e0372 | 620 | } else { |
cb0cec99 A |
621 | domain_qualify[0] = '\\'; |
622 | strncpy(User, NTDomain, DNLEN + UNLEN + 2); | |
26ac0430 | 623 | domain_qualify[0] = '\0'; |
9e6e0372 GS |
624 | } |
625 | ||
9fa76e99 | 626 | debug("Valid_Global_Groups: checking group membership of '%s'.\n", User); |
9e6e0372 GS |
627 | |
628 | /* Convert ANSI User Name to Unicode */ | |
629 | ||
630 | MultiByteToWideChar(CP_ACP, 0, User, | |
cb0cec99 A |
631 | strlen(User) + 1, wszUser, |
632 | sizeof(wszUser) / sizeof(wszUser[0])); | |
9e6e0372 | 633 | |
9fa76e99 AJ |
634 | /* Get CN of User */ |
635 | if ((User_DN = My_NameTranslate(wszUser, ADS_NAME_TYPE_NT4, ADS_NAME_TYPE_1779)) == NULL) { | |
cb0cec99 | 636 | debug("Valid_Global_Groups: cannot get DN for '%s'.\n", User); |
26ac0430 | 637 | return result; |
9e6e0372 | 638 | } |
9fa76e99 AJ |
639 | wszGroups = build_groups_DN_array(Groups, NTDomain); |
640 | ||
641 | User_LDAP_path = GetLDAPPath(User_DN, GC_MODE); | |
642 | ||
643 | hr = ADsGetObject(User_LDAP_path, &IID_IADs, (void **) &pUser); | |
644 | if (SUCCEEDED(hr)) { | |
cb0cec99 A |
645 | wchar_t *User_PrimaryGroup_Path; |
646 | IADs *pGrp; | |
647 | ||
648 | User_PrimaryGroup = Get_primaryGroup(pUser); | |
649 | if (User_PrimaryGroup == NULL) | |
650 | debug("Valid_Global_Groups: cannot get Primary Group for '%s'.\n", User); | |
651 | else { | |
652 | add_User_Group(User_PrimaryGroup); | |
653 | User_PrimaryGroup_Path = GetLDAPPath(User_PrimaryGroup, GC_MODE); | |
654 | hr = ADsGetObject(User_PrimaryGroup_Path, &IID_IADs, (void **) &pGrp); | |
655 | if (SUCCEEDED(hr)) { | |
656 | hr = Recursive_Memberof(pGrp); | |
657 | pGrp->lpVtbl->Release(pGrp); | |
658 | safe_free(User_PrimaryGroup_Path); | |
659 | User_PrimaryGroup_Path = GetLDAPPath(User_PrimaryGroup, LDAP_MODE); | |
660 | hr = ADsGetObject(User_PrimaryGroup_Path, &IID_IADs, (void **) &pGrp); | |
661 | if (SUCCEEDED(hr)) { | |
662 | hr = Recursive_Memberof(pGrp); | |
663 | pGrp->lpVtbl->Release(pGrp); | |
664 | } else | |
665 | debug("Valid_Global_Groups: ADsGetObject for %S failed, ERROR: %s\n", User_PrimaryGroup_Path, Get_WIN32_ErrorMessage(hr)); | |
666 | } else | |
667 | debug("Valid_Global_Groups: ADsGetObject for %S failed, ERROR: %s\n", User_PrimaryGroup_Path, Get_WIN32_ErrorMessage(hr)); | |
668 | safe_free(User_PrimaryGroup_Path); | |
669 | } | |
670 | hr = Recursive_Memberof(pUser); | |
671 | pUser->lpVtbl->Release(pUser); | |
672 | safe_free(User_LDAP_path); | |
673 | User_LDAP_path = GetLDAPPath(User_DN, LDAP_MODE); | |
674 | hr = ADsGetObject(User_LDAP_path, &IID_IADs, (void **) &pUser); | |
675 | if (SUCCEEDED(hr)) { | |
676 | hr = Recursive_Memberof(pUser); | |
677 | pUser->lpVtbl->Release(pUser); | |
678 | } else | |
679 | debug("Valid_Global_Groups: ADsGetObject for %S failed, ERROR: %s\n", User_LDAP_path, Get_WIN32_ErrorMessage(hr)); | |
680 | ||
681 | tmp = User_Groups; | |
682 | while (*tmp) { | |
683 | if (wccmparray(*tmp, wszGroups) == 0) { | |
684 | result = 1; | |
685 | break; | |
26ac0430 | 686 | } |
755494da | 687 | ++tmp; |
26ac0430 | 688 | } |
9fa76e99 | 689 | } else |
cb0cec99 | 690 | debug("Valid_Global_Groups: ADsGetObject for %S failed, ERROR: %s\n", User_LDAP_path, Get_WIN32_ErrorMessage(hr)); |
9fa76e99 AJ |
691 | |
692 | safe_free(User_DN); | |
693 | safe_free(User_LDAP_path); | |
694 | safe_free(User_PrimaryGroup); | |
695 | tmp = wszGroups; | |
696 | while (*tmp) { | |
cb0cec99 | 697 | safe_free(*tmp); |
755494da | 698 | ++tmp; |
9e6e0372 | 699 | } |
9fa76e99 | 700 | safe_free(wszGroups); |
9e6e0372 | 701 | |
9fa76e99 AJ |
702 | tmp = User_Groups; |
703 | while (*tmp) { | |
cb0cec99 | 704 | safe_free(*tmp); |
755494da | 705 | ++tmp; |
9e6e0372 | 706 | } |
9fa76e99 AJ |
707 | safe_free(User_Groups); |
708 | User_Groups_Count = 0; | |
709 | ||
9e6e0372 GS |
710 | return result; |
711 | } | |
712 | ||
713 | static void | |
c152a447 | 714 | usage(const char *program) |
9e6e0372 | 715 | { |
9fa76e99 | 716 | fprintf(stderr, "Usage: %s [-D domain][-G][-c][-d][-h]\n" |
26ac0430 | 717 | " -D default user Domain\n" |
cb0cec99 A |
718 | " -G enable Active Directory Global group mode\n" |
719 | " -c use case insensitive compare (local mode only)\n" | |
26ac0430 AJ |
720 | " -d enable debugging\n" |
721 | " -h this message\n", | |
722 | program); | |
9e6e0372 GS |
723 | } |
724 | ||
725 | void | |
726 | process_options(int argc, char *argv[]) | |
727 | { | |
728 | int opt; | |
729 | ||
730 | opterr = 0; | |
731 | while (-1 != (opt = getopt(argc, argv, "D:Gcdh"))) { | |
26ac0430 AJ |
732 | switch (opt) { |
733 | case 'D': | |
734 | DefaultDomain = xstrndup(optarg, DNLEN + 1); | |
735 | strlwr(DefaultDomain); | |
736 | break; | |
737 | case 'G': | |
738 | use_global = 1; | |
739 | break; | |
740 | case 'c': | |
741 | use_case_insensitive_compare = 1; | |
742 | break; | |
743 | case 'd': | |
744 | debug_enabled = 1; | |
745 | break; | |
746 | case 'h': | |
747 | usage(argv[0]); | |
748 | exit(0); | |
749 | case '?': | |
750 | opt = optopt; | |
751 | /* fall thru to default */ | |
752 | default: | |
c152a447 | 753 | fprintf(stderr, "%s: FATAL: Unknown option: -%c. Exiting\n", program_name, opt); |
26ac0430 AJ |
754 | usage(argv[0]); |
755 | exit(1); | |
756 | break; /* not reached */ | |
757 | } | |
9e6e0372 GS |
758 | } |
759 | return; | |
760 | } | |
761 | ||
9e6e0372 GS |
762 | int |
763 | main(int argc, char *argv[]) | |
764 | { | |
765 | char *p; | |
c152a447 | 766 | char buf[HELPER_INPUT_BUFFER]; |
9e6e0372 GS |
767 | char *username; |
768 | char *group; | |
9e6e0372 GS |
769 | const char *groups[512]; |
770 | int n; | |
771 | ||
772 | if (argc > 0) { /* should always be true */ | |
c152a447 AJ |
773 | program_name = strrchr(argv[0], '/'); |
774 | if (program_name == NULL) | |
775 | program_name = argv[0]; | |
9e6e0372 | 776 | } else { |
c152a447 | 777 | program_name = "(unknown)"; |
9e6e0372 GS |
778 | } |
779 | mypid = getpid(); | |
780 | ||
781 | setbuf(stdout, NULL); | |
782 | setbuf(stderr, NULL); | |
783 | ||
784 | /* Check Command Line */ | |
785 | process_options(argc, argv); | |
786 | ||
787 | if (use_global) { | |
26ac0430 | 788 | if ((machinedomain = GetDomainName()) == NULL) { |
c152a447 | 789 | fprintf(stderr, "%s: FATAL: Can't read machine domain\n", program_name); |
26ac0430 AJ |
790 | exit(1); |
791 | } | |
792 | strlwr(machinedomain); | |
793 | if (!DefaultDomain) | |
794 | DefaultDomain = xstrdup(machinedomain); | |
9e6e0372 GS |
795 | } |
796 | debug("External ACL win32 group helper build " __DATE__ ", " __TIME__ | |
26ac0430 | 797 | " starting up...\n"); |
9e6e0372 | 798 | if (use_global) |
26ac0430 | 799 | debug("Domain Global group mode enabled using '%s' as default domain.\n", DefaultDomain); |
9e6e0372 | 800 | if (use_case_insensitive_compare) |
26ac0430 | 801 | debug("Warning: running in case insensitive mode !!!\n"); |
9e6e0372 | 802 | |
9fa76e99 AJ |
803 | atexit(CloseCOM); |
804 | ||
9e6e0372 | 805 | /* Main Loop */ |
c152a447 | 806 | while (fgets(buf, HELPER_INPUT_BUFFER, stdin)) { |
26ac0430 AJ |
807 | if (NULL == strchr(buf, '\n')) { |
808 | /* too large message received.. skip and deny */ | |
809 | fprintf(stderr, "%s: ERROR: Too large: %s\n", argv[0], buf); | |
c152a447 | 810 | while (fgets(buf, HELPER_INPUT_BUFFER, stdin)) { |
26ac0430 AJ |
811 | fprintf(stderr, "%s: ERROR: Too large..: %s\n", argv[0], buf); |
812 | if (strchr(buf, '\n') != NULL) | |
813 | break; | |
814 | } | |
c152a447 AJ |
815 | SEND_ERR("Invalid Request. Too Long."); |
816 | continue; | |
26ac0430 AJ |
817 | } |
818 | if ((p = strchr(buf, '\n')) != NULL) | |
819 | *p = '\0'; /* strip \n */ | |
820 | if ((p = strchr(buf, '\r')) != NULL) | |
821 | *p = '\0'; /* strip \r */ | |
822 | ||
823 | debug("Got '%s' from Squid (length: %d).\n", buf, strlen(buf)); | |
824 | ||
825 | if (buf[0] == '\0') { | |
c152a447 AJ |
826 | SEND_ERR("Invalid Request. No Input."); |
827 | continue; | |
26ac0430 AJ |
828 | } |
829 | username = strtok(buf, " "); | |
eb62585f | 830 | for (n = 0; (group = strtok(NULL, " ")) != NULL; ++n) { |
26ac0430 AJ |
831 | rfc1738_unescape(group); |
832 | groups[n] = group; | |
833 | } | |
834 | groups[n] = NULL; | |
cb0cec99 | 835 | numberofgroups = n; |
26ac0430 AJ |
836 | |
837 | if (NULL == username) { | |
c152a447 AJ |
838 | SEND_ERR("Invalid Request. No Username."); |
839 | continue; | |
26ac0430 AJ |
840 | } |
841 | rfc1738_unescape(username); | |
842 | ||
843 | if ((use_global ? Valid_Global_Groups(username, groups) : Valid_Local_Groups(username, groups))) { | |
c152a447 | 844 | SEND_OK(""); |
26ac0430 | 845 | } else { |
c152a447 | 846 | SEND_ERR(""); |
26ac0430 AJ |
847 | } |
848 | err = 0; | |
9e6e0372 GS |
849 | } |
850 | return 0; | |
851 | } |