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