]> git.ipfire.org Git - thirdparty/squid.git/blame - helpers/external_acl/AD_group/ext_ad_group_acl.cc
Boilerplate: update copyright blurbs for Squid tools
[thirdparty/squid.git] / helpers / external_acl / AD_group / ext_ad_group_acl.cc
CommitLineData
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>
66int _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
87enum ADSI_PATH {
88 LDAP_MODE,
89 GC_MODE
90} ADSI_Path;
91
9e6e0372 92int use_global = 0;
c152a447 93char *program_name;
9e6e0372
GS
94pid_t mypid;
95char *machinedomain;
96int use_case_insensitive_compare = 0;
97char *DefaultDomain = NULL;
98const char NTV_VALID_DOMAIN_SEPARATOR[] = "\\/";
9fa76e99
AJ
99int numberofgroups = 0;
100int WIN32_COM_initialized = 0;
101char *WIN32_ErrorMessage = NULL;
102wchar_t **User_Groups;
103int User_Groups_Count = 0;
9e6e0372 104
9fa76e99
AJ
105wchar_t *My_NameTranslate(wchar_t *, int, int);
106char *Get_WIN32_ErrorMessage(HRESULT);
107
9fa76e99
AJ
108void
109CloseCOM(void)
110{
111 if (WIN32_COM_initialized == 1)
cb0cec99 112 CoUninitialize();
9fa76e99
AJ
113}
114
9fa76e99
AJ
115HRESULT
116GetLPBYTEtoOctetString(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
145wchar_t *
146Get_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
203char *
204Get_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
217wchar_t *
218My_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
272wchar_t *
273GetLDAPPath(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
288char *
289GetDomainName(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
338int
339add_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 */
364static int
365wccmparray(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 */
377static int
378wcstrcmparray(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
393HRESULT
394Recursive_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
472static wchar_t **
473build_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 */
515int
516Valid_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 */
593int
594Valid_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
713static void
c152a447 714usage(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
725void
726process_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
762int
763main(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}