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