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