]> git.ipfire.org Git - thirdparty/squid.git/blame - helpers/external_acl/LM_group/ext_lm_group_acl.cc
Boilerplate: update copyright blurbs for Squid tools
[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 71#include "helpers/defines.h"
16cca888
AJ
72#include "rfc1738.h"
73#include "util.h"
c152a447 74
be266cb2 75#if _SQUID_CYGWIN_
736a9a4d 76#include <wchar.h>
45648be2 77int _wcsicmp(const wchar_t *, const wchar_t *);
736a9a4d 78#endif
c152a447 79
074d6a40
AJ
80#undef assert
81#include <cassert>
82#include <cctype>
83#include <cstring>
736a9a4d 84#if HAVE_GETOPT_H
85#include <getopt.h>
86#endif
736a9a4d 87#include <windows.h>
88#include <lm.h>
89#include <ntsecapi.h>
90
736a9a4d 91int use_global = 0;
92int use_PDC_only = 0;
385df818 93const char *program_name;
736a9a4d 94pid_t mypid;
45648be2 95char *machinedomain;
736a9a4d 96int use_case_insensitive_compare = 0;
45648be2 97char *DefaultDomain = NULL;
736a9a4d 98const char NTV_VALID_DOMAIN_SEPARATOR[] = "\\/";
99
45648be2 100char *
101AllocStrFromLSAStr(LSA_UNICODE_STRING LsaStr)
736a9a4d 102{
103 size_t len;
45648be2 104 static char *target;
736a9a4d 105
45648be2 106 len = LsaStr.Length / sizeof(WCHAR) + 1;
736a9a4d 107
108 /* allocate buffer for str + null termination */
109 safe_free(target);
45648be2 110 target = (char *) xmalloc(len);
736a9a4d 111 if (target == NULL)
26ac0430 112 return NULL;
736a9a4d 113
114 /* copy unicode buffer */
45648be2 115 WideCharToMultiByte(CP_ACP, 0, LsaStr.Buffer, LsaStr.Length, target, len, NULL, NULL);
736a9a4d 116
117 /* add null termination */
45648be2 118 target[len - 1] = '\0';
736a9a4d 119 return target;
120}
121
45648be2 122char *
123GetDomainName(void)
736a9a4d 124{
125 LSA_HANDLE PolicyHandle;
126 LSA_OBJECT_ATTRIBUTES ObjectAttributes;
127 NTSTATUS status;
128 PPOLICY_PRIMARY_DOMAIN_INFO ppdiDomainInfo;
129 PWKSTA_INFO_100 pwkiWorkstationInfo;
130 DWORD netret;
45648be2 131 char *DomainName = NULL;
736a9a4d 132
26ac0430 133 /*
736a9a4d 134 * Always initialize the object attributes to all zeroes.
45648be2 135 */
736a9a4d 136 memset(&ObjectAttributes, '\0', sizeof(ObjectAttributes));
137
26ac0430 138 /*
736a9a4d 139 * You need the local workstation name. Use NetWkstaGetInfo at level
140 * 100 to retrieve a WKSTA_INFO_100 structure.
26ac0430 141 *
736a9a4d 142 * The wki100_computername field contains a pointer to a UNICODE
143 * string containing the local computer name.
45648be2 144 */
145 netret = NetWkstaGetInfo(NULL, 100, (LPBYTE *) & pwkiWorkstationInfo);
736a9a4d 146 if (netret == NERR_Success) {
26ac0430
AJ
147 /*
148 * We have the workstation name in:
149 * pwkiWorkstationInfo->wki100_computername
150 *
151 * Next, open the policy object for the local system using
152 * the LsaOpenPolicy function.
153 */
154 status = LsaOpenPolicy(
155 NULL,
156 &ObjectAttributes,
157 GENERIC_READ | POLICY_VIEW_LOCAL_INFORMATION,
158 &PolicyHandle
159 );
160
161 /*
162 * Error checking.
163 */
164 if (status) {
165 debug("OpenPolicy Error: %ld\n", status);
166 } else {
167
168 /*
169 * You have a handle to the policy object. Now, get the
170 * domain information using LsaQueryInformationPolicy.
171 */
172 status = LsaQueryInformationPolicy(PolicyHandle,
173 PolicyPrimaryDomainInformation,
174 (PVOID *) & ppdiDomainInfo);
175 if (status) {
176 debug("LsaQueryInformationPolicy Error: %ld\n", status);
177 } else {
178
179 /* Get name in useable format */
180 DomainName = AllocStrFromLSAStr(ppdiDomainInfo->Name);
181
182 /*
183 * Check the Sid pointer, if it is null, the
184 * workstation is either a stand-alone computer
185 * or a member of a workgroup.
186 */
187 if (ppdiDomainInfo->Sid) {
188
189 /*
190 * Member of a domain. Display it in debug mode.
191 */
192 debug("Member of Domain %s\n", DomainName);
193 } else {
194 DomainName = NULL;
195 }
196 }
197 }
198
199 /*
200 * Clean up all the memory buffers created by the LSA and
201 * Net* APIs.
202 */
203 NetApiBufferFree(pwkiWorkstationInfo);
204 LsaFreeMemory((LPVOID) ppdiDomainInfo);
45648be2 205 } else
26ac0430 206 debug("NetWkstaGetInfo Error: %ld\n", netret);
736a9a4d 207 return DomainName;
208}
209
210/* returns 0 on match, -1 if no match */
26ac0430 211static int
45648be2 212wcstrcmparray(const wchar_t * str, const char **array)
736a9a4d 213{
45648be2 214 WCHAR wszGroup[GNLEN + 1]; // Unicode Group
736a9a4d 215
216 while (*array) {
26ac0430
AJ
217 MultiByteToWideChar(CP_ACP, 0, *array,
218 strlen(*array) + 1, wszGroup, sizeof(wszGroup) / sizeof(wszGroup[0]));
219 debug("Windows group: %S, Squid group: %S\n", str, wszGroup);
220 if ((use_case_insensitive_compare ? _wcsicmp(str, wszGroup) : wcscmp(str, wszGroup)) == 0)
221 return 0;
755494da 222 ++array;
736a9a4d 223 }
224 return -1;
225}
226
227/* returns 1 on success, 0 on failure */
228int
229Valid_Local_Groups(char *UserName, const char **Groups)
230{
231 int result = 0;
45648be2 232 char *Domain_Separator;
233 WCHAR wszUserName[UNLEN + 1]; // Unicode user name
736a9a4d 234
235 LPLOCALGROUP_USERS_INFO_0 pBuf = NULL;
236 LPLOCALGROUP_USERS_INFO_0 pTmpBuf;
237 DWORD dwLevel = 0;
238 DWORD dwFlags = LG_INCLUDE_INDIRECT;
239 DWORD dwPrefMaxLen = -1;
240 DWORD dwEntriesRead = 0;
241 DWORD dwTotalEntries = 0;
242 NET_API_STATUS nStatus;
243 DWORD i;
244 DWORD dwTotalCount = 0;
245
246 if ((Domain_Separator = strchr(UserName, '/')) != NULL)
26ac0430 247 *Domain_Separator = '\\';
45648be2 248
736a9a4d 249 debug("Valid_Local_Groups: checking group membership of '%s'.\n", UserName);
45648be2 250
26ac0430 251 /* Convert ANSI User Name and Group to Unicode */
736a9a4d 252
253 MultiByteToWideChar(CP_ACP, 0, UserName,
26ac0430 254 strlen(UserName) + 1, wszUserName, sizeof(wszUserName) / sizeof(wszUserName[0]));
736a9a4d 255
256 /*
26ac0430 257 * Call the NetUserGetLocalGroups function
45648be2 258 * specifying information level 0.
26ac0430
AJ
259 *
260 * The LG_INCLUDE_INDIRECT flag specifies that the
261 * function should also return the names of the local
45648be2 262 * groups in which the user is indirectly a member.
263 */
736a9a4d 264 nStatus = NetUserGetLocalGroups(
26ac0430
AJ
265 NULL,
266 wszUserName,
267 dwLevel,
268 dwFlags,
269 (LPBYTE *) & pBuf,
270 dwPrefMaxLen,
271 &dwEntriesRead,
272 &dwTotalEntries);
45648be2 273 /*
274 * If the call succeeds,
275 */
736a9a4d 276 if (nStatus == NERR_Success) {
26ac0430 277 if ((pTmpBuf = pBuf) != NULL) {
a2f5277a 278 for (i = 0; i < dwEntriesRead; ++i) {
26ac0430
AJ
279 assert(pTmpBuf != NULL);
280 if (pTmpBuf == NULL) {
281 result = 0;
282 break;
283 }
284 if (wcstrcmparray(pTmpBuf->lgrui0_name, Groups) == 0) {
285 result = 1;
286 break;
287 }
755494da
FC
288 ++pTmpBuf;
289 ++dwTotalCount;
26ac0430
AJ
290 }
291 }
736a9a4d 292 } else
26ac0430
AJ
293 result = 0;
294 /*
295 * Free the allocated memory.
296 */
736a9a4d 297 if (pBuf != NULL)
26ac0430 298 NetApiBufferFree(pBuf);
736a9a4d 299 return result;
300}
301
736a9a4d 302/* returns 1 on success, 0 on failure */
303int
304Valid_Global_Groups(char *UserName, const char **Groups)
305{
306 int result = 0;
45648be2 307 WCHAR wszUserName[UNLEN + 1]; // Unicode user name
736a9a4d 308
45648be2 309 WCHAR wszLocalDomain[DNLEN + 1]; // Unicode Local Domain
310
311 WCHAR wszUserDomain[DNLEN + 1]; // Unicode User Domain
312
313 char NTDomain[DNLEN + UNLEN + 2];
736a9a4d 314 char *domain_qualify;
45648be2 315 char User[UNLEN + 1];
736a9a4d 316 size_t j;
317
318 LPWSTR LclDCptr = NULL;
319 LPWSTR UsrDCptr = NULL;
320 LPGROUP_USERS_INFO_0 pUsrBuf = NULL;
321 LPGROUP_USERS_INFO_0 pTmpBuf;
322 LPSERVER_INFO_101 pSrvBuf = NULL;
323 DWORD dwLevel = 0;
324 DWORD dwPrefMaxLen = -1;
325 DWORD dwEntriesRead = 0;
326 DWORD dwTotalEntries = 0;
327 NET_API_STATUS nStatus;
328 DWORD i;
329 DWORD dwTotalCount = 0;
330
331 strncpy(NTDomain, UserName, sizeof(NTDomain));
332
a2f5277a 333 for (j = 0; j < strlen(NTV_VALID_DOMAIN_SEPARATOR); ++j) {
26ac0430
AJ
334 if ((domain_qualify = strchr(NTDomain, NTV_VALID_DOMAIN_SEPARATOR[j])) != NULL)
335 break;
736a9a4d 336 }
337 if (domain_qualify == NULL) {
26ac0430
AJ
338 strcpy(User, NTDomain);
339 strcpy(NTDomain, DefaultDomain);
736a9a4d 340 } else {
26ac0430
AJ
341 strcpy(User, domain_qualify + 1);
342 domain_qualify[0] = '\0';
343 strlwr(NTDomain);
736a9a4d 344 }
345
346 debug("Valid_Global_Groups: checking group membership of '%s\\%s'.\n", NTDomain, User);
347
348 /* Convert ANSI User Name and Group to Unicode */
349
350 MultiByteToWideChar(CP_ACP, 0, User,
26ac0430
AJ
351 strlen(User) + 1, wszUserName,
352 sizeof(wszUserName) / sizeof(wszUserName[0]));
736a9a4d 353 MultiByteToWideChar(CP_ACP, 0, machinedomain,
26ac0430 354 strlen(machinedomain) + 1, wszLocalDomain, sizeof(wszLocalDomain) / sizeof(wszLocalDomain[0]));
736a9a4d 355
26ac0430 356 /* Call the NetServerGetInfo function for local computer, specifying level 101. */
736a9a4d 357 dwLevel = 101;
45648be2 358 nStatus = NetServerGetInfo(NULL, dwLevel, (LPBYTE *) & pSrvBuf);
359
360 if (nStatus == NERR_Success) {
26ac0430
AJ
361 /* Check if we are running on a Domain Controller */
362 if ((pSrvBuf->sv101_type & SV_TYPE_DOMAIN_CTRL) ||
363 (pSrvBuf->sv101_type & SV_TYPE_DOMAIN_BAKCTRL)) {
364 LclDCptr = NULL;
365 debug("Running on a DC.\n");
366 } else
367 nStatus = (use_PDC_only ? NetGetDCName(NULL, wszLocalDomain, (LPBYTE *) & LclDCptr) : NetGetAnyDCName(NULL, wszLocalDomain, (LPBYTE *) & LclDCptr));
736a9a4d 368 } else {
c152a447 369 fprintf(stderr, "%s: ERROR: NetServerGetInfo() failed.'\n", program_name);
26ac0430
AJ
370 if (pSrvBuf != NULL)
371 NetApiBufferFree(pSrvBuf);
372 return result;
736a9a4d 373 }
374
375 if (nStatus == NERR_Success) {
26ac0430
AJ
376 debug("Using '%S' as DC for '%S' local domain.\n", LclDCptr, wszLocalDomain);
377
378 if (strcmp(NTDomain, machinedomain) != 0) {
379 MultiByteToWideChar(CP_ACP, 0, NTDomain,
380 strlen(NTDomain) + 1, wszUserDomain, sizeof(wszUserDomain) / sizeof(wszUserDomain[0]));
381 nStatus = (use_PDC_only ? NetGetDCName(LclDCptr, wszUserDomain, (LPBYTE *) & UsrDCptr) : NetGetAnyDCName(LclDCptr, wszUserDomain, (LPBYTE *) & UsrDCptr));
382 if (nStatus != NERR_Success) {
c152a447 383 fprintf(stderr, "%s: ERROR: Can't find DC for user's domain '%s'\n", program_name, NTDomain);
26ac0430
AJ
384 if (pSrvBuf != NULL)
385 NetApiBufferFree(pSrvBuf);
386 if (LclDCptr != NULL)
387 NetApiBufferFree((LPVOID) LclDCptr);
388 if (UsrDCptr != NULL)
389 NetApiBufferFree((LPVOID) UsrDCptr);
390 return result;
391 }
392 } else
393 UsrDCptr = LclDCptr;
394
395 debug("Using '%S' as DC for '%s' user's domain.\n", UsrDCptr, NTDomain);
396 /*
397 * Call the NetUserGetGroups function
398 * specifying information level 0.
399 */
400 dwLevel = 0;
401 nStatus = NetUserGetGroups(UsrDCptr,
402 wszUserName,
403 dwLevel,
404 (LPBYTE *) & pUsrBuf,
405 dwPrefMaxLen,
406 &dwEntriesRead,
407 &dwTotalEntries);
408 /*
409 * If the call succeeds,
410 */
411 if (nStatus == NERR_Success) {
412 if ((pTmpBuf = pUsrBuf) != NULL) {
a2f5277a 413 for (i = 0; i < dwEntriesRead; ++i) {
26ac0430
AJ
414 assert(pTmpBuf != NULL);
415 if (pTmpBuf == NULL) {
416 result = 0;
417 break;
418 }
419 if (wcstrcmparray(pTmpBuf->grui0_name, Groups) == 0) {
420 result = 1;
421 break;
422 }
755494da
FC
423 ++pTmpBuf;
424 ++dwTotalCount;
26ac0430
AJ
425 }
426 }
427 } else {
428 result = 0;
c152a447 429 fprintf(stderr, "%s: ERROR: NetUserGetGroups() failed.'\n", program_name);
26ac0430 430 }
736a9a4d 431 } else {
c152a447 432 fprintf(stderr, "%s: ERROR: Can't find DC for local domain '%s'\n", program_name, machinedomain);
736a9a4d 433 }
434 /*
435 * Free the allocated memory.
436 */
437 if (pSrvBuf != NULL)
26ac0430 438 NetApiBufferFree(pSrvBuf);
736a9a4d 439 if (pUsrBuf != NULL)
26ac0430 440 NetApiBufferFree(pUsrBuf);
736a9a4d 441 if ((UsrDCptr != NULL) && (UsrDCptr != LclDCptr))
26ac0430 442 NetApiBufferFree((LPVOID) UsrDCptr);
736a9a4d 443 if (LclDCptr != NULL)
26ac0430 444 NetApiBufferFree((LPVOID) LclDCptr);
736a9a4d 445 return result;
446}
447
448static void
c152a447 449usage(const char *program)
736a9a4d 450{
45648be2 451 fprintf(stderr, "Usage: %s [-D domain][-G][-P][-c][-d][-h]\n"
26ac0430
AJ
452 " -D default user Domain\n"
453 " -G enable Domain Global group mode\n"
454 " -P use ONLY PDCs for group validation\n"
455 " -c use case insensitive compare\n"
456 " -d enable debugging\n"
457 " -h this message\n",
458 program);
736a9a4d 459}
460
461void
462process_options(int argc, char *argv[])
463{
464 int opt;
465
466 opterr = 0;
467 while (-1 != (opt = getopt(argc, argv, "D:GPcdh"))) {
26ac0430
AJ
468 switch (opt) {
469 case 'D':
470 DefaultDomain = xstrndup(optarg, DNLEN + 1);
471 strlwr(DefaultDomain);
472 break;
473 case 'G':
474 use_global = 1;
475 break;
476 case 'P':
477 use_PDC_only = 1;
478 break;
479 case 'c':
480 use_case_insensitive_compare = 1;
481 break;
482 case 'd':
483 debug_enabled = 1;
484 break;
485 case 'h':
486 usage(argv[0]);
487 exit(0);
488 case '?':
489 opt = optopt;
490 /* fall thru to default */
491 default:
c152a447 492 fprintf(stderr, "%s: FATAL: Unknown option: -%c. Exiting\n", program_name, opt);
26ac0430
AJ
493 usage(argv[0]);
494 exit(1);
495 break; /* not reached */
496 }
736a9a4d 497 }
498 return;
499}
500
736a9a4d 501int
45648be2 502main(int argc, char *argv[])
736a9a4d 503{
504 char *p;
c152a447 505 char buf[HELPER_INPUT_BUFFER];
736a9a4d 506 char *username;
507 char *group;
736a9a4d 508 const char *groups[512];
509 int n;
510
45648be2 511 if (argc > 0) { /* should always be true */
c152a447
AJ
512 program_name = strrchr(argv[0], '/');
513 if (program_name == NULL)
514 program_name = argv[0];
736a9a4d 515 } else {
c152a447 516 program_name = "(unknown)";
736a9a4d 517 }
45648be2 518 mypid = getpid();
736a9a4d 519
520 setbuf(stdout, NULL);
521 setbuf(stderr, NULL);
522
523 /* Check Command Line */
524 process_options(argc, argv);
525
526 if (use_global) {
26ac0430 527 if ((machinedomain = GetDomainName()) == NULL) {
c152a447 528 fprintf(stderr, "%s: FATAL: Can't read machine domain\n", program_name);
26ac0430
AJ
529 exit(1);
530 }
531 strlwr(machinedomain);
532 if (!DefaultDomain)
533 DefaultDomain = xstrdup(machinedomain);
736a9a4d 534 }
736a9a4d 535 debug("External ACL win32 group helper build " __DATE__ ", " __TIME__
26ac0430 536 " starting up...\n");
385df818 537 if (use_global) {
26ac0430 538 debug("Domain Global group mode enabled using '%s' as default domain.\n", DefaultDomain);
385df818
AJ
539 }
540 if (use_case_insensitive_compare) {
26ac0430 541 debug("Warning: running in case insensitive mode !!!\n");
385df818
AJ
542 }
543 if (use_PDC_only) {
26ac0430 544 debug("Warning: using only PDCs for group validation !!!\n");
385df818 545 }
736a9a4d 546
547 /* Main Loop */
c152a447 548 while (fgets(buf, HELPER_INPUT_BUFFER, stdin)) {
26ac0430
AJ
549 if (NULL == strchr(buf, '\n')) {
550 /* too large message received.. skip and deny */
c152a447
AJ
551 debug("%s: ERROR: Too large: %s\n", argv[0], buf);
552 while (fgets(buf, HELPER_INPUT_BUFFER, stdin)) {
553 debug("%s: ERROR: Too large..: %s\n", argv[0], buf);
26ac0430
AJ
554 if (strchr(buf, '\n') != NULL)
555 break;
556 }
c152a447
AJ
557 SEND_ERR("Input Too Long.");
558 continue;
26ac0430
AJ
559 }
560 if ((p = strchr(buf, '\n')) != NULL)
561 *p = '\0'; /* strip \n */
562 if ((p = strchr(buf, '\r')) != NULL)
563 *p = '\0'; /* strip \r */
564
565 debug("Got '%s' from Squid (length: %d).\n", buf, strlen(buf));
566
567 if (buf[0] == '\0') {
c152a447
AJ
568 SEND_ERR("Invalid Request.");
569 continue;
26ac0430
AJ
570 }
571 username = strtok(buf, " ");
a2f5277a 572 for (n = 0; (group = strtok(NULL, " ")) != NULL; ++n) {
26ac0430
AJ
573 rfc1738_unescape(group);
574 groups[n] = group;
575 }
576 groups[n] = NULL;
577
578 if (NULL == username) {
c152a447
AJ
579 SEND_ERR("Invalid Request. No Username.");
580 continue;
26ac0430
AJ
581 }
582 rfc1738_unescape(username);
583
584 if ((use_global ? Valid_Global_Groups(username, groups) : Valid_Local_Groups(username, groups))) {
c152a447 585 SEND_OK("");
26ac0430 586 } else {
c152a447 587 SEND_ERR("");
26ac0430 588 }
736a9a4d 589 }
590 return 0;
591}