2 * Copyright (C) 1996-2023 The Squid Software Foundation and contributors
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.
10 * mswin_check_lm_group: lookup group membership in a Windows NT/2000 domain
12 * (C)2002,2005 Guido Serassio - Acme Consulting S.r.l.
15 * Guido Serassio <guido.serassio@acmeconsulting.it>
16 * Acme Consulting S.r.l., Italy <http://www.acmeconsulting.it>
18 * With contributions from others mentioned in the change history section
21 * In part based on check_group by Rodrigo Albani de Campos.
23 * Dependencies: Windows NT4 SP4 and later.
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.
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.
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.
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.
47 * 23-04-2005 Guido Serassio
48 * Added -D option for specify default user's domain.
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)
54 * 13-06-2004 Guido Serassio
55 * Added support for running on a Domain Controller.
57 * 01-05-2003 Guido Serassio
58 * Added option for case insensitive group name comparison.
60 * Updated documentation.
61 * Segfault bug fix (Bugzilla #574)
63 * 24-06-2002 Guido Serassio
64 * Using the main function from check_group and sections
65 * from wbinfo wrote win32_group
67 * This is a helper for the external ACL interface for Squid Cache
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
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
79 #include "helper/protocol_defines.h"
85 int _wcsicmp(const wchar_t *, const wchar_t *);
100 int use_PDC_only
= 0;
101 const char *program_name
;
104 int use_case_insensitive_compare
= 0;
105 char *DefaultDomain
= NULL
;
106 const char NTV_VALID_DOMAIN_SEPARATOR
[] = "\\/";
109 AllocStrFromLSAStr(LSA_UNICODE_STRING LsaStr
)
114 len
= LsaStr
.Length
/ sizeof(WCHAR
) + 1;
116 /* allocate buffer for str + null termination */
118 target
= (char *) xmalloc(len
);
122 /* copy unicode buffer */
123 WideCharToMultiByte(CP_ACP
, 0, LsaStr
.Buffer
, LsaStr
.Length
, target
, len
, NULL
, NULL
);
125 /* add null termination */
126 target
[len
- 1] = '\0';
133 LSA_HANDLE PolicyHandle
;
134 LSA_OBJECT_ATTRIBUTES ObjectAttributes
;
136 PPOLICY_PRIMARY_DOMAIN_INFO ppdiDomainInfo
;
137 PWKSTA_INFO_100 pwkiWorkstationInfo
;
139 char *DomainName
= NULL
;
142 * Always initialize the object attributes to all zeroes.
144 memset(&ObjectAttributes
, '\0', sizeof(ObjectAttributes
));
147 * You need the local workstation name. Use NetWkstaGetInfo at level
148 * 100 to retrieve a WKSTA_INFO_100 structure.
150 * The wki100_computername field contains a pointer to a UNICODE
151 * string containing the local computer name.
153 netret
= NetWkstaGetInfo(NULL
, 100, (LPBYTE
*) & pwkiWorkstationInfo
);
154 if (netret
== NERR_Success
) {
156 * We have the workstation name in:
157 * pwkiWorkstationInfo->wki100_computername
159 * Next, open the policy object for the local system using
160 * the LsaOpenPolicy function.
162 status
= LsaOpenPolicy(
165 GENERIC_READ
| POLICY_VIEW_LOCAL_INFORMATION
,
173 debug("OpenPolicy Error: %ld\n", status
);
177 * You have a handle to the policy object. Now, get the
178 * domain information using LsaQueryInformationPolicy.
180 status
= LsaQueryInformationPolicy(PolicyHandle
,
181 PolicyPrimaryDomainInformation
,
182 (PVOID
*) & ppdiDomainInfo
);
184 debug("LsaQueryInformationPolicy Error: %ld\n", status
);
187 /* Get name in usable format */
188 DomainName
= AllocStrFromLSAStr(ppdiDomainInfo
->Name
);
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.
195 if (ppdiDomainInfo
->Sid
) {
198 * Member of a domain. Display it in debug mode.
200 debug("Member of Domain %s\n", DomainName
);
208 * Clean up all the memory buffers created by the LSA and
211 NetApiBufferFree(pwkiWorkstationInfo
);
212 LsaFreeMemory((LPVOID
) ppdiDomainInfo
);
214 debug("NetWkstaGetInfo Error: %ld\n", netret
);
218 /* returns 0 on match, -1 if no match */
220 wcstrcmparray(const wchar_t * str
, const char **array
)
222 WCHAR wszGroup
[GNLEN
+ 1]; // Unicode Group
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)
235 /* returns 1 on success, 0 on failure */
237 Valid_Local_Groups(char *UserName
, const char **Groups
)
240 char *Domain_Separator
;
241 WCHAR wszUserName
[UNLEN
+ 1]; // Unicode user name
243 LPLOCALGROUP_USERS_INFO_0 pBuf
= NULL
;
244 LPLOCALGROUP_USERS_INFO_0 pTmpBuf
;
246 DWORD dwFlags
= LG_INCLUDE_INDIRECT
;
247 DWORD dwPrefMaxLen
= -1;
248 DWORD dwEntriesRead
= 0;
249 DWORD dwTotalEntries
= 0;
250 NET_API_STATUS nStatus
;
252 DWORD dwTotalCount
= 0;
254 if ((Domain_Separator
= strchr(UserName
, '/')) != NULL
)
255 *Domain_Separator
= '\\';
257 debug("Valid_Local_Groups: checking group membership of '%s'.\n", UserName
);
259 /* Convert ANSI User Name and Group to Unicode */
261 MultiByteToWideChar(CP_ACP
, 0, UserName
,
262 strlen(UserName
) + 1, wszUserName
, sizeof(wszUserName
) / sizeof(wszUserName
[0]));
265 * Call the NetUserGetLocalGroups function
266 * specifying information level 0.
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.
272 nStatus
= NetUserGetLocalGroups(
282 * If the call succeeds,
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
) {
292 if (wcstrcmparray(pTmpBuf
->lgrui0_name
, Groups
) == 0) {
303 * Free the allocated memory.
306 NetApiBufferFree(pBuf
);
310 /* returns 1 on success, 0 on failure */
312 Valid_Global_Groups(char *UserName
, const char **Groups
)
315 WCHAR wszUserName
[UNLEN
+ 1]; // Unicode user name
317 WCHAR wszLocalDomain
[DNLEN
+ 1]; // Unicode Local Domain
319 WCHAR wszUserDomain
[DNLEN
+ 1]; // Unicode User Domain
321 char NTDomain
[DNLEN
+ UNLEN
+ 2];
322 char *domain_qualify
;
323 char User
[UNLEN
+ 1];
326 LPWSTR LclDCptr
= NULL
;
327 LPWSTR UsrDCptr
= NULL
;
328 LPGROUP_USERS_INFO_0 pUsrBuf
= NULL
;
329 LPGROUP_USERS_INFO_0 pTmpBuf
;
330 LPSERVER_INFO_101 pSrvBuf
= NULL
;
332 DWORD dwPrefMaxLen
= -1;
333 DWORD dwEntriesRead
= 0;
334 DWORD dwTotalEntries
= 0;
335 NET_API_STATUS nStatus
;
337 DWORD dwTotalCount
= 0;
339 xstrncpy(NTDomain
, UserName
, sizeof(NTDomain
));
341 for (j
= 0; j
< strlen(NTV_VALID_DOMAIN_SEPARATOR
); ++j
) {
342 if ((domain_qualify
= strchr(NTDomain
, NTV_VALID_DOMAIN_SEPARATOR
[j
])) != NULL
)
345 if (domain_qualify
== NULL
) {
346 xstrncpy(User
, NTDomain
, sizeof(User
));
347 xstrncpy(NTDomain
, DefaultDomain
, sizeof(NTDomain
));
349 xstrncpy(User
, domain_qualify
+ 1, sizeof(User
));
350 domain_qualify
[0] = '\0';
354 debug("Valid_Global_Groups: checking group membership of '%s\\%s'.\n", NTDomain
, User
);
356 /* Convert ANSI User Name and Group to Unicode */
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]));
364 /* Call the NetServerGetInfo function for local computer, specifying level 101. */
366 nStatus
= NetServerGetInfo(NULL
, dwLevel
, (LPBYTE
*) & pSrvBuf
);
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
)) {
373 debug("Running on a DC.\n");
375 nStatus
= (use_PDC_only
? NetGetDCName(NULL
, wszLocalDomain
, (LPBYTE
*) & LclDCptr
) : NetGetAnyDCName(NULL
, wszLocalDomain
, (LPBYTE
*) & LclDCptr
));
377 fprintf(stderr
, "%s: ERROR: NetServerGetInfo() failed.'\n", program_name
);
379 NetApiBufferFree(pSrvBuf
);
383 if (nStatus
== NERR_Success
) {
384 debug("Using '%S' as DC for '%S' local domain.\n", LclDCptr
, wszLocalDomain
);
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
);
393 NetApiBufferFree(pSrvBuf
);
394 if (LclDCptr
!= NULL
)
395 NetApiBufferFree((LPVOID
) LclDCptr
);
396 if (UsrDCptr
!= NULL
)
397 NetApiBufferFree((LPVOID
) UsrDCptr
);
403 debug("Using '%S' as DC for '%s' user's domain.\n", UsrDCptr
, NTDomain
);
405 * Call the NetUserGetGroups function
406 * specifying information level 0.
409 nStatus
= NetUserGetGroups(UsrDCptr
,
412 (LPBYTE
*) & pUsrBuf
,
417 * If the call succeeds,
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
) {
427 if (wcstrcmparray(pTmpBuf
->grui0_name
, Groups
) == 0) {
437 fprintf(stderr
, "%s: ERROR: NetUserGetGroups() failed.'\n", program_name
);
440 fprintf(stderr
, "%s: ERROR: Can't find DC for local domain '%s'\n", program_name
, machinedomain
);
443 * Free the allocated memory.
446 NetApiBufferFree(pSrvBuf
);
448 NetApiBufferFree(pUsrBuf
);
449 if ((UsrDCptr
!= NULL
) && (UsrDCptr
!= LclDCptr
))
450 NetApiBufferFree((LPVOID
) UsrDCptr
);
451 if (LclDCptr
!= NULL
)
452 NetApiBufferFree((LPVOID
) LclDCptr
);
457 usage(const char *program
)
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",
470 process_options(int argc
, char *argv
[])
475 while (-1 != (opt
= getopt(argc
, argv
, "D:GPcdh"))) {
478 DefaultDomain
= xstrndup(optarg
, DNLEN
+ 1);
479 strlwr(DefaultDomain
);
488 use_case_insensitive_compare
= 1;
500 fprintf(stderr
, "%s: FATAL: Unknown option: -%c. Exiting\n", program_name
, opt
);
503 break; /* not reached */
510 main(int argc
, char *argv
[])
513 char buf
[HELPER_INPUT_BUFFER
];
516 const char *groups
[512];
519 if (argc
> 0) { /* should always be true */
520 program_name
= strrchr(argv
[0], '/');
521 if (program_name
== NULL
)
522 program_name
= argv
[0];
524 program_name
= "(unknown)";
528 setbuf(stdout
, NULL
);
529 setbuf(stderr
, NULL
);
531 /* Check Command Line */
532 process_options(argc
, argv
);
535 if ((machinedomain
= GetDomainName()) == NULL
) {
536 fprintf(stderr
, "%s: FATAL: Can't read machine domain\n", program_name
);
539 strlwr(machinedomain
);
541 DefaultDomain
= xstrdup(machinedomain
);
543 debug("%s " VERSION
" " SQUID_BUILD_INFO
" starting up...\n", argv
[0]);
545 debug("Domain Global group mode enabled using '%s' as default domain.\n", DefaultDomain
);
547 if (use_case_insensitive_compare
) {
548 debug("Warning: running in case insensitive mode !!!\n");
551 debug("Warning: using only PDCs for group validation !!!\n");
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
)
564 SEND_BH(HLP_MSG("Input Too Long."));
567 if ((p
= strchr(buf
, '\n')) != NULL
)
568 *p
= '\0'; /* strip \n */
569 if ((p
= strchr(buf
, '\r')) != NULL
)
570 *p
= '\0'; /* strip \r */
572 debug("Got '%s' from Squid (length: %d).\n", buf
, strlen(buf
));
574 if (buf
[0] == '\0') {
575 SEND_BH(HLP_MSG("Invalid Request."));
578 username
= strtok(buf
, " ");
579 for (n
= 0; (group
= strtok(NULL
, " ")) != NULL
; ++n
) {
580 rfc1738_unescape(group
);
585 if (NULL
== username
) {
586 SEND_BH(HLP_MSG("Invalid Request. No Username."));
589 rfc1738_unescape(username
);
591 if ((use_global
? Valid_Global_Groups(username
, groups
) : Valid_Local_Groups(username
, groups
))) {