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
71 #include "helpers/defines.h"
72 #include "include/util.h"
77 int _wcsicmp(const wchar_t *, const wchar_t *);
100 int use_PDC_only
= 0;
104 int use_case_insensitive_compare
= 0;
105 char *DefaultDomain
= NULL
;
106 const char NTV_VALID_DOMAIN_SEPARATOR
[] = "\\/";
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: ERROR: NetServerGetInfo() failed.'\n", program_name
);
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: ERROR: Can't find DC for user's domain '%s'\n", program_name
, 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: ERROR: NetUserGetGroups() failed.'\n", program_name
);
444 fprintf(stderr
, "%s: ERROR: Can't find DC for local domain '%s'\n", program_name
, 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
);
461 usage(const char *program
)
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: FATAL: Unknown option: -%c. Exiting\n", program_name
, opt
);
507 break; /* not reached */
515 main(int argc
, char *argv
[])
518 char buf
[HELPER_INPUT_BUFFER
];
521 const char *groups
[512];
524 if (argc
> 0) { /* should always be true */
525 program_name
= strrchr(argv
[0], '/');
526 if (program_name
== NULL
)
527 program_name
= argv
[0];
529 program_name
= "(unknown)";
533 setbuf(stdout
, NULL
);
534 setbuf(stderr
, NULL
);
536 /* Check Command Line */
537 process_options(argc
, argv
);
540 if ((machinedomain
= GetDomainName()) == NULL
) {
541 fprintf(stderr
, "%s: FATAL: Can't read machine domain\n", program_name
);
544 strlwr(machinedomain
);
546 DefaultDomain
= xstrdup(machinedomain
);
548 debug("External ACL win32 group helper build " __DATE__
", " __TIME__
549 " starting up...\n");
551 debug("Domain Global group mode enabled using '%s' as default domain.\n", DefaultDomain
);
552 if (use_case_insensitive_compare
)
553 debug("Warning: running in case insensitive mode !!!\n");
555 debug("Warning: using only PDCs for group validation !!!\n");
558 while (fgets(buf
, HELPER_INPUT_BUFFER
, stdin
)) {
559 if (NULL
== strchr(buf
, '\n')) {
560 /* too large message received.. skip and deny */
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
);
564 if (strchr(buf
, '\n') != NULL
)
567 SEND_ERR("Input Too Long.");
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 SEND_ERR("Invalid Request.");
581 username
= strtok(buf
, " ");
582 for (n
= 0; (group
= strtok(NULL
, " ")) != NULL
; n
++) {
583 rfc1738_unescape(group
);
588 if (NULL
== username
) {
589 SEND_ERR("Invalid Request. No Username.");
592 rfc1738_unescape(username
);
594 if ((use_global
? Valid_Global_Groups(username
, groups
) : Valid_Local_Groups(username
, groups
))) {