2
* mswin_check_lm_group: lookup group membership in a Windows NT/2000 domain
4
* (C)2002,2005 Guido Serassio - Acme Consulting S.r.l.
7
* Guido Serassio <guido.serassio@acmeconsulting.it>
8
* Acme Consulting S.r.l., Italy <http://www.acmeconsulting.it>
10
* With contributions from others mentioned in the change history section
13
* In part based on check_group by Rodrigo Albani de Campos.
15
* Dependencies: Windows NT4 SP4 and later.
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.
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.
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.
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.
37
* Fixed Bugzilla #1336.
39
* 23-04-2005 Guido Serassio
40
* Added -D option for specify default user's domain.
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)
46
* 13-06-2004 Guido Serassio
47
* Added support for running on a Domain Controller.
49
* 01-05-2003 Guido Serassio
50
* Added option for case insensitive group name comparation.
52
* Updated documentation.
53
* Segfault bug fix (Bugzilla #574)
55
* 24-06-2002 Guido Serassio
56
* Using the main function from check_group and sections
57
* from wbinfo wrote win32_group
59
* This is a helper for the external ACL interface for Squid Cache
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
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
73
int _wcsicmp(const wchar_t *, const wchar_t *);
95
#define BUFSIZE 8192 /* the stdin buffer size */
98
char debug_enabled = 0;
102
int use_case_insensitive_compare = 0;
103
char *DefaultDomain = NULL;
104
const char NTV_VALID_DOMAIN_SEPARATOR[] = "\\/";
106
#include "win32_check_group.h"
110
AllocStrFromLSAStr(LSA_UNICODE_STRING LsaStr)
115
len = LsaStr.Length / sizeof(WCHAR) + 1;
117
/* allocate buffer for str + null termination */
119
target = (char *) xmalloc(len);
123
/* copy unicode buffer */
124
WideCharToMultiByte(CP_ACP, 0, LsaStr.Buffer, LsaStr.Length, target, len, NULL, NULL);
126
/* add null termination */
127
target[len - 1] = '\0';
135
LSA_HANDLE PolicyHandle;
136
LSA_OBJECT_ATTRIBUTES ObjectAttributes;
138
PPOLICY_PRIMARY_DOMAIN_INFO ppdiDomainInfo;
139
PWKSTA_INFO_100 pwkiWorkstationInfo;
141
char *DomainName = NULL;
144
* Always initialize the object attributes to all zeroes.
146
memset(&ObjectAttributes, '\0', sizeof(ObjectAttributes));
149
* You need the local workstation name. Use NetWkstaGetInfo at level
150
* 100 to retrieve a WKSTA_INFO_100 structure.
152
* The wki100_computername field contains a pointer to a UNICODE
153
* string containing the local computer name.
155
netret = NetWkstaGetInfo(NULL, 100, (LPBYTE *) & pwkiWorkstationInfo);
156
if (netret == NERR_Success) {
158
* We have the workstation name in:
159
* pwkiWorkstationInfo->wki100_computername
161
* Next, open the policy object for the local system using
162
* the LsaOpenPolicy function.
164
status = LsaOpenPolicy(
167
GENERIC_READ | POLICY_VIEW_LOCAL_INFORMATION,
175
debug("OpenPolicy Error: %ld\n", status);
179
* You have a handle to the policy object. Now, get the
180
* domain information using LsaQueryInformationPolicy.
182
status = LsaQueryInformationPolicy(PolicyHandle,
183
PolicyPrimaryDomainInformation,
184
(PVOID *) & ppdiDomainInfo);
186
debug("LsaQueryInformationPolicy Error: %ld\n", status);
189
/* Get name in useable format */
190
DomainName = AllocStrFromLSAStr(ppdiDomainInfo->Name);
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.
197
if (ppdiDomainInfo->Sid) {
200
* Member of a domain. Display it in debug mode.
202
debug("Member of Domain %s\n", DomainName);
210
* Clean up all the memory buffers created by the LSA and
213
NetApiBufferFree(pwkiWorkstationInfo);
214
LsaFreeMemory((LPVOID) ppdiDomainInfo);
216
debug("NetWkstaGetInfo Error: %ld\n", netret);
220
/* returns 0 on match, -1 if no match */
222
wcstrcmparray(const wchar_t * str, const char **array)
224
WCHAR wszGroup[GNLEN + 1]; // Unicode Group
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)
237
/* returns 1 on success, 0 on failure */
239
Valid_Local_Groups(char *UserName, const char **Groups)
242
char *Domain_Separator;
243
WCHAR wszUserName[UNLEN + 1]; // Unicode user name
245
LPLOCALGROUP_USERS_INFO_0 pBuf = NULL;
246
LPLOCALGROUP_USERS_INFO_0 pTmpBuf;
248
DWORD dwFlags = LG_INCLUDE_INDIRECT;
249
DWORD dwPrefMaxLen = -1;
250
DWORD dwEntriesRead = 0;
251
DWORD dwTotalEntries = 0;
252
NET_API_STATUS nStatus;
254
DWORD dwTotalCount = 0;
256
if ((Domain_Separator = strchr(UserName, '/')) != NULL)
257
*Domain_Separator = '\\';
259
debug("Valid_Local_Groups: checking group membership of '%s'.\n", UserName);
261
/* Convert ANSI User Name and Group to Unicode */
263
MultiByteToWideChar(CP_ACP, 0, UserName,
264
strlen(UserName) + 1, wszUserName, sizeof(wszUserName) / sizeof(wszUserName[0]));
267
* Call the NetUserGetLocalGroups function
268
* specifying information level 0.
270
* The LG_INCLUDE_INDIRECT flag specifies that the
271
* function should also return the names of the local
272
* groups in which the user is indirectly a member.
274
nStatus = NetUserGetLocalGroups(
284
* If the call succeeds,
286
if (nStatus == NERR_Success) {
287
if ((pTmpBuf = pBuf) != NULL) {
288
for (i = 0; i < dwEntriesRead; i++) {
289
assert(pTmpBuf != NULL);
290
if (pTmpBuf == NULL) {
294
if (wcstrcmparray(pTmpBuf->lgrui0_name, Groups) == 0) {
305
* Free the allocated memory.
308
NetApiBufferFree(pBuf);
313
/* returns 1 on success, 0 on failure */
315
Valid_Global_Groups(char *UserName, const char **Groups)
318
WCHAR wszUserName[UNLEN + 1]; // Unicode user name
320
WCHAR wszLocalDomain[DNLEN + 1]; // Unicode Local Domain
322
WCHAR wszUserDomain[DNLEN + 1]; // Unicode User Domain
324
char NTDomain[DNLEN + UNLEN + 2];
325
char *domain_qualify;
326
char User[UNLEN + 1];
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;
335
DWORD dwPrefMaxLen = -1;
336
DWORD dwEntriesRead = 0;
337
DWORD dwTotalEntries = 0;
338
NET_API_STATUS nStatus;
340
DWORD dwTotalCount = 0;
342
strncpy(NTDomain, UserName, sizeof(NTDomain));
344
for (j = 0; j < strlen(NTV_VALID_DOMAIN_SEPARATOR); j++) {
345
if ((domain_qualify = strchr(NTDomain, NTV_VALID_DOMAIN_SEPARATOR[j])) != NULL)
348
if (domain_qualify == NULL) {
349
strcpy(User, NTDomain);
350
strcpy(NTDomain, DefaultDomain);
352
strcpy(User, domain_qualify + 1);
353
domain_qualify[0] = '\0';
357
debug("Valid_Global_Groups: checking group membership of '%s\\%s'.\n", NTDomain, User);
359
/* Convert ANSI User Name and Group to Unicode */
361
MultiByteToWideChar(CP_ACP, 0, User,
362
strlen(User) + 1, wszUserName,
363
sizeof(wszUserName) / sizeof(wszUserName[0]));
364
MultiByteToWideChar(CP_ACP, 0, machinedomain,
365
strlen(machinedomain) + 1, wszLocalDomain, sizeof(wszLocalDomain) / sizeof(wszLocalDomain[0]));
368
/* Call the NetServerGetInfo function for local computer, specifying level 101. */
370
nStatus = NetServerGetInfo(NULL, dwLevel, (LPBYTE *) & pSrvBuf);
372
if (nStatus == NERR_Success) {
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)) {
377
debug("Running on a DC.\n");
379
nStatus = (use_PDC_only ? NetGetDCName(NULL, wszLocalDomain, (LPBYTE *) & LclDCptr) : NetGetAnyDCName(NULL, wszLocalDomain, (LPBYTE *) & LclDCptr));
381
fprintf(stderr, "%s NetServerGetInfo() failed.'\n", myname);
383
NetApiBufferFree(pSrvBuf);
387
if (nStatus == NERR_Success) {
388
debug("Using '%S' as DC for '%S' local domain.\n", LclDCptr, wszLocalDomain);
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) {
395
fprintf(stderr, "%s Can't find DC for user's domain '%s'\n", myname, NTDomain);
397
NetApiBufferFree(pSrvBuf);
398
if (LclDCptr != NULL)
399
NetApiBufferFree((LPVOID) LclDCptr);
400
if (UsrDCptr != NULL)
401
NetApiBufferFree((LPVOID) UsrDCptr);
407
debug("Using '%S' as DC for '%s' user's domain.\n", UsrDCptr, NTDomain);
409
* Call the NetUserGetGroups function
410
* specifying information level 0.
413
nStatus = NetUserGetGroups(UsrDCptr,
416
(LPBYTE *) & pUsrBuf,
421
* If the call succeeds,
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) {
431
if (wcstrcmparray(pTmpBuf->grui0_name, Groups) == 0) {
441
fprintf(stderr, "%s NetUserGetGroups() failed.'\n", myname);
444
fprintf(stderr, "%s Can't find DC for local domain '%s'\n", myname, machinedomain);
447
* Free the allocated memory.
450
NetApiBufferFree(pSrvBuf);
452
NetApiBufferFree(pUsrBuf);
453
if ((UsrDCptr != NULL) && (UsrDCptr != LclDCptr))
454
NetApiBufferFree((LPVOID) UsrDCptr);
455
if (LclDCptr != NULL)
456
NetApiBufferFree((LPVOID) LclDCptr);
463
fprintf(stderr, "Usage: %s [-D domain][-G][-P][-c][-d][-h]\n"
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",
474
process_options(int argc, char *argv[])
479
while (-1 != (opt = getopt(argc, argv, "D:GPcdh"))) {
482
DefaultDomain = xstrndup(optarg, DNLEN + 1);
483
strlwr(DefaultDomain);
492
use_case_insensitive_compare = 1;
502
/* fall thru to default */
504
fprintf(stderr, "%s Unknown option: -%c. Exiting\n", myname, opt);
507
break; /* not reached */
515
main(int argc, char *argv[])
522
const char *groups[512];
525
if (argc > 0) { /* should always be true */
526
myname = strrchr(argv[0], '/');
530
myname = "(unknown)";
534
setbuf(stdout, NULL);
535
setbuf(stderr, NULL);
537
/* Check Command Line */
538
process_options(argc, argv);
541
if ((machinedomain = GetDomainName()) == NULL) {
542
fprintf(stderr, "%s Can't read machine domain\n", myname);
545
strlwr(machinedomain);
547
DefaultDomain = xstrdup(machinedomain);
549
debug("External ACL win32 group helper build " __DATE__ ", " __TIME__
550
" starting up...\n");
552
debug("Domain Global group mode enabled using '%s' as default domain.\n", DefaultDomain);
553
if (use_case_insensitive_compare)
554
debug("Warning: running in case insensitive mode !!!\n");
556
debug("Warning: using only PDCs for group validation !!!\n");
559
while (fgets(buf, sizeof(buf), stdin)) {
560
if (NULL == strchr(buf, '\n')) {
561
/* too large message received.. skip and deny */
562
fprintf(stderr, "%s: ERROR: Too large: %s\n", argv[0], buf);
563
while (fgets(buf, sizeof(buf), stdin)) {
564
fprintf(stderr, "%s: ERROR: Too large..: %s\n", argv[0], buf);
565
if (strchr(buf, '\n') != NULL)
570
if ((p = strchr(buf, '\n')) != NULL)
571
*p = '\0'; /* strip \n */
572
if ((p = strchr(buf, '\r')) != NULL)
573
*p = '\0'; /* strip \r */
575
debug("Got '%s' from Squid (length: %d).\n", buf, strlen(buf));
577
if (buf[0] == '\0') {
578
fprintf(stderr, "Invalid Request\n");
581
username = strtok(buf, " ");
582
for (n = 0; (group = strtok(NULL, " ")) != NULL; n++) {
583
rfc1738_unescape(group);
588
if (NULL == username) {
589
fprintf(stderr, "Invalid Request\n");
592
rfc1738_unescape(username);
594
if ((use_global ? Valid_Global_Groups(username, groups) : Valid_Local_Groups(username, groups))) {