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