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