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