]> git.ipfire.org Git - thirdparty/squid.git/blame - helpers/external_acl/AD_group/ext_ad_group_acl.cc
SourceFormat Enforcement
[thirdparty/squid.git] / helpers / external_acl / AD_group / ext_ad_group_acl.cc
CommitLineData
ca02e0ec 1/*
bde978a6 2 * Copyright (C) 1996-2015 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"
c152a447
AJ
69#include "helpers/defines.h"
70#include "include/util.h"
71
be266cb2 72#if _SQUID_CYGWIN_
9e6e0372
GS
73#include <wchar.h>
74int _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
95enum ADSI_PATH {
96 LDAP_MODE,
97 GC_MODE
98} ADSI_Path;
99
9e6e0372 100int use_global = 0;
c152a447 101char *program_name;
9e6e0372
GS
102pid_t mypid;
103char *machinedomain;
104int use_case_insensitive_compare = 0;
105char *DefaultDomain = NULL;
106const char NTV_VALID_DOMAIN_SEPARATOR[] = "\\/";
9fa76e99
AJ
107int numberofgroups = 0;
108int WIN32_COM_initialized = 0;
109char *WIN32_ErrorMessage = NULL;
110wchar_t **User_Groups;
111int User_Groups_Count = 0;
9e6e0372 112
9fa76e99
AJ
113wchar_t *My_NameTranslate(wchar_t *, int, int);
114char *Get_WIN32_ErrorMessage(HRESULT);
115
9fa76e99
AJ
116void
117CloseCOM(void)
118{
119 if (WIN32_COM_initialized == 1)
cb0cec99 120 CoUninitialize();
9fa76e99
AJ
121}
122
9fa76e99
AJ
123HRESULT
124GetLPBYTEtoOctetString(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
153wchar_t *
154Get_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
211char *
212Get_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
225wchar_t *
226My_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
280wchar_t *
281GetLDAPPath(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
296char *
297GetDomainName(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
346int
347add_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 */
372static int
373wccmparray(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 */
385static int
386wcstrcmparray(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
401HRESULT
402Recursive_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
480static wchar_t **
481build_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 */
523int
524Valid_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 */
601int
602Valid_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
721static void
c152a447 722usage(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
733void
734process_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
770int
771main(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
GS
803 }
804 debug("External ACL win32 group helper build " __DATE__ ", " __TIME__
26ac0430 805 " starting up...\n");
9e6e0372 806 if (use_global)
26ac0430 807 debug("Domain Global group mode enabled using '%s' as default domain.\n", DefaultDomain);
9e6e0372 808 if (use_case_insensitive_compare)
26ac0430 809 debug("Warning: running in case insensitive mode !!!\n");
9e6e0372 810
9fa76e99
AJ
811 atexit(CloseCOM);
812
9e6e0372 813 /* Main Loop */
c152a447 814 while (fgets(buf, HELPER_INPUT_BUFFER, stdin)) {
26ac0430
AJ
815 if (NULL == strchr(buf, '\n')) {
816 /* too large message received.. skip and deny */
817 fprintf(stderr, "%s: ERROR: Too large: %s\n", argv[0], buf);
c152a447 818 while (fgets(buf, HELPER_INPUT_BUFFER, stdin)) {
26ac0430
AJ
819 fprintf(stderr, "%s: ERROR: Too large..: %s\n", argv[0], buf);
820 if (strchr(buf, '\n') != NULL)
821 break;
822 }
c152a447
AJ
823 SEND_ERR("Invalid Request. Too Long.");
824 continue;
26ac0430
AJ
825 }
826 if ((p = strchr(buf, '\n')) != NULL)
f53969cc 827 *p = '\0'; /* strip \n */
26ac0430 828 if ((p = strchr(buf, '\r')) != NULL)
f53969cc 829 *p = '\0'; /* strip \r */
26ac0430
AJ
830
831 debug("Got '%s' from Squid (length: %d).\n", buf, strlen(buf));
832
833 if (buf[0] == '\0') {
c152a447
AJ
834 SEND_ERR("Invalid Request. No Input.");
835 continue;
26ac0430
AJ
836 }
837 username = strtok(buf, " ");
eb62585f 838 for (n = 0; (group = strtok(NULL, " ")) != NULL; ++n) {
26ac0430
AJ
839 rfc1738_unescape(group);
840 groups[n] = group;
841 }
842 groups[n] = NULL;
cb0cec99 843 numberofgroups = n;
26ac0430
AJ
844
845 if (NULL == username) {
c152a447
AJ
846 SEND_ERR("Invalid Request. No Username.");
847 continue;
26ac0430
AJ
848 }
849 rfc1738_unescape(username);
850
851 if ((use_global ? Valid_Global_Groups(username, groups) : Valid_Local_Groups(username, groups))) {
c152a447 852 SEND_OK("");
26ac0430 853 } else {
c152a447 854 SEND_ERR("");
26ac0430
AJ
855 }
856 err = 0;
9e6e0372
GS
857 }
858 return 0;
859}
f53969cc 860