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

 

Introduction

Documentation

Support

Miscellaneous