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>
85int _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
99int use_global = 0;
101const char *program_name;
102pid_t mypid;
105char *DefaultDomain = nullptr;
106const char NTV_VALID_DOMAIN_SEPARATOR[] = "\\/";
107
108char *
109AllocStrFromLSAStr(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
130char *
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 */
219static int
220wcstrcmparray(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 */
236int
237Valid_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 */
311int
312Valid_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
456static void
457usage(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
469void
470process_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
509int
510main(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
#define assert(EX)
Definition: assert.h:17
#define HELPER_INPUT_BUFFER
Definition: UserRequest.cc:24
int debug_enabled
Definition: debug.cc:13
void debug(const char *format,...)
Definition: debug.cc:19
int main(int argc, char *argv[])
pid_t mypid
static int wcstrcmparray(const wchar_t *str, const char **array)
char * AllocStrFromLSAStr(LSA_UNICODE_STRING LsaStr)
char * machinedomain
char * DefaultDomain
int use_global
const char NTV_VALID_DOMAIN_SEPARATOR[]
void process_options(int argc, char *argv[])
char * GetDomainName(void)
int use_PDC_only
int use_case_insensitive_compare
int Valid_Global_Groups(char *UserName, const char **Groups)
int Valid_Local_Groups(char *UserName, const char **Groups)
const char * program_name
static void usage(const char *program)
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
std::vector< ServiceGroupPointer > Groups
#define VERSION
#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.c:146
#define NULL
Definition: types.h:145
#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

 

Introduction

Documentation

Support

Miscellaneous

Web Site Translations

Mirrors