]> git.ipfire.org Git - thirdparty/squid.git/blob - helpers/external_acl/AD_group/ext_ad_group_acl.cc
SourceFormat Enforcement
[thirdparty/squid.git] / helpers / external_acl / AD_group / ext_ad_group_acl.cc
1 /*
2 * ext_ad_group_acl: lookup group membership in a Windows
3 * Active Directory domain
4 *
5 * (C)2008-2009 Guido Serassio - Acme Consulting S.r.l.
6 *
7 * Authors:
8 * Guido Serassio <guido.serassio@acmeconsulting.it>
9 * Acme Consulting S.r.l., Italy <http://www.acmeconsulting.it>
10 *
11 * With contributions from others mentioned in the change history section
12 * below.
13 *
14 * Based on mswin_check_lm_group by Guido Serassio.
15 *
16 * Dependencies: Windows 2000 SP4 and later.
17 *
18 * This program is free software; you can redistribute it and/or modify
19 * it under the terms of the GNU General Public License as published by
20 * the Free Software Foundation; either version 2 of the License, or
21 * (at your option) any later version.
22 *
23 * This program is distributed in the hope that it will be useful,
24 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 * GNU General Public License for more details.
27 *
28 * You should have received a copy of the GNU General Public License
29 * along with this program; if not, write to the Free Software
30 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
31 *
32 * History:
33 *
34 * Version 2.1
35 * 20-09-2009 Guido Serassio
36 * Added explicit Global Catalog query
37 *
38 * Version 2.0
39 * 20-07-2009 Guido Serassio
40 * Global groups support rewritten, now is based on ADSI.
41 * New Features:
42 * - support for Domain Local, Domain Global ad Universal
43 * groups
44 * - full group nesting support
45 * Version 1.0
46 * 02-05-2008 Guido Serassio
47 * First release, based on mswin_check_lm_group.
48 *
49 * This is a helper for the external ACL interface for Squid Cache
50 *
51 * It reads from the standard input the domain username and a list of
52 * groups and tries to match it against the groups membership of the
53 * specified username.
54 *
55 * Returns `OK' if the user belongs to a group or `ERR' otherwise, as
56 * described on http://devel.squid-cache.org/external_acl/config.html
57 *
58 */
59
60 #include "squid.h"
61 #include "helpers/defines.h"
62 #include "include/util.h"
63
64 #if _SQUID_CYGWIN_
65 #include <wchar.h>
66 int _wcsicmp(const wchar_t *, const wchar_t *);
67 #endif
68
69 #if HAVE_STDIO_H
70 #include <stdio.h>
71 #endif
72 #if HAVE_CTYPE_H
73 #include <ctype.h>
74 #endif
75 #if HAVE_STRING_H
76 #include <string.h>
77 #endif
78 #if HAVE_GETOPT_H
79 #include <getopt.h>
80 #endif
81 #undef assert
82 #include <assert.h>
83 #include <windows.h>
84 #include <objbase.h>
85 #include <initguid.h>
86 #include <adsiid.h>
87 #include <iads.h>
88 #include <adshlp.h>
89 #include <adserr.h>
90 #include <lm.h>
91 #include <dsrole.h>
92 #include <sddl.h>
93
94 enum ADSI_PATH {
95 LDAP_MODE,
96 GC_MODE
97 } ADSI_Path;
98
99 int use_global = 0;
100 char *program_name;
101 pid_t mypid;
102 char *machinedomain;
103 int use_case_insensitive_compare = 0;
104 char *DefaultDomain = NULL;
105 const char NTV_VALID_DOMAIN_SEPARATOR[] = "\\/";
106 int numberofgroups = 0;
107 int WIN32_COM_initialized = 0;
108 char *WIN32_ErrorMessage = NULL;
109 wchar_t **User_Groups;
110 int User_Groups_Count = 0;
111
112 wchar_t *My_NameTranslate(wchar_t *, int, int);
113 char *Get_WIN32_ErrorMessage(HRESULT);
114
115 void
116 CloseCOM(void)
117 {
118 if (WIN32_COM_initialized == 1)
119 CoUninitialize();
120 }
121
122 HRESULT
123 GetLPBYTEtoOctetString(VARIANT * pVar, LPBYTE * ppByte)
124 {
125 HRESULT hr = E_FAIL;
126 void HUGEP *pArray;
127 long lLBound, lUBound, cElements;
128
129 if ((!pVar) || (!ppByte))
130 return E_INVALIDARG;
131 if ((pVar->n1.n2.vt) != (VT_UI1 | VT_ARRAY))
132 return E_INVALIDARG;
133
134 hr = SafeArrayGetLBound(V_ARRAY(pVar), 1, &lLBound);
135 hr = SafeArrayGetUBound(V_ARRAY(pVar), 1, &lUBound);
136
137 cElements = lUBound - lLBound + 1;
138 hr = SafeArrayAccessData(V_ARRAY(pVar), &pArray);
139 if (SUCCEEDED(hr)) {
140 LPBYTE pTemp = (LPBYTE) pArray;
141 *ppByte = (LPBYTE) CoTaskMemAlloc(cElements);
142 if (*ppByte)
143 memcpy(*ppByte, pTemp, cElements);
144 else
145 hr = E_OUTOFMEMORY;
146 }
147 SafeArrayUnaccessData(V_ARRAY(pVar));
148
149 return hr;
150 }
151
152 wchar_t *
153 Get_primaryGroup(IADs * pUser)
154 {
155 HRESULT hr;
156 VARIANT var;
157 unsigned User_primaryGroupID;
158 char tmpSID[SECURITY_MAX_SID_SIZE * 2];
159 wchar_t *wc = NULL, *result = NULL;
160 int wcsize;
161
162 VariantInit(&var);
163
164 /* Get the primaryGroupID property */
165 hr = pUser->lpVtbl->Get(pUser, L"primaryGroupID", &var);
166 if (SUCCEEDED(hr)) {
167 User_primaryGroupID = var.n1.n2.n3.uintVal;
168 } else {
169 debug("Get_primaryGroup: cannot get primaryGroupID, ERROR: %s\n", Get_WIN32_ErrorMessage(hr));
170 VariantClear(&var);
171 return result;
172 }
173 VariantClear(&var);
174
175 /*Get the objectSid property */
176 hr = pUser->lpVtbl->Get(pUser, L"objectSid", &var);
177 if (SUCCEEDED(hr)) {
178 PSID pObjectSID;
179 LPBYTE pByte = NULL;
180 char *szSID = NULL;
181 hr = GetLPBYTEtoOctetString(&var, &pByte);
182
183 pObjectSID = (PSID) pByte;
184
185 /* Convert SID to string. */
186 ConvertSidToStringSid(pObjectSID, &szSID);
187 CoTaskMemFree(pByte);
188
189 *(strrchr(szSID, '-') + 1) = '\0';
190 sprintf(tmpSID, "%s%u", szSID, User_primaryGroupID);
191
192 wcsize = MultiByteToWideChar(CP_ACP, 0, tmpSID, -1, wc, 0);
193 wc = (wchar_t *) xmalloc(wcsize * sizeof(wchar_t));
194 MultiByteToWideChar(CP_ACP, 0, tmpSID, -1, wc, wcsize);
195 LocalFree(szSID);
196
197 result = My_NameTranslate(wc, ADS_NAME_TYPE_SID_OR_SID_HISTORY_NAME, ADS_NAME_TYPE_1779);
198 safe_free(wc);
199
200 if (result == NULL)
201 debug("Get_primaryGroup: cannot get DN for %s.\n", tmpSID);
202 else
203 debug("Get_primaryGroup: Primary group DN: %S.\n", result);
204 } else
205 debug("Get_primaryGroup: cannot get objectSid, ERROR: %s\n", Get_WIN32_ErrorMessage(hr));
206 VariantClear(&var);
207 return result;
208 }
209
210 char *
211 Get_WIN32_ErrorMessage(HRESULT hr)
212 {
213 FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
214 FORMAT_MESSAGE_IGNORE_INSERTS,
215 NULL,
216 hr,
217 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
218 (LPTSTR) & WIN32_ErrorMessage,
219 0,
220 NULL);
221 return WIN32_ErrorMessage;
222 }
223
224 wchar_t *
225 My_NameTranslate(wchar_t * name, int in_format, int out_format)
226 {
227 IADsNameTranslate *pNto;
228 HRESULT hr;
229 BSTR bstr;
230 wchar_t *wc;
231
232 if (WIN32_COM_initialized == 0) {
233 hr = CoInitialize(NULL);
234 if (FAILED(hr)) {
235 debug("My_NameTranslate: cannot initialize COM interface, ERROR: %s\n", Get_WIN32_ErrorMessage(hr));
236 /* This is a fatal error */
237 exit(1);
238 }
239 WIN32_COM_initialized = 1;
240 }
241 hr = CoCreateInstance(&CLSID_NameTranslate,
242 NULL,
243 CLSCTX_INPROC_SERVER,
244 &IID_IADsNameTranslate,
245 (void **) &pNto);
246 if (FAILED(hr)) {
247 debug("My_NameTranslate: cannot create COM instance, ERROR: %s\n", Get_WIN32_ErrorMessage(hr));
248 /* This is a fatal error */
249 exit(1);
250 }
251 hr = pNto->lpVtbl->Init(pNto, ADS_NAME_INITTYPE_GC, L"");
252 if (FAILED(hr)) {
253 debug("My_NameTranslate: cannot initialise NameTranslate API, ERROR: %s\n", Get_WIN32_ErrorMessage(hr));
254 pNto->lpVtbl->Release(pNto);
255 /* This is a fatal error */
256 exit(1);
257 }
258 hr = pNto->lpVtbl->Set(pNto, in_format, name);
259 if (FAILED(hr)) {
260 debug("My_NameTranslate: cannot set translate of %S, ERROR: %s\n", name, Get_WIN32_ErrorMessage(hr));
261 pNto->lpVtbl->Release(pNto);
262 return NULL;
263 }
264 hr = pNto->lpVtbl->Get(pNto, out_format, &bstr);
265 if (FAILED(hr)) {
266 debug("My_NameTranslate: cannot get translate of %S, ERROR: %s\n", name, Get_WIN32_ErrorMessage(hr));
267 pNto->lpVtbl->Release(pNto);
268 return NULL;
269 }
270 debug("My_NameTranslate: %S translated to %S\n", name, bstr);
271
272 wc = (wchar_t *) xmalloc((wcslen(bstr) + 1) * sizeof(wchar_t));
273 wcscpy(wc, bstr);
274 SysFreeString(bstr);
275 pNto->lpVtbl->Release(pNto);
276 return wc;
277 }
278
279 wchar_t *
280 GetLDAPPath(wchar_t * Base_DN, int query_mode)
281 {
282 wchar_t *wc;
283
284 wc = (wchar_t *) xmalloc((wcslen(Base_DN) + 8) * sizeof(wchar_t));
285
286 if (query_mode == LDAP_MODE)
287 wcscpy(wc, L"LDAP://");
288 else
289 wcscpy(wc, L"GC://");
290 wcscat(wc, Base_DN);
291
292 return wc;
293 }
294
295 char *
296 GetDomainName(void)
297 {
298 static char *DomainName = NULL;
299 PDSROLE_PRIMARY_DOMAIN_INFO_BASIC pDSRoleInfo;
300 DWORD netret;
301
302 if ((netret = DsRoleGetPrimaryDomainInformation(NULL, DsRolePrimaryDomainInfoBasic, (PBYTE *) & pDSRoleInfo) == ERROR_SUCCESS)) {
303 /*
304 * Check the machine role.
305 */
306
307 if ((pDSRoleInfo->MachineRole == DsRole_RoleMemberWorkstation) ||
308 (pDSRoleInfo->MachineRole == DsRole_RoleMemberServer) ||
309 (pDSRoleInfo->MachineRole == DsRole_RoleBackupDomainController) ||
310 (pDSRoleInfo->MachineRole == DsRole_RolePrimaryDomainController)) {
311
312 size_t len = wcslen(pDSRoleInfo->DomainNameFlat);
313
314 /* allocate buffer for str + null termination */
315 safe_free(DomainName);
316 DomainName = (char *) xmalloc(len + 1);
317
318 /* copy unicode buffer */
319 WideCharToMultiByte(CP_ACP, 0, pDSRoleInfo->DomainNameFlat, -1, DomainName, len, NULL, NULL);
320
321 /* add null termination */
322 DomainName[len] = '\0';
323
324 /*
325 * Member of a domain. Display it in debug mode.
326 */
327 debug("Member of Domain %s\n", DomainName);
328 debug("Into forest %S\n", pDSRoleInfo->DomainForestName);
329
330 } else {
331 debug("Not a Domain member\n");
332 }
333 } else
334 debug("GetDomainName: ERROR DsRoleGetPrimaryDomainInformation returned: %s\n", Get_WIN32_ErrorMessage(netret));
335
336 /*
337 * Free the allocated memory.
338 */
339 if (pDSRoleInfo != NULL)
340 DsRoleFreeMemory(pDSRoleInfo);
341
342 return DomainName;
343 }
344
345 int
346 add_User_Group(wchar_t * Group)
347 {
348 wchar_t **array;
349
350 if (User_Groups_Count == 0) {
351 User_Groups = (wchar_t **) xmalloc(sizeof(wchar_t *));
352 *User_Groups = NULL;
353 ++User_Groups_Count;
354 }
355 array = User_Groups;
356 while (*array) {
357 if (wcscmp(Group, *array) == 0)
358 return 0;
359 ++array;
360 }
361 User_Groups = (wchar_t **) xrealloc(User_Groups, sizeof(wchar_t *) * (User_Groups_Count + 1));
362 User_Groups[User_Groups_Count] = NULL;
363 User_Groups[User_Groups_Count - 1] = (wchar_t *) xmalloc((wcslen(Group) + 1) * sizeof(wchar_t));
364 wcscpy(User_Groups[User_Groups_Count - 1], Group);
365 ++User_Groups_Count;
366
367 return 1;
368 }
369
370 /* returns 0 on match, -1 if no match */
371 static int
372 wccmparray(const wchar_t * str, const wchar_t ** array)
373 {
374 while (*array) {
375 debug("Windows group: %S, Squid group: %S\n", str, *array);
376 if (wcscmp(str, *array) == 0)
377 return 0;
378 ++array;
379 }
380 return -1;
381 }
382
383 /* returns 0 on match, -1 if no match */
384 static int
385 wcstrcmparray(const wchar_t * str, const char **array)
386 {
387 WCHAR wszGroup[GNLEN + 1]; // Unicode Group
388
389 while (*array) {
390 MultiByteToWideChar(CP_ACP, 0, *array,
391 strlen(*array) + 1, wszGroup, sizeof(wszGroup) / sizeof(wszGroup[0]));
392 debug("Windows group: %S, Squid group: %S\n", str, wszGroup);
393 if ((use_case_insensitive_compare ? _wcsicmp(str, wszGroup) : wcscmp(str, wszGroup)) == 0)
394 return 0;
395 ++array;
396 }
397 return -1;
398 }
399
400 HRESULT
401 Recursive_Memberof(IADs * pObj)
402 {
403 VARIANT var;
404 long lBound, uBound;
405 HRESULT hr;
406
407 VariantInit(&var);
408 hr = pObj->lpVtbl->Get(pObj, L"memberOf", &var);
409 if (SUCCEEDED(hr)) {
410 if (VT_BSTR == var.n1.n2.vt) {
411 if (add_User_Group(var.n1.n2.n3.bstrVal)) {
412 wchar_t *Group_Path;
413 IADs *pGrp;
414
415 Group_Path = GetLDAPPath(var.n1.n2.n3.bstrVal, GC_MODE);
416 hr = ADsGetObject(Group_Path, &IID_IADs, (void **) &pGrp);
417 if (SUCCEEDED(hr)) {
418 hr = Recursive_Memberof(pGrp);
419 pGrp->lpVtbl->Release(pGrp);
420 safe_free(Group_Path);
421 Group_Path = GetLDAPPath(var.n1.n2.n3.bstrVal, LDAP_MODE);
422 hr = ADsGetObject(Group_Path, &IID_IADs, (void **) &pGrp);
423 if (SUCCEEDED(hr)) {
424 hr = Recursive_Memberof(pGrp);
425 pGrp->lpVtbl->Release(pGrp);
426 } else
427 debug("Recursive_Memberof: ERROR ADsGetObject for %S failed: %s\n", Group_Path, Get_WIN32_ErrorMessage(hr));
428 } else
429 debug("Recursive_Memberof: ERROR ADsGetObject for %S failed: %s\n", Group_Path, Get_WIN32_ErrorMessage(hr));
430 safe_free(Group_Path);
431 }
432 } else {
433 if (SUCCEEDED(SafeArrayGetLBound(V_ARRAY(&var), 1, &lBound)) &&
434 SUCCEEDED(SafeArrayGetUBound(V_ARRAY(&var), 1, &uBound))) {
435 VARIANT elem;
436 while (lBound <= uBound) {
437 hr = SafeArrayGetElement(V_ARRAY(&var), &lBound, &elem);
438 if (SUCCEEDED(hr)) {
439 if (add_User_Group(elem.n1.n2.n3.bstrVal)) {
440 wchar_t *Group_Path;
441 IADs *pGrp;
442
443 Group_Path = GetLDAPPath(elem.n1.n2.n3.bstrVal, GC_MODE);
444 hr = ADsGetObject(Group_Path, &IID_IADs, (void **) &pGrp);
445 if (SUCCEEDED(hr)) {
446 hr = Recursive_Memberof(pGrp);
447 pGrp->lpVtbl->Release(pGrp);
448 safe_free(Group_Path);
449 Group_Path = GetLDAPPath(elem.n1.n2.n3.bstrVal, LDAP_MODE);
450 hr = ADsGetObject(Group_Path, &IID_IADs, (void **) &pGrp);
451 if (SUCCEEDED(hr)) {
452 hr = Recursive_Memberof(pGrp);
453 pGrp->lpVtbl->Release(pGrp);
454 safe_free(Group_Path);
455 } else
456 debug("Recursive_Memberof: ERROR ADsGetObject for %S failed: %s\n", Group_Path, Get_WIN32_ErrorMessage(hr));
457 } else
458 debug("Recursive_Memberof: ERROR ADsGetObject for %S failed: %s\n", Group_Path, Get_WIN32_ErrorMessage(hr));
459 safe_free(Group_Path);
460 }
461 VariantClear(&elem);
462 } else {
463 debug("Recursive_Memberof: ERROR SafeArrayGetElement failed: %s\n", Get_WIN32_ErrorMessage(hr));
464 VariantClear(&elem);
465 }
466 ++lBound;
467 }
468 } else
469 debug("Recursive_Memberof: ERROR SafeArrayGetxBound failed: %s\n", Get_WIN32_ErrorMessage(hr));
470 }
471 VariantClear(&var);
472 } else {
473 if (hr != E_ADS_PROPERTY_NOT_FOUND)
474 debug("Recursive_Memberof: ERROR getting memberof attribute: %s\n", Get_WIN32_ErrorMessage(hr));
475 }
476 return hr;
477 }
478
479 static wchar_t **
480 build_groups_DN_array(const char **array, char *userdomain)
481 {
482 wchar_t *wc = NULL;
483 int wcsize;
484 int source_group_format;
485 char Group[GNLEN + 1];
486
487 wchar_t **wc_array, **entry;
488
489 entry = wc_array = (wchar_t **) xmalloc((numberofgroups + 1) * sizeof(wchar_t *));
490
491 while (*array) {
492 if (strchr(*array, '/') != NULL) {
493 strncpy(Group, *array, GNLEN);
494 source_group_format = ADS_NAME_TYPE_CANONICAL;
495 } else {
496 source_group_format = ADS_NAME_TYPE_NT4;
497 if (strchr(*array, '\\') == NULL) {
498 strcpy(Group, userdomain);
499 strcat(Group, "\\");
500 strncat(Group, *array, GNLEN - sizeof(userdomain) - 1);
501 } else
502 strncpy(Group, *array, GNLEN);
503 }
504
505 wcsize = MultiByteToWideChar(CP_ACP, 0, Group, -1, wc, 0);
506 wc = (wchar_t *) xmalloc(wcsize * sizeof(wchar_t));
507 MultiByteToWideChar(CP_ACP, 0, Group, -1, wc, wcsize);
508 *entry = My_NameTranslate(wc, source_group_format, ADS_NAME_TYPE_1779);
509 safe_free(wc);
510 ++array;
511 if (*entry == NULL) {
512 debug("build_groups_DN_array: cannot get DN for '%s'.\n", Group);
513 continue;
514 }
515 ++entry;
516 }
517 *entry = NULL;
518 return wc_array;
519 }
520
521 /* returns 1 on success, 0 on failure */
522 int
523 Valid_Local_Groups(char *UserName, const char **Groups)
524 {
525 int result = 0;
526 char *Domain_Separator;
527 WCHAR wszUserName[UNLEN + 1]; /* Unicode user name */
528
529 LPLOCALGROUP_USERS_INFO_0 pBuf;
530 LPLOCALGROUP_USERS_INFO_0 pTmpBuf;
531 DWORD dwLevel = 0;
532 DWORD dwFlags = LG_INCLUDE_INDIRECT;
533 DWORD dwPrefMaxLen = -1;
534 DWORD dwEntriesRead = 0;
535 DWORD dwTotalEntries = 0;
536 NET_API_STATUS nStatus;
537 DWORD i;
538 DWORD dwTotalCount = 0;
539 LPBYTE pBufTmp = NULL;
540
541 if ((Domain_Separator = strchr(UserName, '/')) != NULL)
542 *Domain_Separator = '\\';
543
544 debug("Valid_Local_Groups: checking group membership of '%s'.\n", UserName);
545
546 /* Convert ANSI User Name and Group to Unicode */
547
548 MultiByteToWideChar(CP_ACP, 0, UserName,
549 strlen(UserName) + 1, wszUserName, sizeof(wszUserName) / sizeof(wszUserName[0]));
550
551 /*
552 * Call the NetUserGetLocalGroups function
553 * specifying information level 0.
554 *
555 * The LG_INCLUDE_INDIRECT flag specifies that the
556 * function should also return the names of the local
557 * groups in which the user is indirectly a member.
558 */
559 nStatus = NetUserGetLocalGroups(NULL,
560 wszUserName,
561 dwLevel,
562 dwFlags,
563 &pBufTmp,
564 dwPrefMaxLen,
565 &dwEntriesRead,
566 &dwTotalEntries);
567 pBuf = (LPLOCALGROUP_USERS_INFO_0) pBufTmp;
568 /*
569 * If the call succeeds,
570 */
571 if (nStatus == NERR_Success) {
572 if ((pTmpBuf = pBuf) != NULL) {
573 for (i = 0; i < dwEntriesRead; ++i) {
574 assert(pTmpBuf != NULL);
575 if (pTmpBuf == NULL) {
576 result = 0;
577 break;
578 }
579 if (wcstrcmparray(pTmpBuf->lgrui0_name, Groups) == 0) {
580 result = 1;
581 break;
582 }
583 ++pTmpBuf;
584 ++dwTotalCount;
585 }
586 }
587 } else {
588 debug("Valid_Local_Groups: ERROR NetUserGetLocalGroups returned: %s\n", Get_WIN32_ErrorMessage(nStatus));
589 result = 0;
590 }
591 /*
592 * Free the allocated memory.
593 */
594 if (pBuf != NULL)
595 NetApiBufferFree(pBuf);
596 return result;
597 }
598
599 /* returns 1 on success, 0 on failure */
600 int
601 Valid_Global_Groups(char *UserName, const char **Groups)
602 {
603 int result = 0;
604 WCHAR wszUser[DNLEN + UNLEN + 2]; /* Unicode user name */
605 char NTDomain[DNLEN + UNLEN + 2];
606
607 char *domain_qualify = NULL;
608 char User[DNLEN + UNLEN + 2];
609 size_t j;
610
611 wchar_t *User_DN, *User_LDAP_path, *User_PrimaryGroup;
612 wchar_t **wszGroups, **tmp;
613 IADs *pUser;
614 HRESULT hr;
615
616 strncpy(NTDomain, UserName, sizeof(NTDomain));
617
618 for (j = 0; j < strlen(NTV_VALID_DOMAIN_SEPARATOR); ++j) {
619 if ((domain_qualify = strchr(NTDomain, NTV_VALID_DOMAIN_SEPARATOR[j])) != NULL)
620 break;
621 }
622 if (domain_qualify == NULL) {
623 strncpy(User, DefaultDomain, DNLEN);
624 strcat(User, "\\");
625 strncat(User, UserName, UNLEN);
626 strncpy(NTDomain, DefaultDomain, DNLEN);
627 } else {
628 domain_qualify[0] = '\\';
629 strncpy(User, NTDomain, DNLEN + UNLEN + 2);
630 domain_qualify[0] = '\0';
631 }
632
633 debug("Valid_Global_Groups: checking group membership of '%s'.\n", User);
634
635 /* Convert ANSI User Name to Unicode */
636
637 MultiByteToWideChar(CP_ACP, 0, User,
638 strlen(User) + 1, wszUser,
639 sizeof(wszUser) / sizeof(wszUser[0]));
640
641 /* Get CN of User */
642 if ((User_DN = My_NameTranslate(wszUser, ADS_NAME_TYPE_NT4, ADS_NAME_TYPE_1779)) == NULL) {
643 debug("Valid_Global_Groups: cannot get DN for '%s'.\n", User);
644 return result;
645 }
646 wszGroups = build_groups_DN_array(Groups, NTDomain);
647
648 User_LDAP_path = GetLDAPPath(User_DN, GC_MODE);
649
650 hr = ADsGetObject(User_LDAP_path, &IID_IADs, (void **) &pUser);
651 if (SUCCEEDED(hr)) {
652 wchar_t *User_PrimaryGroup_Path;
653 IADs *pGrp;
654
655 User_PrimaryGroup = Get_primaryGroup(pUser);
656 if (User_PrimaryGroup == NULL)
657 debug("Valid_Global_Groups: cannot get Primary Group for '%s'.\n", User);
658 else {
659 add_User_Group(User_PrimaryGroup);
660 User_PrimaryGroup_Path = GetLDAPPath(User_PrimaryGroup, GC_MODE);
661 hr = ADsGetObject(User_PrimaryGroup_Path, &IID_IADs, (void **) &pGrp);
662 if (SUCCEEDED(hr)) {
663 hr = Recursive_Memberof(pGrp);
664 pGrp->lpVtbl->Release(pGrp);
665 safe_free(User_PrimaryGroup_Path);
666 User_PrimaryGroup_Path = GetLDAPPath(User_PrimaryGroup, LDAP_MODE);
667 hr = ADsGetObject(User_PrimaryGroup_Path, &IID_IADs, (void **) &pGrp);
668 if (SUCCEEDED(hr)) {
669 hr = Recursive_Memberof(pGrp);
670 pGrp->lpVtbl->Release(pGrp);
671 } else
672 debug("Valid_Global_Groups: ADsGetObject for %S failed, ERROR: %s\n", User_PrimaryGroup_Path, Get_WIN32_ErrorMessage(hr));
673 } else
674 debug("Valid_Global_Groups: ADsGetObject for %S failed, ERROR: %s\n", User_PrimaryGroup_Path, Get_WIN32_ErrorMessage(hr));
675 safe_free(User_PrimaryGroup_Path);
676 }
677 hr = Recursive_Memberof(pUser);
678 pUser->lpVtbl->Release(pUser);
679 safe_free(User_LDAP_path);
680 User_LDAP_path = GetLDAPPath(User_DN, LDAP_MODE);
681 hr = ADsGetObject(User_LDAP_path, &IID_IADs, (void **) &pUser);
682 if (SUCCEEDED(hr)) {
683 hr = Recursive_Memberof(pUser);
684 pUser->lpVtbl->Release(pUser);
685 } else
686 debug("Valid_Global_Groups: ADsGetObject for %S failed, ERROR: %s\n", User_LDAP_path, Get_WIN32_ErrorMessage(hr));
687
688 tmp = User_Groups;
689 while (*tmp) {
690 if (wccmparray(*tmp, wszGroups) == 0) {
691 result = 1;
692 break;
693 }
694 ++tmp;
695 }
696 } else
697 debug("Valid_Global_Groups: ADsGetObject for %S failed, ERROR: %s\n", User_LDAP_path, Get_WIN32_ErrorMessage(hr));
698
699 safe_free(User_DN);
700 safe_free(User_LDAP_path);
701 safe_free(User_PrimaryGroup);
702 tmp = wszGroups;
703 while (*tmp) {
704 safe_free(*tmp);
705 ++tmp;
706 }
707 safe_free(wszGroups);
708
709 tmp = User_Groups;
710 while (*tmp) {
711 safe_free(*tmp);
712 ++tmp;
713 }
714 safe_free(User_Groups);
715 User_Groups_Count = 0;
716
717 return result;
718 }
719
720 static void
721 usage(const char *program)
722 {
723 fprintf(stderr, "Usage: %s [-D domain][-G][-c][-d][-h]\n"
724 " -D default user Domain\n"
725 " -G enable Active Directory Global group mode\n"
726 " -c use case insensitive compare (local mode only)\n"
727 " -d enable debugging\n"
728 " -h this message\n",
729 program);
730 }
731
732 void
733 process_options(int argc, char *argv[])
734 {
735 int opt;
736
737 opterr = 0;
738 while (-1 != (opt = getopt(argc, argv, "D:Gcdh"))) {
739 switch (opt) {
740 case 'D':
741 DefaultDomain = xstrndup(optarg, DNLEN + 1);
742 strlwr(DefaultDomain);
743 break;
744 case 'G':
745 use_global = 1;
746 break;
747 case 'c':
748 use_case_insensitive_compare = 1;
749 break;
750 case 'd':
751 debug_enabled = 1;
752 break;
753 case 'h':
754 usage(argv[0]);
755 exit(0);
756 case '?':
757 opt = optopt;
758 /* fall thru to default */
759 default:
760 fprintf(stderr, "%s: FATAL: Unknown option: -%c. Exiting\n", program_name, opt);
761 usage(argv[0]);
762 exit(1);
763 break; /* not reached */
764 }
765 }
766 return;
767 }
768
769 int
770 main(int argc, char *argv[])
771 {
772 char *p;
773 char buf[HELPER_INPUT_BUFFER];
774 char *username;
775 char *group;
776 const char *groups[512];
777 int n;
778
779 if (argc > 0) { /* should always be true */
780 program_name = strrchr(argv[0], '/');
781 if (program_name == NULL)
782 program_name = argv[0];
783 } else {
784 program_name = "(unknown)";
785 }
786 mypid = getpid();
787
788 setbuf(stdout, NULL);
789 setbuf(stderr, NULL);
790
791 /* Check Command Line */
792 process_options(argc, argv);
793
794 if (use_global) {
795 if ((machinedomain = GetDomainName()) == NULL) {
796 fprintf(stderr, "%s: FATAL: Can't read machine domain\n", program_name);
797 exit(1);
798 }
799 strlwr(machinedomain);
800 if (!DefaultDomain)
801 DefaultDomain = xstrdup(machinedomain);
802 }
803 debug("External ACL win32 group helper build " __DATE__ ", " __TIME__
804 " starting up...\n");
805 if (use_global)
806 debug("Domain Global group mode enabled using '%s' as default domain.\n", DefaultDomain);
807 if (use_case_insensitive_compare)
808 debug("Warning: running in case insensitive mode !!!\n");
809
810 atexit(CloseCOM);
811
812 /* Main Loop */
813 while (fgets(buf, HELPER_INPUT_BUFFER, stdin)) {
814 if (NULL == strchr(buf, '\n')) {
815 /* too large message received.. skip and deny */
816 fprintf(stderr, "%s: ERROR: Too large: %s\n", argv[0], buf);
817 while (fgets(buf, HELPER_INPUT_BUFFER, stdin)) {
818 fprintf(stderr, "%s: ERROR: Too large..: %s\n", argv[0], buf);
819 if (strchr(buf, '\n') != NULL)
820 break;
821 }
822 SEND_ERR("Invalid Request. Too Long.");
823 continue;
824 }
825 if ((p = strchr(buf, '\n')) != NULL)
826 *p = '\0'; /* strip \n */
827 if ((p = strchr(buf, '\r')) != NULL)
828 *p = '\0'; /* strip \r */
829
830 debug("Got '%s' from Squid (length: %d).\n", buf, strlen(buf));
831
832 if (buf[0] == '\0') {
833 SEND_ERR("Invalid Request. No Input.");
834 continue;
835 }
836 username = strtok(buf, " ");
837 for (n = 0; (group = strtok(NULL, " ")) != NULL; ++n) {
838 rfc1738_unescape(group);
839 groups[n] = group;
840 }
841 groups[n] = NULL;
842 numberofgroups = n;
843
844 if (NULL == username) {
845 SEND_ERR("Invalid Request. No Username.");
846 continue;
847 }
848 rfc1738_unescape(username);
849
850 if ((use_global ? Valid_Global_Groups(username, groups) : Valid_Local_Groups(username, groups))) {
851 SEND_OK("");
852 } else {
853 SEND_ERR("");
854 }
855 err = 0;
856 }
857 return 0;
858 }