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