From: Amos Jeffries Date: Sun, 14 Feb 2010 05:36:46 +0000 (+1300) Subject: Author: Serassio Guido X-Git-Tag: SQUID_3_2_0_1~415 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=9fa76e99829f5b340a9a7e7f02ef62ba4937bdd0;p=thirdparty%2Fsquid.git Author: Serassio Guido Windows port: Update mswin_check_ad_group to version 2.0 The global groups support was rewritten, now is based on ADSI. New Features: - support for Domain Local, Domain Global ad Universal groups - full group nesting support This helper, like the previous version, can be compiled only using Microsoft Visual Studio because some needed library are not available on MSYS+MinGW or Cygwin. --- diff --git a/helpers/external_acl/mswin_ad_group/Makefile.am b/helpers/external_acl/mswin_ad_group/Makefile.am index 049a008090..5d8e9ba27c 100644 --- a/helpers/external_acl/mswin_ad_group/Makefile.am +++ b/helpers/external_acl/mswin_ad_group/Makefile.am @@ -6,21 +6,30 @@ # Uncomment and customize the following to suit your needs: # - libexec_PROGRAMS = mswin_check_ad_group mswin_check_ad_group_SOURCES = mswin_check_ad_group.c mswin_check_ad_group.h INCLUDES = \ -I$(top_srcdir) \ + -I$(top_srcdir)/src \ -I$(top_srcdir)/include +# +# Currently activeds and adsiid libraries are not available on MinGW or Cygwin, +# so the following library list is just a placeholder for future MinGW/Cygwin releases. +# This helper can be compiled only using Microsoft Visual Studio. +# LDADD = \ $(top_builddir)/compat/libcompat.la \ -L$(top_builddir)/lib -lmiscutil \ -lnetapi32 \ -ladvapi32 \ + -lole32 \ + -loleaut32 \ -lntdll \ + -lactiveds \ + -ladsiid \ $(XTRA_LIBS) EXTRA_DIST = readme.txt diff --git a/helpers/external_acl/mswin_ad_group/mswin_check_ad_group.c b/helpers/external_acl/mswin_ad_group/mswin_check_ad_group.c index f0edab38b5..e1603d262a 100644 --- a/helpers/external_acl/mswin_ad_group/mswin_check_ad_group.c +++ b/helpers/external_acl/mswin_ad_group/mswin_check_ad_group.c @@ -2,7 +2,7 @@ * mswin_check_ad_group: lookup group membership in a Windows * Active Directory domain * - * (C)2008 Guido Serassio - Acme Consulting S.r.l. + * (C)2008-2009 Guido Serassio - Acme Consulting S.r.l. * * Authors: * Guido Serassio @@ -31,12 +31,23 @@ * * History: * + * Version 2.1 + * 20-09-2009 Guido Serassio + * Added explicit Global Catalog query + * + * Version 2.0 + * 20-07-2009 Guido Serassio + * Global groups support rewritten, now is based on ADSI. + * New Features: + * - support for Domain Local, Domain Global ad Universal + * groups + * - full group nesting support * Version 1.0 * 02-05-2008 Guido Serassio * First release, based on mswin_check_lm_group. * * This is a helper for the external ACL interface for Squid Cache - * + * * It reads from the standard input the domain username and a list of * groups and tries to match it against the groups membership of the * specified username. @@ -66,12 +77,23 @@ int _wcsicmp(const wchar_t *, const wchar_t *); #undef assert #include #include +#include +#include +#include +#include +#include +#include #include -#include #include +#include #include "util.h" +enum ADSI_PATH { + LDAP_MODE, + GC_MODE +} ADSI_Path; + #define BUFSIZE 8192 /* the stdin buffer size */ int use_global = 0; char debug_enabled = 0; @@ -81,9 +103,203 @@ char *machinedomain; int use_case_insensitive_compare = 0; char *DefaultDomain = NULL; const char NTV_VALID_DOMAIN_SEPARATOR[] = "\\/"; +int numberofgroups = 0; +int WIN32_COM_initialized = 0; +char *WIN32_ErrorMessage = NULL; +wchar_t **User_Groups; +int User_Groups_Count = 0; #include "mswin_check_ad_group.h" +wchar_t *My_NameTranslate(wchar_t *, int, int); +char *Get_WIN32_ErrorMessage(HRESULT); + + +void +CloseCOM(void) +{ + if (WIN32_COM_initialized == 1) + CoUninitialize(); +} + + +HRESULT +GetLPBYTEtoOctetString(VARIANT * pVar, LPBYTE * ppByte) +{ + HRESULT hr = E_FAIL; + void HUGEP *pArray; + long lLBound, lUBound, cElements; + + if ((!pVar) || (!ppByte)) + return E_INVALIDARG; + if ((pVar->n1.n2.vt) != (VT_UI1 | VT_ARRAY)) + return E_INVALIDARG; + + hr = SafeArrayGetLBound(V_ARRAY(pVar), 1, &lLBound); + hr = SafeArrayGetUBound(V_ARRAY(pVar), 1, &lUBound); + + cElements = lUBound - lLBound + 1; + hr = SafeArrayAccessData(V_ARRAY(pVar), &pArray); + if (SUCCEEDED(hr)) { + LPBYTE pTemp = (LPBYTE) pArray; + *ppByte = (LPBYTE) CoTaskMemAlloc(cElements); + if (*ppByte) + memcpy(*ppByte, pTemp, cElements); + else + hr = E_OUTOFMEMORY; + } + SafeArrayUnaccessData(V_ARRAY(pVar)); + + return hr; +} + + +wchar_t * +Get_primaryGroup(IADs * pUser) +{ + HRESULT hr; + VARIANT var; + unsigned User_primaryGroupID; + char tmpSID[SECURITY_MAX_SID_SIZE * 2]; + wchar_t *wc = NULL, *result = NULL; + int wcsize; + + VariantInit(&var); + + /* Get the primaryGroupID property */ + hr = pUser->lpVtbl->Get(pUser, L"primaryGroupID", &var); + if (SUCCEEDED(hr)) { + User_primaryGroupID = var.n1.n2.n3.uintVal; + } else { + debug("Get_primaryGroup: cannot get primaryGroupID, ERROR: %s\n", Get_WIN32_ErrorMessage(hr)); + VariantClear(&var); + return result; + } + VariantClear(&var); + + /*Get the objectSid property */ + hr = pUser->lpVtbl->Get(pUser, L"objectSid", &var); + if (SUCCEEDED(hr)) { + PSID pObjectSID; + LPBYTE pByte = NULL; + char *szSID = NULL; + hr = GetLPBYTEtoOctetString(&var, &pByte); + + pObjectSID = (PSID) pByte; + + /* Convert SID to string. */ + ConvertSidToStringSid(pObjectSID, &szSID); + CoTaskMemFree(pByte); + + *(strrchr(szSID, '-') + 1) = '\0'; + sprintf(tmpSID, "%s%u", szSID, User_primaryGroupID); + + wcsize = MultiByteToWideChar(CP_ACP, 0, tmpSID, -1, wc, 0); + wc = (wchar_t *) xmalloc(wcsize * sizeof(wchar_t)); + MultiByteToWideChar(CP_ACP, 0, tmpSID, -1, wc, wcsize); + LocalFree(szSID); + + result = My_NameTranslate(wc, ADS_NAME_TYPE_SID_OR_SID_HISTORY_NAME, ADS_NAME_TYPE_1779); + safe_free(wc); + + if (result == NULL) + debug("Get_primaryGroup: cannot get DN for %s.\n", tmpSID); + else + debug("Get_primaryGroup: Primary group DN: %S.\n", result); + } else + debug("Get_primaryGroup: cannot get objectSid, ERROR: %s\n", Get_WIN32_ErrorMessage(hr)); + VariantClear(&var); + return result; +} + + +char * +Get_WIN32_ErrorMessage(HRESULT hr) +{ + FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + hr, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) & WIN32_ErrorMessage, + 0, + NULL); + return WIN32_ErrorMessage; +} + + +wchar_t * +My_NameTranslate(wchar_t * name, int in_format, int out_format) +{ + IADsNameTranslate *pNto; + HRESULT hr; + BSTR bstr; + wchar_t *wc; + + if (WIN32_COM_initialized == 0) { + hr = CoInitialize(NULL); + if (FAILED(hr)) { + debug("My_NameTranslate: cannot initialize COM interface, ERROR: %s\n", Get_WIN32_ErrorMessage(hr)); + /* This is a fatal error */ + exit(1); + } + WIN32_COM_initialized = 1; + } + hr = CoCreateInstance(&CLSID_NameTranslate, + NULL, + CLSCTX_INPROC_SERVER, + &IID_IADsNameTranslate, + (void **) &pNto); + if (FAILED(hr)) { + debug("My_NameTranslate: cannot create COM instance, ERROR: %s\n", Get_WIN32_ErrorMessage(hr)); + /* This is a fatal error */ + exit(1); + } + hr = pNto->lpVtbl->Init(pNto, ADS_NAME_INITTYPE_GC, L""); + if (FAILED(hr)) { + debug("My_NameTranslate: cannot initialise NameTranslate API, ERROR: %s\n", Get_WIN32_ErrorMessage(hr)); + pNto->lpVtbl->Release(pNto); + /* This is a fatal error */ + exit(1); + } + hr = pNto->lpVtbl->Set(pNto, in_format, name); + if (FAILED(hr)) { + debug("My_NameTranslate: cannot set translate of %S, ERROR: %s\n", name, Get_WIN32_ErrorMessage(hr)); + pNto->lpVtbl->Release(pNto); + return NULL; + } + hr = pNto->lpVtbl->Get(pNto, out_format, &bstr); + if (FAILED(hr)) { + debug("My_NameTranslate: cannot get translate of %S, ERROR: %s\n", name, Get_WIN32_ErrorMessage(hr)); + pNto->lpVtbl->Release(pNto); + return NULL; + } + debug("My_NameTranslate: %S translated to %S\n", name, bstr); + + wc = (wchar_t *) xmalloc((wcslen(bstr) + 1) * sizeof(wchar_t)); + wcscpy(wc, bstr); + SysFreeString(bstr); + pNto->lpVtbl->Release(pNto); + return wc; +} + + +wchar_t * +GetLDAPPath(wchar_t * Base_DN, int query_mode) +{ + wchar_t *wc; + + wc = (wchar_t *) xmalloc((wcslen(Base_DN) + 8) * sizeof(wchar_t)); + + if (query_mode == LDAP_MODE) + wcscpy(wc, L"LDAP://"); + else + wcscpy(wc, L"GC://"); + wcscat(wc, Base_DN); + + return wc; +} + char * GetDomainName(void) @@ -93,50 +309,89 @@ GetDomainName(void) DWORD netret; if ((netret = DsRoleGetPrimaryDomainInformation(NULL, DsRolePrimaryDomainInfoBasic, (PBYTE *) & pDSRoleInfo) == ERROR_SUCCESS)) { - /* - * Check the machine role. - */ - - if ((pDSRoleInfo->MachineRole == DsRole_RoleMemberWorkstation) || - (pDSRoleInfo->MachineRole == DsRole_RoleMemberServer) || - (pDSRoleInfo->MachineRole == DsRole_RoleBackupDomainController) || - (pDSRoleInfo->MachineRole == DsRole_RolePrimaryDomainController)) { - - size_t len = wcslen(pDSRoleInfo->DomainNameFlat); - - /* allocate buffer for str + null termination */ - safe_free(DomainName); - DomainName = (char *) xmalloc(len + 1); - if (DomainName == NULL) - return NULL; - - /* copy unicode buffer */ - WideCharToMultiByte(CP_ACP, 0, pDSRoleInfo->DomainNameFlat, -1, DomainName, len, NULL, NULL); - - /* add null termination */ - DomainName[len] = '\0'; - - /* - * Member of a domain. Display it in debug mode. - */ - debug("Member of Domain %s\n", DomainName); - debug("Into forest %S\n", pDSRoleInfo->DomainForestName); - - } else { - debug("Not a Domain member\n"); - } + /* + * Check the machine role. + */ + + if ((pDSRoleInfo->MachineRole == DsRole_RoleMemberWorkstation) || + (pDSRoleInfo->MachineRole == DsRole_RoleMemberServer) || + (pDSRoleInfo->MachineRole == DsRole_RoleBackupDomainController) || + (pDSRoleInfo->MachineRole == DsRole_RolePrimaryDomainController)) { + + size_t len = wcslen(pDSRoleInfo->DomainNameFlat); + + /* allocate buffer for str + null termination */ + safe_free(DomainName); + DomainName = (char *) xmalloc(len + 1); + + /* copy unicode buffer */ + WideCharToMultiByte(CP_ACP, 0, pDSRoleInfo->DomainNameFlat, -1, DomainName, len, NULL, NULL); + + /* add null termination */ + DomainName[len] = '\0'; + + /* + * Member of a domain. Display it in debug mode. + */ + debug("Member of Domain %s\n", DomainName); + debug("Into forest %S\n", pDSRoleInfo->DomainForestName); + + } else { + debug("Not a Domain member\n"); + } } else - debug("DsRoleGetPrimaryDomainInformation Error: %ld\n", netret); + debug("GetDomainName: ERROR DsRoleGetPrimaryDomainInformation returned: %s\n", Get_WIN32_ErrorMessage(netret)); /* * Free the allocated memory. */ if (pDSRoleInfo != NULL) - DsRoleFreeMemory(pDSRoleInfo); + DsRoleFreeMemory(pDSRoleInfo); return DomainName; } + +int +add_User_Group(wchar_t * Group) +{ + wchar_t **array; + + if (User_Groups_Count == 0) { + User_Groups = (wchar_t **) xmalloc(sizeof(wchar_t *)); + *User_Groups = NULL; + User_Groups_Count++; + } + array = User_Groups; + while (*array) { + if (wcscmp(Group, *array) == 0) + return 0; + array++; + } + User_Groups = (wchar_t **) xrealloc(User_Groups, sizeof(wchar_t *) * (User_Groups_Count + 1)); + User_Groups[User_Groups_Count] = NULL; + User_Groups[User_Groups_Count - 1] = (wchar_t *) xmalloc((wcslen(Group) + 1) * sizeof(wchar_t)); + wcscpy(User_Groups[User_Groups_Count - 1], Group); + User_Groups_Count++; + + return 1; +} + + +/* returns 0 on match, -1 if no match */ +static int +wccmparray(const wchar_t * str, const wchar_t ** array) +{ + while (*array) { + debug("Windows group: %S, Squid group: %S\n", str, *array); + if (wcscmp(str, *array) == 0) + return 0; + array++; + } + return -1; +} + + /* returns 0 on match, -1 if no match */ static int wcstrcmparray(const wchar_t * str, const char **array) @@ -144,23 +399,147 @@ wcstrcmparray(const wchar_t * str, const char **array) WCHAR wszGroup[GNLEN + 1]; // Unicode Group while (*array) { - MultiByteToWideChar(CP_ACP, 0, *array, - strlen(*array) + 1, wszGroup, sizeof(wszGroup) / sizeof(wszGroup[0])); - debug("Windows group: %S, Squid group: %S\n", str, wszGroup); - if ((use_case_insensitive_compare ? _wcsicmp(str, wszGroup) : wcscmp(str, wszGroup)) == 0) - return 0; - array++; + MultiByteToWideChar(CP_ACP, 0, *array, + strlen(*array) + 1, wszGroup, sizeof(wszGroup) / sizeof(wszGroup[0])); + debug("Windows group: %S, Squid group: %S\n", str, wszGroup); + if ((use_case_insensitive_compare ? _wcsicmp(str, wszGroup) : wcscmp(str, wszGroup)) == 0) + return 0; + array++; } return -1; } + +HRESULT +Recursive_Memberof(IADs * pObj) +{ + VARIANT var; + long lBound, uBound; + HRESULT hr; + + VariantInit(&var); + hr = pObj->lpVtbl->Get(pObj, L"memberOf", &var); + if (SUCCEEDED(hr)) { + if (VT_BSTR == var.n1.n2.vt) { + if (add_User_Group(var.n1.n2.n3.bstrVal)) { + wchar_t *Group_Path; + IADs *pGrp; + + Group_Path = GetLDAPPath(var.n1.n2.n3.bstrVal, GC_MODE); + hr = ADsGetObject(Group_Path, &IID_IADs, (void **) &pGrp); + if (SUCCEEDED(hr)) { + hr = Recursive_Memberof(pGrp); + pGrp->lpVtbl->Release(pGrp); + safe_free(Group_Path); + Group_Path = GetLDAPPath(var.n1.n2.n3.bstrVal, LDAP_MODE); + hr = ADsGetObject(Group_Path, &IID_IADs, (void **) &pGrp); + if (SUCCEEDED(hr)) { + hr = Recursive_Memberof(pGrp); + pGrp->lpVtbl->Release(pGrp); + } else + debug("Recursive_Memberof: ERROR ADsGetObject for %S failed: %s\n", Group_Path, Get_WIN32_ErrorMessage(hr)); + } else + debug("Recursive_Memberof: ERROR ADsGetObject for %S failed: %s\n", Group_Path, Get_WIN32_ErrorMessage(hr)); + safe_free(Group_Path); + } + } else { + if (SUCCEEDED(SafeArrayGetLBound(V_ARRAY(&var), 1, &lBound)) && + SUCCEEDED(SafeArrayGetUBound(V_ARRAY(&var), 1, &uBound))) { + VARIANT elem; + while (lBound <= uBound) { + hr = SafeArrayGetElement(V_ARRAY(&var), &lBound, &elem); + if (SUCCEEDED(hr)) { + if (add_User_Group(elem.n1.n2.n3.bstrVal)) { + wchar_t *Group_Path; + IADs *pGrp; + + Group_Path = GetLDAPPath(elem.n1.n2.n3.bstrVal, GC_MODE); + hr = ADsGetObject(Group_Path, &IID_IADs, (void **) &pGrp); + if (SUCCEEDED(hr)) { + hr = Recursive_Memberof(pGrp); + pGrp->lpVtbl->Release(pGrp); + safe_free(Group_Path); + Group_Path = GetLDAPPath(elem.n1.n2.n3.bstrVal, LDAP_MODE); + hr = ADsGetObject(Group_Path, &IID_IADs, (void **) &pGrp); + if (SUCCEEDED(hr)) { + hr = Recursive_Memberof(pGrp); + pGrp->lpVtbl->Release(pGrp); + safe_free(Group_Path); + } else + debug("Recursive_Memberof: ERROR ADsGetObject for %S failed: %s\n", Group_Path, Get_WIN32_ErrorMessage(hr)); + } else + debug("Recursive_Memberof: ERROR ADsGetObject for %S failed: %s\n", Group_Path, Get_WIN32_ErrorMessage(hr)); + safe_free(Group_Path); + } + VariantClear(&elem); + } else { + debug("Recursive_Memberof: ERROR SafeArrayGetElement failed: %s\n", Get_WIN32_ErrorMessage(hr)); + VariantClear(&elem); + } + ++lBound; + } + } else + debug("Recursive_Memberof: ERROR SafeArrayGetxBound failed: %s\n", Get_WIN32_ErrorMessage(hr)); + } + VariantClear(&var); + } else { + if (hr != E_ADS_PROPERTY_NOT_FOUND) + debug("Recursive_Memberof: ERROR getting memberof attribute: %s\n", Get_WIN32_ErrorMessage(hr)); + } + return hr; +} + + +static wchar_t ** +build_groups_DN_array(const char **array, char *userdomain) +{ + wchar_t *wc = NULL; + int wcsize; + int source_group_format; + char Group[GNLEN + 1]; + + wchar_t **wc_array, **entry; + + entry = wc_array = (wchar_t **) xmalloc((numberofgroups + 1) * sizeof(wchar_t *)); + + while (*array) { + if (strchr(*array, '/') != NULL) { + strncpy(Group, *array, GNLEN); + source_group_format = ADS_NAME_TYPE_CANONICAL; + } else { + source_group_format = ADS_NAME_TYPE_NT4; + if (strchr(*array, '\\') == NULL) { + strcpy(Group, userdomain); + strcat(Group, "\\"); + strncat(Group, *array, GNLEN - sizeof(userdomain) - 1); + } else + strncpy(Group, *array, GNLEN); + } + + wcsize = MultiByteToWideChar(CP_ACP, 0, Group, -1, wc, 0); + wc = (wchar_t *) xmalloc(wcsize * sizeof(wchar_t)); + MultiByteToWideChar(CP_ACP, 0, Group, -1, wc, wcsize); + *entry = My_NameTranslate(wc, source_group_format, ADS_NAME_TYPE_1779); + safe_free(wc); + array++; + if (*entry == NULL) { + debug("build_groups_DN_array: cannot get DN for '%s'.\n", Group); + continue; + } + entry++; + } + *entry = NULL; + return wc_array; +} + + /* returns 1 on success, 0 on failure */ int Valid_Local_Groups(char *UserName, const char **Groups) { int result = 0; char *Domain_Separator; - WCHAR wszUserName[UNLEN + 1]; // Unicode user name + WCHAR wszUserName[UNLEN + 1]; /* Unicode user name */ LPLOCALGROUP_USERS_INFO_0 pBuf; LPLOCALGROUP_USERS_INFO_0 pTmpBuf; @@ -175,58 +554,60 @@ Valid_Local_Groups(char *UserName, const char **Groups) LPBYTE pBufTmp = NULL; if ((Domain_Separator = strchr(UserName, '/')) != NULL) - *Domain_Separator = '\\'; + *Domain_Separator = '\\'; debug("Valid_Local_Groups: checking group membership of '%s'.\n", UserName); - /* Convert ANSI User Name and Group to Unicode */ +/* Convert ANSI User Name and Group to Unicode */ MultiByteToWideChar(CP_ACP, 0, UserName, - strlen(UserName) + 1, wszUserName, sizeof(wszUserName) / sizeof(wszUserName[0])); + strlen(UserName) + 1, wszUserName, sizeof(wszUserName) / sizeof(wszUserName[0])); /* - * Call the NetUserGetLocalGroups function + * Call the NetUserGetLocalGroups function * specifying information level 0. - * - * The LG_INCLUDE_INDIRECT flag specifies that the - * function should also return the names of the local + * + * The LG_INCLUDE_INDIRECT flag specifies that the + * function should also return the names of the local * groups in which the user is indirectly a member. */ nStatus = NetUserGetLocalGroups(NULL, - wszUserName, - dwLevel, - dwFlags, - &pBufTmp, - dwPrefMaxLen, - &dwEntriesRead, - &dwTotalEntries); + wszUserName, + dwLevel, + dwFlags, + &pBufTmp, + dwPrefMaxLen, + &dwEntriesRead, + &dwTotalEntries); pBuf = (LPLOCALGROUP_USERS_INFO_0) pBufTmp; /* * If the call succeeds, */ if (nStatus == NERR_Success) { - if ((pTmpBuf = pBuf) != NULL) { - for (i = 0; i < dwEntriesRead; i++) { - assert(pTmpBuf != NULL); - if (pTmpBuf == NULL) { - result = 0; - break; - } - if (wcstrcmparray(pTmpBuf->lgrui0_name, Groups) == 0) { - result = 1; - break; - } - pTmpBuf++; - dwTotalCount++; - } - } - } else - result = 0; - /* - * Free the allocated memory. - */ + if ((pTmpBuf = pBuf) != NULL) { + for (i = 0; i < dwEntriesRead; i++) { + assert(pTmpBuf != NULL); + if (pTmpBuf == NULL) { + result = 0; + break; + } + if (wcstrcmparray(pTmpBuf->lgrui0_name, Groups) == 0) { + result = 1; + break; + } + pTmpBuf++; + dwTotalCount++; + } + } + } else { + debug("Valid_Local_Groups: ERROR NetUserGetLocalGroups returned: %s\n", Get_WIN32_ErrorMessage(nStatus)); + result = 0; + } +/* + * Free the allocated memory. + */ if (pBuf != NULL) - NetApiBufferFree(pBuf); + NetApiBufferFree(pBuf); return result; } @@ -236,125 +617,132 @@ int Valid_Global_Groups(char *UserName, const char **Groups) { int result = 0; - WCHAR wszUserName[UNLEN + 1]; // Unicode user name - - WCHAR wszDomainControllerName[UNCLEN + 1]; - + WCHAR wszUser[DNLEN + UNLEN + 2]; /* Unicode user name */ char NTDomain[DNLEN + UNLEN + 2]; + char *domain_qualify = NULL; - char User[UNLEN + 1]; + char User[DNLEN + UNLEN + 2]; size_t j; - LPGROUP_USERS_INFO_0 pUsrBuf = NULL; - LPGROUP_USERS_INFO_0 pTmpBuf; - PDOMAIN_CONTROLLER_INFO pDCInfo = NULL; - DWORD dwLevel = 0; - DWORD dwPrefMaxLen = -1; - DWORD dwEntriesRead = 0; - DWORD dwTotalEntries = 0; - NET_API_STATUS nStatus; - DWORD i; - DWORD dwTotalCount = 0; - LPBYTE pBufTmp = NULL; + wchar_t *User_DN, *User_LDAP_path, *User_PrimaryGroup; + wchar_t **wszGroups, **tmp; + IADs *pUser; + HRESULT hr; strncpy(NTDomain, UserName, sizeof(NTDomain)); for (j = 0; j < strlen(NTV_VALID_DOMAIN_SEPARATOR); j++) { - if ((domain_qualify = strchr(NTDomain, NTV_VALID_DOMAIN_SEPARATOR[j])) != NULL) - break; + if ((domain_qualify = strchr(NTDomain, NTV_VALID_DOMAIN_SEPARATOR[j])) != NULL) + break; } if (domain_qualify == NULL) { - strcpy(User, NTDomain); - strcpy(NTDomain, DefaultDomain); + strncpy(User, DefaultDomain, DNLEN); + strcat(User, "\\"); + strncat(User, UserName, UNLEN); + strncpy(NTDomain, DefaultDomain, DNLEN); } else { - strcpy(User, domain_qualify + 1); - domain_qualify[0] = '\0'; - strlwr(NTDomain); + domain_qualify[0] = '\\'; + strncpy(User, NTDomain, DNLEN + UNLEN + 2); + domain_qualify[0] = '\0'; } - debug("Valid_Global_Groups: checking group membership of '%s\\%s'.\n", NTDomain, User); + debug("Valid_Global_Groups: checking group membership of '%s'.\n", User); /* Convert ANSI User Name to Unicode */ MultiByteToWideChar(CP_ACP, 0, User, - strlen(User) + 1, wszUserName, - sizeof(wszUserName) / sizeof(wszUserName[0])); - - /* Query AD for a DC */ + strlen(User) + 1, wszUser, + sizeof(wszUser) / sizeof(wszUser[0])); - if (DsGetDcName(NULL, NTDomain, NULL, NULL, DS_IS_FLAT_NAME | DS_RETURN_FLAT_NAME, &pDCInfo) != NO_ERROR) { - fprintf(stderr, "%s DsGetDcName() failed.'\n", myname); - if (pDCInfo != NULL) - NetApiBufferFree(pDCInfo); - return result; + /* Get CN of User */ + if ((User_DN = My_NameTranslate(wszUser, ADS_NAME_TYPE_NT4, ADS_NAME_TYPE_1779)) == NULL) { + debug("Valid_Global_Groups: cannot get DN for '%s'.\n", User); + return result; } - /* Convert ANSI Domain Controller Name to Unicode */ - - MultiByteToWideChar(CP_ACP, 0, pDCInfo->DomainControllerName, - strlen(pDCInfo->DomainControllerName) + 1, wszDomainControllerName, - sizeof(wszDomainControllerName) / sizeof(wszDomainControllerName[0])); - - debug("Using '%S' as DC for '%s' user's domain.\n", wszDomainControllerName, NTDomain); - debug("DC Active Directory Site is %s\n", pDCInfo->DcSiteName); - debug("Machine Active Directory Site is %s\n", pDCInfo->ClientSiteName); + wszGroups = build_groups_DN_array(Groups, NTDomain); + + User_LDAP_path = GetLDAPPath(User_DN, GC_MODE); + + hr = ADsGetObject(User_LDAP_path, &IID_IADs, (void **) &pUser); + if (SUCCEEDED(hr)) { + wchar_t *User_PrimaryGroup_Path; + IADs *pGrp; + + User_PrimaryGroup = Get_primaryGroup(pUser); + if (User_PrimaryGroup == NULL) + debug("Valid_Global_Groups: cannot get Primary Group for '%s'.\n", User); + else { + add_User_Group(User_PrimaryGroup); + User_PrimaryGroup_Path = GetLDAPPath(User_PrimaryGroup, GC_MODE); + hr = ADsGetObject(User_PrimaryGroup_Path, &IID_IADs, (void **) &pGrp); + if (SUCCEEDED(hr)) { + hr = Recursive_Memberof(pGrp); + pGrp->lpVtbl->Release(pGrp); + safe_free(User_PrimaryGroup_Path); + User_PrimaryGroup_Path = GetLDAPPath(User_PrimaryGroup, LDAP_MODE); + hr = ADsGetObject(User_PrimaryGroup_Path, &IID_IADs, (void **) &pGrp); + if (SUCCEEDED(hr)) { + hr = Recursive_Memberof(pGrp); + pGrp->lpVtbl->Release(pGrp); + } else + debug("Valid_Global_Groups: ADsGetObject for %S failed, ERROR: %s\n", User_PrimaryGroup_Path, Get_WIN32_ErrorMessage(hr)); + } else + debug("Valid_Global_Groups: ADsGetObject for %S failed, ERROR: %s\n", User_PrimaryGroup_Path, Get_WIN32_ErrorMessage(hr)); + safe_free(User_PrimaryGroup_Path); + } + hr = Recursive_Memberof(pUser); + pUser->lpVtbl->Release(pUser); + safe_free(User_LDAP_path); + User_LDAP_path = GetLDAPPath(User_DN, LDAP_MODE); + hr = ADsGetObject(User_LDAP_path, &IID_IADs, (void **) &pUser); + if (SUCCEEDED(hr)) { + hr = Recursive_Memberof(pUser); + pUser->lpVtbl->Release(pUser); + } else + debug("Valid_Global_Groups: ADsGetObject for %S failed, ERROR: %s\n", User_LDAP_path, Get_WIN32_ErrorMessage(hr)); + + tmp = User_Groups; + while (*tmp) { + if (wccmparray(*tmp, wszGroups) == 0) { + result = 1; + break; + } + tmp++; + } + } else + debug("Valid_Global_Groups: ADsGetObject for %S failed, ERROR: %s\n", User_LDAP_path, Get_WIN32_ErrorMessage(hr)); + + safe_free(User_DN); + safe_free(User_LDAP_path); + safe_free(User_PrimaryGroup); + tmp = wszGroups; + while (*tmp) { + safe_free(*tmp); + tmp++; + } + safe_free(wszGroups); - /* - * Call the NetUserGetGroups function - * specifying information level 0. - */ - dwLevel = 0; - pBufTmp = NULL; - nStatus = NetUserGetGroups(wszDomainControllerName, - wszUserName, - dwLevel, - &pBufTmp, - dwPrefMaxLen, - &dwEntriesRead, - &dwTotalEntries); - pUsrBuf = (LPGROUP_USERS_INFO_0) pBufTmp; - /* - * If the call succeeds, - */ - if (nStatus == NERR_Success) { - if ((pTmpBuf = pUsrBuf) != NULL) { - for (i = 0; i < dwEntriesRead; i++) { - assert(pTmpBuf != NULL); - if (pTmpBuf == NULL) { - result = 0; - break; - } - if (wcstrcmparray(pTmpBuf->grui0_name, Groups) == 0) { - result = 1; - break; - } - pTmpBuf++; - dwTotalCount++; - } - } - } else { - result = 0; - fprintf(stderr, "%s NetUserGetGroups() failed.'\n", myname); + tmp = User_Groups; + while (*tmp) { + safe_free(*tmp); + tmp++; } - /* - * Free the allocated memory. - */ - if (pUsrBuf != NULL) - NetApiBufferFree(pUsrBuf); - if (pDCInfo != NULL) - NetApiBufferFree((LPVOID) pDCInfo); + safe_free(User_Groups); + User_Groups_Count = 0; + return result; } static void usage(char *program) { - fprintf(stderr, "Usage: %s [-D domain][-G][-P][-c][-d][-h]\n" - " -D default user Domain\n" - " -G enable Domain Global group mode\n" - " -c use case insensitive compare\n" - " -d enable debugging\n" - " -h this message\n", - program); + fprintf(stderr, "Usage: %s [-D domain][-G][-c][-d][-h]\n" + " -D default user Domain\n" + " -G enable Active Directory Global group mode\n" + " -c use case insensitive compare (local mode only)\n" + " -d enable debugging\n" + " -h this message\n", + program); } void @@ -364,32 +752,32 @@ process_options(int argc, char *argv[]) opterr = 0; while (-1 != (opt = getopt(argc, argv, "D:Gcdh"))) { - switch (opt) { - case 'D': - DefaultDomain = xstrndup(optarg, DNLEN + 1); - strlwr(DefaultDomain); - break; - case 'G': - use_global = 1; - break; - case 'c': - use_case_insensitive_compare = 1; - break; - case 'd': - debug_enabled = 1; - break; - case 'h': - usage(argv[0]); - exit(0); - case '?': - opt = optopt; - /* fall thru to default */ - default: - fprintf(stderr, "%s Unknown option: -%c. Exiting\n", myname, opt); - usage(argv[0]); - exit(1); - break; /* not reached */ - } + switch (opt) { + case 'D': + DefaultDomain = xstrndup(optarg, DNLEN + 1); + strlwr(DefaultDomain); + break; + case 'G': + use_global = 1; + break; + case 'c': + use_case_insensitive_compare = 1; + break; + case 'd': + debug_enabled = 1; + break; + case 'h': + usage(argv[0]); + exit(0); + case '?': + opt = optopt; + /* fall thru to default */ + default: + fprintf(stderr, "%s Unknown option: -%c. Exiting\n", myname, opt); + usage(argv[0]); + exit(1); + break; /* not reached */ + } } return; } @@ -407,11 +795,11 @@ main(int argc, char *argv[]) int n; if (argc > 0) { /* should always be true */ - myname = strrchr(argv[0], '/'); - if (myname == NULL) - myname = argv[0]; + myname = strrchr(argv[0], '/'); + if (myname == NULL) + myname = argv[0]; } else { - myname = "(unknown)"; + myname = "(unknown)"; } mypid = getpid(); @@ -422,64 +810,68 @@ main(int argc, char *argv[]) process_options(argc, argv); if (use_global) { - if ((machinedomain = GetDomainName()) == NULL) { - fprintf(stderr, "%s Can't read machine domain\n", myname); - exit(1); - } - strlwr(machinedomain); - if (!DefaultDomain) - DefaultDomain = xstrdup(machinedomain); + if ((machinedomain = GetDomainName()) == NULL) { + fprintf(stderr, "%s Can't read machine domain\n", myname); + exit(1); + } + strlwr(machinedomain); + if (!DefaultDomain) + DefaultDomain = xstrdup(machinedomain); } debug("External ACL win32 group helper build " __DATE__ ", " __TIME__ - " starting up...\n"); + " starting up...\n"); if (use_global) - debug("Domain Global group mode enabled using '%s' as default domain.\n", DefaultDomain); + debug("Domain Global group mode enabled using '%s' as default domain.\n", DefaultDomain); if (use_case_insensitive_compare) - debug("Warning: running in case insensitive mode !!!\n"); + debug("Warning: running in case insensitive mode !!!\n"); + + atexit(CloseCOM); + /* Main Loop */ while (fgets(buf, sizeof(buf), stdin)) { - if (NULL == strchr(buf, '\n')) { - /* too large message received.. skip and deny */ - fprintf(stderr, "%s: ERROR: Too large: %s\n", argv[0], buf); - while (fgets(buf, sizeof(buf), stdin)) { - fprintf(stderr, "%s: ERROR: Too large..: %s\n", argv[0], buf); - if (strchr(buf, '\n') != NULL) - break; - } - goto error; - } - if ((p = strchr(buf, '\n')) != NULL) - *p = '\0'; /* strip \n */ - if ((p = strchr(buf, '\r')) != NULL) - *p = '\0'; /* strip \r */ - - debug("Got '%s' from Squid (length: %d).\n", buf, strlen(buf)); - - if (buf[0] == '\0') { - fprintf(stderr, "Invalid Request\n"); - goto error; - } - username = strtok(buf, " "); - for (n = 0; (group = strtok(NULL, " ")) != NULL; n++) { - rfc1738_unescape(group); - groups[n] = group; - } - groups[n] = NULL; - - if (NULL == username) { - fprintf(stderr, "Invalid Request\n"); - goto error; - } - rfc1738_unescape(username); - - if ((use_global ? Valid_Global_Groups(username, groups) : Valid_Local_Groups(username, groups))) { - printf("OK\n"); - } else { -error: - printf("ERR\n"); - } - err = 0; + if (NULL == strchr(buf, '\n')) { + /* too large message received.. skip and deny */ + fprintf(stderr, "%s: ERROR: Too large: %s\n", argv[0], buf); + while (fgets(buf, sizeof(buf), stdin)) { + fprintf(stderr, "%s: ERROR: Too large..: %s\n", argv[0], buf); + if (strchr(buf, '\n') != NULL) + break; + } + goto error; + } + if ((p = strchr(buf, '\n')) != NULL) + *p = '\0'; /* strip \n */ + if ((p = strchr(buf, '\r')) != NULL) + *p = '\0'; /* strip \r */ + + debug("Got '%s' from Squid (length: %d).\n", buf, strlen(buf)); + + if (buf[0] == '\0') { + fprintf(stderr, "Invalid Request\n"); + goto error; + } + username = strtok(buf, " "); + for (n = 0; (group = strtok(NULL, " ")) != NULL; n++) { + rfc1738_unescape(group); + groups[n] = group; + } + groups[n] = NULL; + numberofgroups = n; + + if (NULL == username) { + fprintf(stderr, "Invalid Request\n"); + goto error; + } + rfc1738_unescape(username); + + if ((use_global ? Valid_Global_Groups(username, groups) : Valid_Local_Groups(username, groups))) { + SEND("OK"); + } else { + error: + SEND("ERR"); + } + err = 0; } return 0; } diff --git a/helpers/external_acl/mswin_ad_group/mswin_check_ad_group.h b/helpers/external_acl/mswin_ad_group/mswin_check_ad_group.h index 041879ed21..5b77b2e4da 100644 --- a/helpers/external_acl/mswin_ad_group/mswin_check_ad_group.h +++ b/helpers/external_acl/mswin_ad_group/mswin_check_ad_group.h @@ -39,29 +39,39 @@ #include static char *__foo; extern char debug_enabled; +extern char *WIN32_ErrorMessage; #define debug(X...) if (debug_enabled) { \ fprintf(stderr,"%s[%d](%s:%d): ", myname, mypid, \ ((__foo=strrchr(__FILE__,'/'))==NULL?__FILE__:__foo+1),\ __LINE__);\ - fprintf(stderr,X); } + fprintf(stderr,X); \ + if (WIN32_ErrorMessage != NULL){ \ + LocalFree(WIN32_ErrorMessage); \ + WIN32_ErrorMessage = NULL; \ + } } #else /* DEBUG */ #define debug(X...) /* */ #endif /* DEBUG */ #else /* __GNUC__ */ extern char debug_enabled; +extern char *WIN32_ErrorMessage; static void debug(char *format,...) { #ifdef DEBUG #ifdef _SQUID_MSWIN_ if (debug_enabled) { - va_list args; + va_list args; - va_start(args, format); - fprintf(stderr, "%s[%d]: ", myname, mypid); - vfprintf(stderr, format, args); - fprintf(stderr, "\n"); - va_end(args); + va_start(args, format); + fprintf(stderr, "%s[%d]: ", myname, mypid); + vfprintf(stderr, format, args); + fprintf(stderr, "\n"); + va_end(args); + if (WIN32_ErrorMessage != NULL) { + LocalFree(WIN32_ErrorMessage); + WIN32_ErrorMessage = NULL; + } } #endif /* _SQUID_MSWIN_ */ #endif /* DEBUG */ diff --git a/helpers/external_acl/mswin_ad_group/readme.txt b/helpers/external_acl/mswin_ad_group/readme.txt index af8cc8598f..0b2ecb66ce 100644 --- a/helpers/external_acl/mswin_ad_group/readme.txt +++ b/helpers/external_acl/mswin_ad_group/readme.txt @@ -1,5 +1,5 @@ -This is the readme.txt file for mswin_check_ad_group, an external +This is the readme.txt file for mswin_check_ad_group 2.0, an external helper for the External ACL Scheme for Squid. @@ -9,18 +9,36 @@ It reads from the standard input the domain username and a list of groups and tries to match it against the groups membership of the specified username. +Two running mode are available: + +- Local mode: +membership is checked against machine's local groups, cannot be used when +running on a Domain Controller. + +- Active Directory Global mode: +membership is checked against the whole Active Directory Forest of the +machine where Squid is running. + The minimal Windows version needed to run mswin_check_ad_group is a Windows 2000 SP4 member of an Active Directory Domain. +When running in Active Directory Global mode, all types of Active Directory +security groups are supported: +- Domain Global +- Domain Local from user's domain +- Universal +and Active Directory group nesting is fully supported. + + ============== Program Syntax ============== -mswin_check_lm_group [-D domain][-G][-c][-d][-h] +mswin_check_ad_group [-D domain][-G][-c][-d][-h] -D domain specify the default user's domain --G start helper in Domain Global Group mode --c use case insensitive compare +-G start helper in Active Directory Global mode +-c use case insensitive compare (local mode only) -d enable debugging -h this message @@ -29,10 +47,24 @@ mswin_check_lm_group [-D domain][-G][-c][-d][-h] squid.conf usage ================ +When running in Active Directory Global mode, the AD Group can be specified using the +following syntax: + +1. Plain NT4 Group Name +2. Full NT4 Group Name +3. Active Directory Canonical name + +As Example: +1. Proxy-Users +2. MYDOMAIN\Proxy-Users +3. mydomain.local/Groups/Proxy-Users + +When using Plain NT4 Group Name, the Group is searched in the user's domain. + external_acl_type AD_global_group %LOGIN c:/squid/libexec/mswin_check_ad_group.exe -G external_acl_type NT_local_group %LOGIN c:/squid/libexec/mswin_check_ad_group.exe -acl GProxyUsers external AD_global_group GProxyUsers +acl GProxyUsers external AD_global_group MYDOMAIN\GProxyUsers acl LProxyUsers external NT_local_group LProxyUsers acl password proxy_auth REQUIRED @@ -40,7 +72,7 @@ http_access allow password GProxyUsers http_access allow password LProxyUsers http_access deny all -In the previous example all validated AD users member of GProxyUsers Global +In the previous example all validated AD users member of MYDOMAIN\GProxyUsers domain group or member of LProxyUsers machine local group are allowed to use the cache. @@ -55,8 +87,12 @@ and the DomainUsers files will contain only the following line: "Domain Users" NOTES: -- The standard group name comparison is case sensitive, so group name - must be specified with same case as in the Active Directory Domain. +- When running in Active Directory Global mode, for better performance, + all Domain Controllers of the Active Directory forest should be configured + as Global Catalog. +- When running in local mode, the standard group name comparison is case + sensitive, so group name must be specified with same case as in the + local SAM database. It's possible to enable case insensitive group name comparison (-c), but on some not-english locales, the results can be unexpected. - Native WIN32 NTLM and Basic Helpers must be used without the