ext_lm_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  * mswin_check_lm_group: lookup group membership in a Windows NT/2000 domain
11  *
12  * (C)2002,2005 Guido Serassio - Acme Consulting S.r.l.
13  *
14  * Authors:
15  * Guido Serassio <guido.serassio@acmeconsulting.it>
16  * Acme Consulting S.r.l., Italy <http://www.acmeconsulting.it>
17  *
18  * With contributions from others mentioned in the change history section
19  * below.
20  *
21  * In part based on check_group by Rodrigo Albani de Campos.
22  *
23  * Dependencies: Windows NT4 SP4 and later.
24  *
25  * This program is free software; you can redistribute it and/or modify
26  * it under the terms of the GNU General Public License as published by
27  * the Free Software Foundation; either version 2 of the License, or
28  * (at your option) any later version.
29  *
30  * This program is distributed in the hope that it will be useful,
31  * but WITHOUT ANY WARRANTY; without even the implied warranty of
32  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
33  * GNU General Public License for more details.
34  *
35  * You should have received a copy of the GNU General Public License
36  * along with this program; if not, write to the Free Software
37  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
38  *
39  * History:
40  *
41  * Version 1.22
42  * 08-07-2005 Guido Serassio
43  * Added -P option for force usage of PDCs for group validation.
44  * Added support for '/' char as domain separator.
45  * Fixed Bugzilla #1336.
46  * Version 1.21
47  * 23-04-2005 Guido Serassio
48  * Added -D option for specify default user's domain.
49  * Version 1.20.1
50  * 15-08-2004 Guido Serassio
51  * Helper protocol changed to use URL escaped strings in Squid-3.0
52  * (Original work of Henrik Nordstrom)
53  * Version 1.20
54  * 13-06-2004 Guido Serassio
55  * Added support for running on a Domain Controller.
56  * Version 1.10
57  * 01-05-2003 Guido Serassio
58  * Added option for case insensitive group name comparison.
59  * More debug info.
60  * Updated documentation.
61  * Segfault bug fix (Bugzilla #574)
62  * Version 1.0
63  * 24-06-2002 Guido Serassio
64  * Using the main function from check_group and sections
65  * from wbinfo wrote win32_group
66  *
67  * This is a helper for the external ACL interface for Squid Cache
68  *
69  * It reads from the standard input the domain username and a list of
70  * groups and tries to match it against the groups membership of the
71  * specified username.
72  *
73  * Returns `OK' if the user belongs to a group or `ERR' otherwise, as
74  * described on http://devel.squid-cache.org/external_acl/config.html
75  *
76  */
77 
78 #include "squid.h"
80 #include "rfc1738.h"
81 #include "util.h"
82 
83 #if _SQUID_CYGWIN_
84 #include <cwchar>
85 int _wcsicmp(const wchar_t *, const wchar_t *);
86 #endif
87 
88 #undef assert
89 #include <cassert>
90 #include <cctype>
91 #include <cstring>
92 #if HAVE_GETOPT_H
93 #include <getopt.h>
94 #endif
95 #include <windows.h>
96 #include <lm.h>
97 #include <ntsecapi.h>
98 
99 int use_global = 0;
100 int use_PDC_only = 0;
101 const char *program_name;
102 pid_t mypid;
105 char *DefaultDomain = nullptr;
106 const char NTV_VALID_DOMAIN_SEPARATOR[] = "\\/";
107 
108 static char *
109 AllocStrFromLSAStr(LSA_UNICODE_STRING LsaStr)
110 {
111  size_t len;
112  static char *target;
113 
114  len = LsaStr.Length / sizeof(WCHAR) + 1;
115 
116  /* allocate buffer for str + null termination */
117  safe_free(target);
118  target = (char *) xmalloc(len);
119  if (target == NULL)
120  return nullptr;
121 
122  /* copy unicode buffer */
123  WideCharToMultiByte(CP_ACP, 0, LsaStr.Buffer, LsaStr.Length, target, len, nullptr, nullptr);
124 
125  /* add null termination */
126  target[len - 1] = '\0';
127  return target;
128 }
129 
130 static char *
132 {
133  LSA_HANDLE PolicyHandle;
134  LSA_OBJECT_ATTRIBUTES ObjectAttributes;
135  NTSTATUS status;
136  PPOLICY_PRIMARY_DOMAIN_INFO ppdiDomainInfo;
137  PWKSTA_INFO_100 pwkiWorkstationInfo;
138  DWORD netret;
139  char *DomainName = nullptr;
140 
141  /*
142  * Always initialize the object attributes to all zeroes.
143  */
144  memset(&ObjectAttributes, '\0', sizeof(ObjectAttributes));
145 
146  /*
147  * You need the local workstation name. Use NetWkstaGetInfo at level
148  * 100 to retrieve a WKSTA_INFO_100 structure.
149  *
150  * The wki100_computername field contains a pointer to a UNICODE
151  * string containing the local computer name.
152  */
153  netret = NetWkstaGetInfo(nullptr, 100, (LPBYTE *) & pwkiWorkstationInfo);
154  if (netret == NERR_Success) {
155  /*
156  * We have the workstation name in:
157  * pwkiWorkstationInfo->wki100_computername
158  *
159  * Next, open the policy object for the local system using
160  * the LsaOpenPolicy function.
161  */
162  status = LsaOpenPolicy(
163  nullptr,
164  &ObjectAttributes,
165  GENERIC_READ | POLICY_VIEW_LOCAL_INFORMATION,
166  &PolicyHandle
167  );
168 
169  /*
170  * Error checking.
171  */
172  if (status) {
173  debug("OpenPolicy Error: %ld\n", status);
174  } else {
175 
176  /*
177  * You have a handle to the policy object. Now, get the
178  * domain information using LsaQueryInformationPolicy.
179  */
180  status = LsaQueryInformationPolicy(PolicyHandle,
181  PolicyPrimaryDomainInformation,
182  (PVOID *) & ppdiDomainInfo);
183  if (status) {
184  debug("LsaQueryInformationPolicy Error: %ld\n", status);
185  } else {
186 
187  /* Get name in usable format */
188  DomainName = AllocStrFromLSAStr(ppdiDomainInfo->Name);
189 
190  /*
191  * Check the Sid pointer, if it is null, the
192  * workstation is either a stand-alone computer
193  * or a member of a workgroup.
194  */
195  if (ppdiDomainInfo->Sid) {
196 
197  /*
198  * Member of a domain. Display it in debug mode.
199  */
200  debug("Member of Domain %s\n", DomainName);
201  } else {
202  DomainName = nullptr;
203  }
204  }
205  }
206 
207  /*
208  * Clean up all the memory buffers created by the LSA and
209  * Net* APIs.
210  */
211  NetApiBufferFree(pwkiWorkstationInfo);
212  LsaFreeMemory((LPVOID) ppdiDomainInfo);
213  } else
214  debug("NetWkstaGetInfo Error: %ld\n", netret);
215  return DomainName;
216 }
217 
218 /* returns 0 on match, -1 if no match */
219 static int
220 wcstrcmparray(const wchar_t * str, const char **array)
221 {
222  WCHAR wszGroup[GNLEN + 1]; // Unicode Group
223 
224  while (*array) {
225  MultiByteToWideChar(CP_ACP, 0, *array,
226  strlen(*array) + 1, wszGroup, sizeof(wszGroup) / sizeof(wszGroup[0]));
227  debug("Windows group: %S, Squid group: %S\n", str, wszGroup);
228  if ((use_case_insensitive_compare ? _wcsicmp(str, wszGroup) : wcscmp(str, wszGroup)) == 0)
229  return 0;
230  ++array;
231  }
232  return -1;
233 }
234 
235 /* returns 1 on success, 0 on failure */
236 static int
237 Valid_Local_Groups(char *UserName, const char **Groups)
238 {
239  int result = 0;
240  char *Domain_Separator;
241  WCHAR wszUserName[UNLEN + 1]; // Unicode user name
242 
243  LPLOCALGROUP_USERS_INFO_0 pBuf = nullptr;
244  LPLOCALGROUP_USERS_INFO_0 pTmpBuf;
245  DWORD dwLevel = 0;
246  DWORD dwFlags = LG_INCLUDE_INDIRECT;
247  DWORD dwPrefMaxLen = -1;
248  DWORD dwEntriesRead = 0;
249  DWORD dwTotalEntries = 0;
250  NET_API_STATUS nStatus;
251  DWORD i;
252  DWORD dwTotalCount = 0;
253 
254  if ((Domain_Separator = strchr(UserName, '/')) != NULL)
255  *Domain_Separator = '\\';
256 
257  debug("Valid_Local_Groups: checking group membership of '%s'.\n", UserName);
258 
259  /* Convert ANSI User Name and Group to Unicode */
260 
261  MultiByteToWideChar(CP_ACP, 0, UserName,
262  strlen(UserName) + 1, wszUserName, sizeof(wszUserName) / sizeof(wszUserName[0]));
263 
264  /*
265  * Call the NetUserGetLocalGroups function
266  * specifying information level 0.
267  *
268  * The LG_INCLUDE_INDIRECT flag specifies that the
269  * function should also return the names of the local
270  * groups in which the user is indirectly a member.
271  */
272  nStatus = NetUserGetLocalGroups(
273  nullptr,
274  wszUserName,
275  dwLevel,
276  dwFlags,
277  (LPBYTE *) & pBuf,
278  dwPrefMaxLen,
279  &dwEntriesRead,
280  &dwTotalEntries);
281  /*
282  * If the call succeeds,
283  */
284  if (nStatus == NERR_Success) {
285  if ((pTmpBuf = pBuf) != NULL) {
286  for (i = 0; i < dwEntriesRead; ++i) {
287  assert(pTmpBuf != NULL);
288  if (pTmpBuf == NULL) {
289  result = 0;
290  break;
291  }
292  if (wcstrcmparray(pTmpBuf->lgrui0_name, Groups) == 0) {
293  result = 1;
294  break;
295  }
296  ++pTmpBuf;
297  ++dwTotalCount;
298  }
299  }
300  } else
301  result = 0;
302  /*
303  * Free the allocated memory.
304  */
305  if (pBuf != NULL)
306  NetApiBufferFree(pBuf);
307  return result;
308 }
309 
310 /* returns 1 on success, 0 on failure */
311 static int
312 Valid_Global_Groups(char *UserName, const char **Groups)
313 {
314  int result = 0;
315  WCHAR wszUserName[UNLEN + 1]; // Unicode user name
316 
317  WCHAR wszLocalDomain[DNLEN + 1]; // Unicode Local Domain
318 
319  WCHAR wszUserDomain[DNLEN + 1]; // Unicode User Domain
320 
321  char NTDomain[DNLEN + UNLEN + 2];
322  char *domain_qualify;
323  char User[UNLEN + 1];
324  size_t j;
325 
326  LPWSTR LclDCptr = nullptr;
327  LPWSTR UsrDCptr = nullptr;
328  LPGROUP_USERS_INFO_0 pUsrBuf = nullptr;
329  LPGROUP_USERS_INFO_0 pTmpBuf;
330  LPSERVER_INFO_101 pSrvBuf = nullptr;
331  DWORD dwLevel = 0;
332  DWORD dwPrefMaxLen = -1;
333  DWORD dwEntriesRead = 0;
334  DWORD dwTotalEntries = 0;
335  NET_API_STATUS nStatus;
336  DWORD i;
337  DWORD dwTotalCount = 0;
338 
339  xstrncpy(NTDomain, UserName, sizeof(NTDomain));
340 
341  for (j = 0; j < strlen(NTV_VALID_DOMAIN_SEPARATOR); ++j) {
342  if ((domain_qualify = strchr(NTDomain, NTV_VALID_DOMAIN_SEPARATOR[j])) != NULL)
343  break;
344  }
345  if (domain_qualify == NULL) {
346  xstrncpy(User, NTDomain, sizeof(User));
347  xstrncpy(NTDomain, DefaultDomain, sizeof(NTDomain));
348  } else {
349  xstrncpy(User, domain_qualify + 1, sizeof(User));
350  domain_qualify[0] = '\0';
351  strlwr(NTDomain);
352  }
353 
354  debug("Valid_Global_Groups: checking group membership of '%s\\%s'.\n", NTDomain, User);
355 
356  /* Convert ANSI User Name and Group to Unicode */
357 
358  MultiByteToWideChar(CP_ACP, 0, User,
359  strlen(User) + 1, wszUserName,
360  sizeof(wszUserName) / sizeof(wszUserName[0]));
361  MultiByteToWideChar(CP_ACP, 0, machinedomain,
362  strlen(machinedomain) + 1, wszLocalDomain, sizeof(wszLocalDomain) / sizeof(wszLocalDomain[0]));
363 
364  /* Call the NetServerGetInfo function for local computer, specifying level 101. */
365  dwLevel = 101;
366  nStatus = NetServerGetInfo(nullptr, dwLevel, (LPBYTE *) & pSrvBuf);
367 
368  if (nStatus == NERR_Success) {
369  /* Check if we are running on a Domain Controller */
370  if ((pSrvBuf->sv101_type & SV_TYPE_DOMAIN_CTRL) ||
371  (pSrvBuf->sv101_type & SV_TYPE_DOMAIN_BAKCTRL)) {
372  LclDCptr = nullptr;
373  debug("Running on a DC.\n");
374  } else
375  nStatus = (use_PDC_only ? NetGetDCName(nullptr, wszLocalDomain, (LPBYTE *) & LclDCptr) : NetGetAnyDCName(nullptr, wszLocalDomain, (LPBYTE *) & LclDCptr));
376  } else {
377  fprintf(stderr, "%s: ERROR: NetServerGetInfo() failed.'\n", program_name);
378  if (pSrvBuf != NULL)
379  NetApiBufferFree(pSrvBuf);
380  return result;
381  }
382 
383  if (nStatus == NERR_Success) {
384  debug("Using '%S' as DC for '%S' local domain.\n", LclDCptr, wszLocalDomain);
385 
386  if (strcmp(NTDomain, machinedomain) != 0) {
387  MultiByteToWideChar(CP_ACP, 0, NTDomain,
388  strlen(NTDomain) + 1, wszUserDomain, sizeof(wszUserDomain) / sizeof(wszUserDomain[0]));
389  nStatus = (use_PDC_only ? NetGetDCName(LclDCptr, wszUserDomain, (LPBYTE *) & UsrDCptr) : NetGetAnyDCName(LclDCptr, wszUserDomain, (LPBYTE *) & UsrDCptr));
390  if (nStatus != NERR_Success) {
391  fprintf(stderr, "%s: ERROR: Can't find DC for user's domain '%s'\n", program_name, NTDomain);
392  if (pSrvBuf != NULL)
393  NetApiBufferFree(pSrvBuf);
394  if (LclDCptr != NULL)
395  NetApiBufferFree((LPVOID) LclDCptr);
396  if (UsrDCptr != NULL)
397  NetApiBufferFree((LPVOID) UsrDCptr);
398  return result;
399  }
400  } else
401  UsrDCptr = LclDCptr;
402 
403  debug("Using '%S' as DC for '%s' user's domain.\n", UsrDCptr, NTDomain);
404  /*
405  * Call the NetUserGetGroups function
406  * specifying information level 0.
407  */
408  dwLevel = 0;
409  nStatus = NetUserGetGroups(UsrDCptr,
410  wszUserName,
411  dwLevel,
412  (LPBYTE *) & pUsrBuf,
413  dwPrefMaxLen,
414  &dwEntriesRead,
415  &dwTotalEntries);
416  /*
417  * If the call succeeds,
418  */
419  if (nStatus == NERR_Success) {
420  if ((pTmpBuf = pUsrBuf) != NULL) {
421  for (i = 0; i < dwEntriesRead; ++i) {
422  assert(pTmpBuf != NULL);
423  if (pTmpBuf == NULL) {
424  result = 0;
425  break;
426  }
427  if (wcstrcmparray(pTmpBuf->grui0_name, Groups) == 0) {
428  result = 1;
429  break;
430  }
431  ++pTmpBuf;
432  ++dwTotalCount;
433  }
434  }
435  } else {
436  result = 0;
437  fprintf(stderr, "%s: ERROR: NetUserGetGroups() failed.'\n", program_name);
438  }
439  } else {
440  fprintf(stderr, "%s: ERROR: Can't find DC for local domain '%s'\n", program_name, machinedomain);
441  }
442  /*
443  * Free the allocated memory.
444  */
445  if (pSrvBuf != NULL)
446  NetApiBufferFree(pSrvBuf);
447  if (pUsrBuf != NULL)
448  NetApiBufferFree(pUsrBuf);
449  if ((UsrDCptr != NULL) && (UsrDCptr != LclDCptr))
450  NetApiBufferFree((LPVOID) UsrDCptr);
451  if (LclDCptr != NULL)
452  NetApiBufferFree((LPVOID) LclDCptr);
453  return result;
454 }
455 
456 static void
457 usage(const char *program)
458 {
459  fprintf(stderr, "Usage: %s [-D domain][-G][-P][-c][-d][-h]\n"
460  " -D default user Domain\n"
461  " -G enable Domain Global group mode\n"
462  " -P use ONLY PDCs for group validation\n"
463  " -c use case insensitive compare\n"
464  " -d enable debugging\n"
465  " -h this message\n",
466  program);
467 }
468 
469 static void
470 process_options(int argc, char *argv[])
471 {
472  int opt;
473 
474  opterr = 0;
475  while (-1 != (opt = getopt(argc, argv, "D:GPcdh"))) {
476  switch (opt) {
477  case 'D':
478  DefaultDomain = xstrndup(optarg, DNLEN + 1);
479  strlwr(DefaultDomain);
480  break;
481  case 'G':
482  use_global = 1;
483  break;
484  case 'P':
485  use_PDC_only = 1;
486  break;
487  case 'c':
489  break;
490  case 'd':
491  debug_enabled = 1;
492  break;
493  case 'h':
494  usage(argv[0]);
495  exit(EXIT_SUCCESS);
496  case '?':
497  opt = optopt;
498  [[fallthrough]];
499  default:
500  fprintf(stderr, "%s: FATAL: Unknown option: -%c. Exiting\n", program_name, opt);
501  usage(argv[0]);
502  exit(EXIT_FAILURE);
503  break; /* not reached */
504  }
505  }
506  return;
507 }
508 
509 int
510 main(int argc, char *argv[])
511 {
512  char *p;
513  char buf[HELPER_INPUT_BUFFER];
514  char *username;
515  char *group;
516  const char *groups[512];
517  int n;
518 
519  if (argc > 0) { /* should always be true */
520  program_name = strrchr(argv[0], '/');
521  if (program_name == NULL)
522  program_name = argv[0];
523  } else {
524  program_name = "(unknown)";
525  }
526  mypid = getpid();
527 
528  setbuf(stdout, nullptr);
529  setbuf(stderr, nullptr);
530 
531  /* Check Command Line */
532  process_options(argc, argv);
533 
534  if (use_global) {
535  if ((machinedomain = GetDomainName()) == NULL) {
536  fprintf(stderr, "%s: FATAL: Can't read machine domain\n", program_name);
537  exit(EXIT_FAILURE);
538  }
539  strlwr(machinedomain);
540  if (!DefaultDomain)
542  }
543  debug("%s " VERSION " " SQUID_BUILD_INFO " starting up...\n", argv[0]);
544  if (use_global) {
545  debug("Domain Global group mode enabled using '%s' as default domain.\n", DefaultDomain);
546  }
548  debug("Warning: running in case insensitive mode !!!\n");
549  }
550  if (use_PDC_only) {
551  debug("Warning: using only PDCs for group validation !!!\n");
552  }
553 
554  /* Main Loop */
555  while (fgets(buf, HELPER_INPUT_BUFFER, stdin)) {
556  if (NULL == strchr(buf, '\n')) {
557  /* too large message received.. skip and deny */
558  debug("%s: ERROR: Too large: %s\n", argv[0], buf);
559  while (fgets(buf, HELPER_INPUT_BUFFER, stdin)) {
560  debug("%s: ERROR: Too large..: %s\n", argv[0], buf);
561  if (strchr(buf, '\n') != NULL)
562  break;
563  }
564  SEND_BH(HLP_MSG("Input Too Long."));
565  continue;
566  }
567  if ((p = strchr(buf, '\n')) != NULL)
568  *p = '\0'; /* strip \n */
569  if ((p = strchr(buf, '\r')) != NULL)
570  *p = '\0'; /* strip \r */
571 
572  debug("Got '%s' from Squid (length: %d).\n", buf, strlen(buf));
573 
574  if (buf[0] == '\0') {
575  SEND_BH(HLP_MSG("Invalid Request."));
576  continue;
577  }
578  username = strtok(buf, " ");
579  for (n = 0; (group = strtok(nullptr, " ")) != NULL; ++n) {
580  rfc1738_unescape(group);
581  groups[n] = group;
582  }
583  groups[n] = nullptr;
584 
585  if (NULL == username) {
586  SEND_BH(HLP_MSG("Invalid Request. No Username."));
587  continue;
588  }
589  rfc1738_unescape(username);
590 
591  if ((use_global ? Valid_Global_Groups(username, groups) : Valid_Local_Groups(username, groups))) {
592  SEND_OK("");
593  } else {
594  SEND_ERR("");
595  }
596  }
597  return EXIT_SUCCESS;
598 }
599 
static int Valid_Local_Groups(char *UserName, const char **Groups)
static char * GetDomainName(void)
#define xmalloc
int opterr
Definition: getopt.c:47
static void process_options(int argc, char *argv[])
void debug(const char *format,...)
Definition: debug.cc:19
static char * AllocStrFromLSAStr(LSA_UNICODE_STRING LsaStr)
const char NTV_VALID_DOMAIN_SEPARATOR[]
#define xstrdup
char * optarg
Definition: getopt.c:51
char * xstrncpy(char *dst, const char *src, size_t n)
Definition: xstring.cc:37
int getopt(int nargc, char *const *nargv, const char *ostr)
Definition: getopt.c:62
static void usage(const char *program)
#define NULL
Definition: types.h:145
void rfc1738_unescape(char *url)
Definition: rfc1738.c:146
char * machinedomain
#define SEND_ERR(x)
const char * program_name
int debug_enabled
Definition: debug.cc:13
#define safe_free(x)
Definition: xalloc.h:73
int main(int argc, char *argv[])
#define assert(EX)
Definition: assert.h:17
#define SEND_BH(x)
int use_PDC_only
char * xstrndup(const char *s, size_t n)
Definition: xstring.cc:56
SBuf DomainName
Definition: forward.h:41
#define HELPER_INPUT_BUFFER
Definition: UserRequest.cc:24
std::vector< ServiceGroupPointer > Groups
int optopt
Definition: getopt.c:49
pid_t mypid
char * DefaultDomain
int use_case_insensitive_compare
int use_global
#define VERSION
#define HLP_MSG(text)
static int wcstrcmparray(const wchar_t *str, const char **array)
static int Valid_Global_Groups(char *UserName, const char **Groups)
#define SEND_OK(x)

 

Introduction

Documentation

Support

Miscellaneous