// File: secmgr.cxx // Contents: This file implements the base IInternetSecurityManager interface // Classes: CSecurityManager // Functions: // History: #include "zonepch.h" // PCH HEADER FILE, DON'T INCLUDE ANYTHING ABOVE PerfDbgTag(tagCSecurityManager, "Urlmon", "Log Security Mgr", DEB_SESSION) PerfDbgTag(tagZONEMAP_COMPONENTS, "Urlmon", "Log Security URL parser", DEB_SESSION) #define PRIVATE static #define ZERO TEXT('0') #define NINE TEXT('9') #define DOT TEXT('.') #define SLASH TEXT('/') #define BACKSLASH TEXT('\\') #define COLON TEXT(':') #define WILDCARD TEXT('*') #define SPACE TEXT(' ') #define HYPHEN TEXT('-') #define BAR TEXT('|') #define AT TEXT('@') #define PERCENT TEXT('%') #define MAX_IPRANGE 32 BOOL CSecurityManager::s_bIPInit = FALSE; BYTE* CSecurityManager::s_pRanges = NULL; DWORD CSecurityManager::s_cNumRanges = 0; DWORD CSecurityManager::s_cbRangeItem = 0; DWORD CSecurityManager::s_dwNextRangeIndex = 0; PRIVATE TCHAR chWildCard = WILDCARD; CSecurityManager::CSecMgrCache CSecurityManager::s_smcache; BOOL CSecurityManager::s_bcsectInit = FALSE; CRITICAL_SECTION CSecurityManager::s_csectIP; HANDLE CSecurityManager::CSecMgrCache::s_hMutexCounter; CLSID* CSecurityManager::s_clsidAllowedList = NULL; CRITICAL_SECTION CSecurityManager::s_csectAList; DWORD CSecurityManager::s_dwNumAllowedControls; // HACK: See assert below. We have to parse '*' as a valid scheme for wildcarding purposes. // The big number is to avoid collisions with the pre-defined URL_SCHEME_* numbers that start // at 0 and go up sequentially. #define URL_SCHEME_WILDCARD (0x0000FFFF) typedef DWORD(APIENTRY* WNETGETCONNECTION) (LPSTR, LPSTR, LPDWORD); // Simple class to force freeing of memory pointer. class CFreeStrPtr { public: CFreeStrPtr(LPWSTR pwsz) { m_pwsz = pwsz; } ~CFreeStrPtr() { delete[] m_pwsz; } private: LPWSTR m_pwsz; }; #ifdef UNICODE #define IsSpace IsCharSpaceW #else #define IsSpace isspace #endif // Scans a string for number from 0 to 255 inclusive. PRIVATE BOOL ScanByte(LPCTSTR& psz, BYTE* bOut) { DWORD dw; // first char if (*psz < ZERO || *psz > NINE) return FALSE; dw = *psz++ - ZERO; // second char if (*psz < ZERO || *psz > NINE) goto done; dw = 10 * dw + *psz++ - ZERO; // third char if (*psz < ZERO || *psz > NINE) goto done; dw = 10 * dw + *psz++ - ZERO; if (dw > 255) return FALSE; done: *bOut = (BYTE)dw; return TRUE; } // Scans a string for a range, wrapping ScanByte PRIVATE BOOL ScanRange(LPCTSTR& psz, BYTE* pbLow, BYTE* pbHigh) { if (*psz == WILDCARD) { *pbLow = 0; *pbHigh = 255; psz++; // move past * return TRUE; } if (!ScanByte(psz, pbLow)) return FALSE; while (*psz == SPACE) psz++; // trim whitespace if (*psz != HYPHEN) { *pbHigh = *pbLow; return TRUE; } else { psz++; // move past - while (*psz == SPACE) psz++; // trim whitespace return ScanByte(psz, pbHigh); } } PRIVATE BOOL ReadIPRule(LPCTSTR psz, BYTE* pbLow, BYTE* pbHigh) { // Note: ScanRange first param passed by reference. return (ScanRange(psz, pbLow++, pbHigh++) && *psz++ == DOT && ScanRange(psz, pbLow++, pbHigh++) && *psz++ == DOT && ScanRange(psz, pbLow++, pbHigh++) && *psz++ == DOT && ScanRange(psz, pbLow++, pbHigh++) ); } // This function is copied here from the network stack code because we don't want to // link urlmon with winsock. Urlmon is pulled in by the shell even in cases where there // is no network connection. /* * Internet address interpretation routine. * All the network library routines call this * routine to interpret entries in the data bases * which are expected to be an address. * The value returned is in network order. */ PRIVATE ULONG inet_addr( IN const TCHAR* cp ) /*++ Routine Description: This function interprets the character string specified by the cp parameter. This string represents a numeric Internet address expressed in the Internet standard ".'' notation. The value returned is a number suitable for use as an Internet address. All Internet addresses are returned in network order (bytes ordered from left to right). Internet Addresses Values specified using the "." notation take one of the following forms: a.b.c.d a.b.c a.b a When four parts are specified, each is interpreted as a byte of data and assigned, from left to right, to the four bytes of an Internet address. Note that when an Internet address is viewed as a 32-bit integer quantity on the Intel architecture, the bytes referred to above appear as "d.c.b.a''. That is, the bytes on an Intel processor are ordered from right to left. Note: The following notations are only used by Berkeley, and nowhere else on the Internet. In the interests of compatibility with their software, they are supported as specified. When a three part address is specified, the last part is interpreted as a 16-bit quantity and placed in the right most two bytes of the network address. This makes the three part address format convenient for specifying Class B network addresses as "128.net.host''. When a two part address is specified, the last part is interpreted as a 24-bit quantity and placed in the right most three bytes of the network address. This makes the two part address format convenient for specifying Class A network addresses as "net.host''. When only one part is given, the value is stored directly in the network address without any byte rearrangement. Arguments: cp - A character string representing a number expressed in the Internet standard "." notation. Return Value: If no error occurs, inet_addr() returns an in_addr structure containing a suitable binary representation of the Internet address given. Otherwise, it returns the value INADDR_NONE. */ { register unsigned long val, base, n; register TCHAR c; unsigned long parts[4], * pp = parts; const unsigned long INADDR_NONE = -1; again: /* * Collect number up to ``.''. * Values are specified as for C: * 0x=hex, 0=octal, other=decimal. */ val = 0; base = 10; if (*cp == '0') { base = 8, cp++; if (*cp == 'x' || *cp == 'X') base = 16, cp++; } while (c = *cp) { // If it is a decimal digit.. if (c <= NINE && c >= ZERO) { val = (val * base) + (c - '0'); cp++; continue; } // If we are base 16 and it is a hex digit... if (base == 16 && ((c >= TEXT('a') && c <= TEXT('f')) || (c >= TEXT('A') && c <= TEXT('F')) ) ) { val = (val << 4) + (c + 10 - (islower(c) ? TEXT('a') : TEXT('A'))); cp++; continue; } break; } if (*cp == '.') { /* * Internet format: * a.b.c.d * a.b.c (with c treated as 16-bits) * a.b (with b treated as 24 bits) */ /* GSS - next line was corrected on 8/5/89, was 'parts + 4' */ if (pp >= parts + 3) { return ((unsigned long)-1); } *pp++ = val, cp++; goto again; } /* * Check for trailing characters. */ if (*cp && !IsSpace(*cp)) { return (INADDR_NONE); } *pp++ = val; /* * Concoct the address according to * the number of parts specified. */ n = (unsigned long)(pp - parts); switch ((int)n) { case 1: /* a -- 32 bits */ val = parts[0]; break; case 2: /* a.b -- 8.24 bits */ if ((parts[0] > 0xff) || (parts[1] > 0xffffff)) { return(INADDR_NONE); } val = (parts[0] << 24) | (parts[1] & 0xffffff); break; case 3: /* a.b.c -- 8.8.16 bits */ if ((parts[0] > 0xff) || (parts[1] > 0xff) || (parts[2] > 0xffff)) { return(INADDR_NONE); } val = (parts[0] << 24) | ((parts[1] & 0xff) << 16) | (parts[2] & 0xffff); break; case 4: /* a.b.c.d -- 8.8.8.8 bits */ if ((parts[0] > 0xff) || (parts[1] > 0xff) || (parts[2] > 0xff) || (parts[3] > 0xff)) { return(INADDR_NONE); } val = (parts[0] << 24) | ((parts[1] & 0xff) << 16) | ((parts[2] & 0xff) << 8) | (parts[3] & 0xff); break; default: return (INADDR_NONE); } #if defined(UNIX) && defined(BIG_ENDIAN) // IEUNIX: Dont swap on BIG_ENDIAN Unix m/c. return (val); #else val = (val & 0xff000000) >> 24 | (val & 0x00ff0000) >> 8 | (val & 0x0000ff00) << 8 | (val & 0x000000ff) << 24; return (val); #endif } // Checks if site is in form of IP address. PRIVATE BOOL ReadAddress(LPCTSTR pwszSite, BYTE* pb) { ULONG ipaddr = inet_addr(pwszSite); if (ipaddr != -1) { #ifndef UNIX * (ULONG*)pb = ipaddr; #else memcpy(pb, &ipaddr, sizeof(ULONG)); #endif /* UNIX */ return TRUE; } return FALSE; } const TCHAR cszFileProt[] = TEXT("file"); struct ZONEMAP_COMPONENTS { // pointers into buffer passed to Crack LPCTSTR pszProtocol, pszSite, pszDomain; DWORD cchProtocol, cchSite, cchDomain; DWORD nScheme; // One of URL_SCHEME_* BOOL fAddr : 1; // whether name is in form of IP address BOOL fIPRange : 1; // whether name is in form of an IP Range. BYTE bAddr[4]; // if IP address, components of IP address RANGE_ITEM rangeItem; // If IP Range, components of IP Range. BOOL fDrive; // URL corresponds to a drive letter that couldn't be mapped to a network share name. DWORD dwDriveType; // if so, drive type TCHAR szProtBuf[INTERNET_MAX_SCHEME_LENGTH]; TCHAR szSiteBuf[MAX_PATH]; // used for remote drives TCHAR szIPAddr[16]; // room for 255.255.255.255 + NULL HRESULT Crack(LPCTSTR pwszUrl, DWORD dwFlags, BOOL bIPRange = FALSE); BOOL SetUNC(LPSTR pszUNC); }; BOOL ZONEMAP_COMPONENTS::SetUNC(LPSTR pszUNC) { PerfDbgLog(tagZONEMAP_COMPONENTS, this, "+ZONEMAP_COMPONENTS::SetUNC"); // Verify and strip leading backslashes. if (pszUNC[0] != '\\' || pszUNC[1] != '\\') return FALSE; pszUNC += 2; // Strip the share name from the host. LPSTR pszSlash = StrChrA(pszUNC, '\\'); if (!pszSlash) return FALSE; *pszSlash = 0; DWORD cchUNC = (DWORD)(pszSlash - pszUNC); // Convert back to unicode. cchSite = MultiByteToWideChar(CP_ACP, 0, pszUNC, cchUNC, szSiteBuf, MAX_PATH); szSiteBuf[cchSite] = 0; pszSite = szSiteBuf; PerfDbgLog(tagZONEMAP_COMPONENTS, this, "-ZONEMAP_COMPONENTS::SetUNC"); return TRUE; } // Helper functions to help with URL cracking. inline BOOL IsDosPath(LPCTSTR p) { #ifndef unix return (*p == BACKSLASH || /* it starts with "x:" where x is from the English alphabet */ ((*p) && ((*p >= TEXT('a') && *p <= TEXT('z')) || (*p >= TEXT('A') && *p <= TEXT('Z'))) && p[1] == COLON)); #else return (*p == SLASH); #endif /* unix */ } inline BOOL IsDrive(LPCTSTR p) { #ifndef unix return (*p && (p[1] == COLON || p[1] == BAR)); #else return (*p == SLASH); #endif /* unix */ } inline BOOL IsWildcardScheme(LPCTSTR p, BOOL& bImplicit) { BOOL bRet = FALSE; if (p && *p) { if (*p == WILDCARD && p[1] == COLON) { bRet = TRUE; bImplicit = FALSE; } else if (StrChr(p, COLON) == NULL) { // If there is no Colon in the string the user didn't specify a scheme // and we will assume it is a wildcard. // i.e *:*.microsoft.com is treated the same as *.microsoft.com bRet = TRUE; bImplicit = TRUE; } } return bRet; } // A scheme is opaque if we cannot interpret the URL after the scheme. inline BOOL IsOpaqueScheme(DWORD dwScheme) { return (dwScheme != URL_SCHEME_FILE && dwScheme != URL_SCHEME_WILDCARD && !IsHierarchicalScheme(dwScheme)); } // Global Init functions. BOOL CSecurityManager::GlobalInit() { InitializeCriticalSection(&s_csectIP); InitializeCriticalSection(&s_csectAList); CSecurityManager::s_bcsectInit = TRUE; return TRUE; } BOOL CSecurityManager::GlobalCleanup() { delete[] s_pRanges; s_pRanges = NULL; if (s_clsidAllowedList) { delete[] s_clsidAllowedList; s_clsidAllowedList = NULL; } if (s_bcsectInit) { DeleteCriticalSection(&s_csectIP); DeleteCriticalSection(&s_csectAList); } return TRUE; } VOID CSecurityManager::IncrementGlobalCounter() { CSecurityManager::CSecMgrCache::IncrementGlobalCounter(); } // Helper functions to deal with caching MapUrlToZone results. HRESULT ZONEMAP_COMPONENTS::Crack(LPCTSTR pszScan, DWORD dwFlags, BOOL bIPRange /* = FALSE*/) { PerfDbgLog(tagZONEMAP_COMPONENTS, this, "+ZONEMAP_COMPONENTS::Crack"); fDrive = FALSE; fAddr = FALSE; fIPRange = FALSE; nScheme = URL_SCHEME_INVALID; if (IsDosPath(pszScan)) dwFlags |= PUAF_ISFILE; if (dwFlags & PUAF_ISFILE) { pszProtocol = cszFileProt; cchProtocol = CSTRLENW(cszFileProt); nScheme = URL_SCHEME_FILE; } else { PARSEDURL pu; pu.cbSize = sizeof(pu); BOOL bImplicit = FALSE; if ((dwFlags & PUAF_ACCEPT_WILDCARD_SCHEME) && IsWildcardScheme(pszScan, bImplicit) ) { nScheme = URL_SCHEME_WILDCARD; pszProtocol = &chWildCard; cchProtocol = 1; // Skip over the *: if the user entered this explicity. if (!bImplicit) pszScan += 2; } else { HRESULT hr = ParseURL(pszScan, &pu); if (SUCCEEDED(hr)) { nScheme = pu.nScheme; pszProtocol = pu.pszProtocol; cchProtocol = pu.cchProtocol; pszScan = pu.pszSuffix; } else { return hr; } } // Copy protocol to null terminate it. if (cchProtocol >= INTERNET_MAX_SCHEME_LENGTH) return E_INVALIDARG; else { memcpy(szProtBuf, pszProtocol, sizeof(TCHAR) * cchProtocol); szProtBuf[cchProtocol] = 0; pszProtocol = szProtBuf; } } // Opaque URLs - We cannot interpret anything besides the scheme. // Just Treat the rest of the string as the Site. if (IsOpaqueScheme(nScheme)) { pszSite = pszScan; cchSite = lstrlen(pszSite); pszDomain = NULL; cchDomain = 0; } else { #ifndef unix // Scan past leading '/' and '\' before site. while (*pszScan == SLASH || *pszScan == BACKSLASH) pszScan++; #endif /* unix */ pszSite = pszScan; // Is this a drive letter. If so we need to figure out whether it is local or remote. if (nScheme == URL_SCHEME_FILE && pszSite[0] != WILDCARD && IsDrive(pszSite)) { fDrive = TRUE; char szDriveRoot[4]; szDriveRoot[0] = (BYTE)pszSite[0]; #ifndef unix szDriveRoot[1] = ':'; szDriveRoot[2] = '\\'; szDriveRoot[3] = 0; #else szDriveRoot[1] = 0; #endif /* unix */ dwDriveType = GetDriveTypeFromCacheA(szDriveRoot); if (dwDriveType == DRIVE_REMOTE) { // Strip the trailing backslash. szDriveRoot[2] = 0; char szUNC[MAX_PATH]; DWORD cchUNC; cchUNC = MAX_PATH; if (NO_ERROR == WNetGetConnectionA(szDriveRoot, szUNC, &cchUNC)) { fDrive = FALSE; SetUNC(szUNC); } } } // SetUNC might have come back with a new site. pszScan = pszSite; if (fDrive) { // Just start using the drive as is. cchSite = lstrlen(pszSite); cchDomain = 0; pszDomain = NULL; } else { // Scan for characters which delimit site. while (1) { switch (*pszScan) { case 0: case SLASH: case BACKSLASH: break; case TEXT('@'): // This happens with custom protocols. Remove assert. // TransAssert(FALSE); default: pszScan++; continue; } break; } cchSite = (DWORD)(pszScan - pszSite); pszDomain = NULL; cchDomain = 0; // Check for IP ranges if we are asked to first. if (bIPRange) { fIPRange = ReadIPRule(pszSite, rangeItem.bLow, rangeItem.bHigh); if (fIPRange) return S_OK; } // Check for names that are form of an IP address. fAddr = ReadAddress(pszSite, bAddr); if (fAddr) { cchSite = wnsprintf(szIPAddr, ARRAYSIZE(szIPAddr), TEXT("%d.%d.%d.%d"), bAddr[0], bAddr[1], bAddr[2], bAddr[3]); pszSite = szIPAddr; return S_OK; } // Scan backward for second '.' indicating domain, // ignoring two-char int'l domains like "co.uk" etc. DWORD cDot = 0; LPCTSTR pszDot = NULL; while (pszScan > pszSite) { if ((*pszScan == DOT) && (pszScan < pszSite + cchSite - 2)) { ++cDot; if (cDot == 1) { pszDot = pszScan; } else if (cDot == 2 && pszDot // Non-null only the 1st time && pszDot == pszSite + cchSite - 3 // Only check .?? ending && pszScan + 3 >= pszDot) // Check distance between the dots { // The distance between the 2 dots is less than 3 chars (.?? or smaller), // so don't count this as a dot and don't check again. --cDot; pszDot = NULL; } else { pszDomain = pszScan + 1; cchDomain = (DWORD)(pszSite + cchSite - pszDomain); cchSite = (DWORD)(pszScan - pszSite); break; } } pszScan--; } } } PerfDbgLog(tagZONEMAP_COMPONENTS, this, "-ZONEMAP_COMPONENTS::Crack"); return S_OK; } // Function to get a new IInternetSecurityManager from outside. We might replace this // with a standard class factory eventually. STDAPI InternetCreateSecurityManager ( IUnknown* pUnkOuter, REFIID riid, void** ppvObj, DWORD dwReserved ) { PerfDbgLog(tagCSecurityManager, NULL, "+InternetCreateSecurityManager"); HRESULT hr = S_OK; *ppvObj = NULL; if (!IsZonesInitialized()) return E_UNEXPECTED; if (dwReserved != 0 || !ppvObj || (pUnkOuter && riid != IID_IUnknown)) { // If the object has to be aggregated the caller can only ask // for an IUnknown back. hr = E_INVALIDARG; } else { CSecurityManager* pSecMgr = new CSecurityManager(pUnkOuter, (IUnknown**)ppvObj); if (pSecMgr) { if (riid == IID_IUnknown || riid == IID_IInternetSecurityManager) { // The correct pointer is in ppvObj *ppvObj = (IInternetSecurityManager*)pSecMgr; } else { hr = E_NOINTERFACE; } } else { hr = E_OUTOFMEMORY; } } PerfDbgLog1(tagCSecurityManager, NULL, "-InternetCreateSecurityManager (hr:%lx)", hr); return hr; } // Class Initialization-Destruction. CSecurityManager::CSecurityManager(IUnknown* pUnkOuter, IUnknown** ppUnkInner) { PerfDbgLog(tagCSecurityManager, this, "+CSecurityManager::CSecurityManager"); DllAddRef(); m_pSite = NULL; m_pDelegateSecMgr = NULL; m_pZoneManager = NULL; if (!pUnkOuter) { pUnkOuter = &m_Unknown; } else { TransAssert(ppUnkInner); if (ppUnkInner) { *ppUnkInner = &m_Unknown; m_ref = 0; } } m_pUnkOuter = pUnkOuter; if (ERROR_SUCCESS != m_regZoneMap.Open(NULL, SZZONEMAP, KEY_READ)) goto done; EnterCriticalSection(&s_csectIP); if (!s_bIPInit) { ReadAllIPRules(); s_bIPInit = TRUE; } LeaveCriticalSection(&s_csectIP); done: PerfDbgLog(tagCSecurityManager, this, "-CSecurityManager::CSecurityManager"); return; } CSecurityManager::~CSecurityManager() { // Due to a circular dependency between wininet and urlmon, this function // could get called after the dlls global uninit has happened. PerfDbgLog depends on // some global Mutext objects and fails because of this reason. // PerfDbgLog(tagCSecurityManager, this, "+~CSecurityManager::CSecurityManager"); if (m_pZoneManager != NULL) m_pZoneManager->Release(); if (m_pSite != NULL) m_pSite->Release(); if (m_pDelegateSecMgr != NULL) m_pDelegateSecMgr->Release(); DllRelease(); // PerfDbgLog(tagCSecurityManager, this, "-~CSecurityManager::CSecurityManager"); } BOOL CSecurityManager::EnsureZoneManager() { if (m_pZoneManager == NULL) { if (SUCCEEDED(InternetCreateZoneManager(NULL, IID_IInternetZoneManager, (void**)&m_pZoneManager, NULL))) { TransAssert(m_pZoneManager != NULL); } } return (m_pZoneManager != NULL); } STDMETHODIMP CSecurityManager::CPrivUnknown::QueryInterface(REFIID riid, void** ppvObj) { HRESULT hr = S_OK; *ppvObj = NULL; CSecurityManager* pSecurityManager = GETPPARENT(this, CSecurityManager, m_Unknown); if (riid == IID_IUnknown || riid == IID_IInternetSecurityManager) { *ppvObj = (IInternetSecurityManager*)pSecurityManager; pSecurityManager->AddRef(); } else { hr = E_NOINTERFACE; } return hr; } STDMETHODIMP_(ULONG) CSecurityManager::CPrivUnknown::AddRef() { LONG lRet = ++m_ref; return lRet; } STDMETHODIMP_(ULONG) CSecurityManager::CPrivUnknown::Release() { CSecurityManager* pSecurityManager = GETPPARENT(this, CSecurityManager, m_Unknown); LONG lRet = --m_ref; if (lRet == 0) { delete pSecurityManager; } return lRet; } // IUnknown methods STDMETHODIMP CSecurityManager::QueryInterface(REFIID riid, void** ppvObj) { PerfDbgLog(tagCSecurityManager, this, "+CSecurityManager::QueryInterface"); HRESULT hr = E_NOINTERFACE; *ppvObj = NULL; if (riid == IID_IUnknown || riid == IID_IInternetSecurityManager) { *ppvObj = (IInternetSecurityManager*)this; } if (*ppvObj != NULL) { ((IUnknown*)*ppvObj)->AddRef(); hr = S_OK; } PerfDbgLog1(tagCSecurityManager, this, "-CSecurityManager::QueryInterface (hr:%lx)", hr); return hr; } STDMETHODIMP_(ULONG) CSecurityManager::AddRef() { LONG lRet = m_pUnkOuter->AddRef(); return lRet; } STDMETHODIMP_(ULONG) CSecurityManager::Release() { LONG lRet = m_pUnkOuter->Release(); // Controlling Unknown will delete the object if reqd. return lRet; } // IInternetSecurityManager methods STDMETHODIMP CSecurityManager::MapUrlToZone ( LPCWSTR pwszUrl, DWORD* pdwZone, DWORD dwFlags ) { PerfDbgLog(tagCSecurityManager, this, "+CSecurityManager::MapUrlToZone"); HRESULT hr = INET_E_DEFAULT_ACTION; if (m_pDelegateSecMgr) { hr = m_pDelegateSecMgr->MapUrlToZone(pwszUrl, pdwZone, dwFlags); } // Check the cache to see if we already know the answer. /* BUGBUG - why are we stomping the delegate's result if we have a cache value? if ((NULL != m_pszPrevUrl) && (0 == StrCmpI(m_pszPrevUrl, pwszUrl)) && IsCounterEqual()) { hr = S_OK; *pdwZone = m_dwPrevZone; } */ if (hr == INET_E_DEFAULT_ACTION) { LPWSTR pwszSecUrl = NULL; if (pdwZone == NULL || pwszUrl == NULL) { hr = E_INVALIDARG; } if (!s_smcache.Lookup(pwszUrl, pdwZone)) { hr = CoInternetGetSecurityUrl(pwszUrl, &pwszSecUrl, PSU_DEFAULT, 0); if (SUCCEEDED(hr)) { TransAssert(pwszSecUrl); CFreeStrPtr freeStr(pwszSecUrl); // Reduce the URL here. Need to do this up to two times. for (int i = 1; i <= 2; i++) { if (StrChr(pwszSecUrl, PERCENT)) { UrlUnescapeW(pwszSecUrl, NULL, 0, URL_UNESCAPE_INPLACE); } else break; } ZONEMAP_COMPONENTS zc; if (SUCCEEDED(hr = zc.Crack(pwszSecUrl, dwFlags))) { BOOL fMarked; hr = MapUrlToZone(&zc, pdwZone, dwFlags, &fMarked); if (hr == S_OK && !(dwFlags & MUTZ_NOCACHE)) { s_smcache.Add(pwszUrl, *pdwZone, fMarked); } } } } else { hr = S_OK; } } PerfDbgLog1(tagCSecurityManager, this, "-CSecurityManager::MapUrlToZone (hr:%lx)", hr); return hr; } VOID CSecurityManager::PickZCString(ZONEMAP_COMPONENTS* pzc, LPCWSTR* ppwsz, DWORD* pcch, LPCWSTR pwszDocDomain) { if (pzc->cchDomain) { TransAssert(!pzc->fDrive); // We should use the whole site even if we were able to get the // primary domain. i.e. security id for http://www.microsoft.com // should be http:www.microsoft.com and NOT http:microsoft.com // We will use the fact that the domain and site are actually // pointing into one contiguous string to get back at the // whole string. TransAssert((pzc->cchSite + 1) == (DWORD)(pzc->pszDomain - pzc->pszSite)); TransAssert(pzc->pszSite[pzc->cchSite] == DOT); *ppwsz = pzc->pszSite; *pcch = pzc->cchSite + pzc->cchDomain + 1; } else if (pzc->fDrive && pzc->dwDriveType != DRIVE_REMOTE) { *ppwsz = TEXT(""); *pcch = 0; } else if (pzc->nScheme == URL_SCHEME_FILE) { // For URL's of the nature \\server\sharename we want to include both the server and sharename in // the security ID. This permits me from looking at \\server\private by putting up a page on // \\server\public At thuis point pzc->pszSite should point to the string "server\private\foo.htm" LPCTSTR lpszCurr = pzc->pszSite; BOOL bFoundFirstSlash = FALSE; for (; *lpszCurr != NULL; lpszCurr++) { if (*lpszCurr == SLASH || *lpszCurr == BACKSLASH) { if (bFoundFirstSlash) { // This is the second slash we are done. break; } else { bFoundFirstSlash = TRUE; } } } *ppwsz = pzc->pszSite; if (lpszCurr != NULL) { *pcch = (DWORD)(lpszCurr - pzc->pszSite); } else { TransAssert(FALSE); *pcch = pzc->cchSite; } } else { TransAssert(!pzc->pszDomain && !pzc->cchDomain); *ppwsz = pzc->pszSite; *pcch = pzc->cchSite; } // If the domain string passed is a suffix of the site string we will // use that string instead. if (*pcch && pwszDocDomain != 0) { DWORD cchDocDomain = lstrlenW(pwszDocDomain); if (*pcch > cchDocDomain && (0 == StrCmpNICW(pwszDocDomain, &((*ppwsz)[*pcch - cchDocDomain]), cchDocDomain))) { *ppwsz = pwszDocDomain; *pcch = cchDocDomain; } } } STDMETHODIMP CSecurityManager::GetSecurityId ( /* [in] */ LPCWSTR pwszUrl, /* [size_is][out] */ BYTE* pbSecurityId, /* [out][in] */ DWORD* pcbSecurityId, /* [in] */ DWORD_PTR dwReserved ) { PerfDbgLog(tagCSecurityManager, this, "Called CSecurityManager::GetSecurityId"); HRESULT hr = INET_E_DEFAULT_ACTION; // Check args ... if (pwszUrl == NULL || !pwszUrl[0] || !pcbSecurityId) { return E_INVALIDARG; } if (m_pDelegateSecMgr) { hr = m_pDelegateSecMgr->GetSecurityId(pwszUrl, pbSecurityId, pcbSecurityId, dwReserved); } if (hr == INET_E_DEFAULT_ACTION) { BOOL fFoundInCache; BOOL fMarked = FALSE; DWORD dwZone; DWORD cbSecurityID = *pcbSecurityId; fFoundInCache = s_smcache.Lookup(pwszUrl, &dwZone, &fMarked, pbSecurityId, &cbSecurityID, (LPCWSTR)dwReserved); // if it wasn't in the cache, or the url and zone were there, but not the security ID, // then we still need to do some work. if (!fFoundInCache || cbSecurityID == 0) { LPWSTR pwszSecUrl = NULL; DWORD dwFlags = 0; hr = CoInternetGetSecurityUrl(pwszUrl, &pwszSecUrl, PSU_DEFAULT, 0); if (SUCCEEDED(hr)) { TransAssert(pwszSecUrl != NULL); CFreeStrPtr freeStr(pwszSecUrl); // Crack the URL. ZONEMAP_COMPONENTS zc; LPWSTR pwszMarkURL = NULL; LPWSTR pwszSecUrl2 = NULL; if (S_OK != zc.Crack(pwszSecUrl, dwFlags)) return E_INVALIDARG; // Select middle portion of Id. LPCWSTR psz2; DWORD cch2; PickZCString(&zc, &psz2, &cch2, (LPCWSTR)dwReserved); // Identify the zone and determine if the ID will bear the // Mark of the Web. // if the url was found in the cache, the an earlier MapUrlToZone // put it there with its zone and Marked flag, if (!fFoundInCache || fMarked) { hr = MapUrlToZone(&zc, &dwZone, 0, &fMarked, &pwszMarkURL); TransAssert(hr == S_OK); // If the Mark of the Web is present, then take the Mark URL // and substitute it for the original one in the zc, this will // allow us to create a security ID that embodies the original // domain, which in turn allows us to recreate the cross-domain // frame security. The '*' we add to the end will prevent a // potentially compromised page on the user's disk from accessing // frames of the live, original site if the two should wind up // in the same frameset. if (fMarked) { TransAssert(pwszMarkURL != NULL); hr = CoInternetGetSecurityUrl(pwszMarkURL, &pwszSecUrl2, PSU_DEFAULT, 0); if (SUCCEEDED(hr)) { TransAssert(pwszSecUrl2 != NULL); if (SUCCEEDED(zc.Crack(pwszSecUrl2, dwFlags))) PickZCString(&zc, &psz2, &cch2, (LPCWSTR)dwReserved); else { if (pwszSecUrl2) delete[] pwszSecUrl2; if (pwszMarkURL) LocalFree(pwszMarkURL); return E_INVALIDARG; } } } } // Calculate required size of buffer. DWORD cbTail = sizeof(DWORD) + ((fMarked) ? sizeof(CHAR) : 0); DWORD cbSite = 0; if (cch2 != 0) { cbSite = WideCharToMultiByte(CP_ACP, 0, psz2, cch2, NULL, 0, NULL, NULL); if (cbSite == 0) return(HRESULT_FROM_WIN32(GetLastError())); } DWORD cbRequired = zc.cchProtocol + 1 + cbSite + cbTail; if (*pcbSecurityId < cbRequired) { *pcbSecurityId = cbRequired; return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); } // Emit the protocol in ANSI. DWORD cbOut; cbOut = WideCharToMultiByte(CP_ACP, 0, zc.pszProtocol, zc.cchProtocol, (LPSTR)pbSecurityId, *pcbSecurityId, NULL, NULL); if (cbOut != zc.cchProtocol) return E_INVALIDARG; // non-ascii chars illegal in URL scheme pbSecurityId[cbOut++] = ':'; // Emit the site/domain in ANSI. if (cch2 != 0) { cbSite = WideCharToMultiByte(CP_ACP, 0, psz2, cch2, (LPSTR)pbSecurityId + cbOut, *pcbSecurityId - cbOut, NULL, NULL); } // HACK: Need to figure out a better way to fix this. // File: url's can come in with slashes and backslashes as seperators. // To prevent things from breaking we replace any slashes in the // pbSecurityId with a backslash. if (zc.nScheme == URL_SCHEME_FILE && cch2 != 0) { LPSTR lpszStart = (LPSTR)pbSecurityId + cbOut; LPSTR lpsz = lpszStart; while (lpsz < lpszStart + cbSite) { if (*lpsz == '/') *lpsz = '\\'; lpsz = CharNextA(lpsz); } } cbOut += cbSite; // Downcase the buffer. pbSecurityId[cbOut] = 0; CharLowerA((LPSTR)pbSecurityId); // Add the zone. memcpy(pbSecurityId + cbOut, &dwZone, sizeof(DWORD)); if (fMarked) { CHAR chMark = WILDCARD; memcpy(pbSecurityId + cbOut + sizeof(DWORD), &chMark, sizeof(CHAR)); } // Report the output data size. *pcbSecurityId = cbRequired; // Now that we have all the pieces, (re)add it to the cache s_smcache.Add(pwszUrl, dwZone, fMarked, pbSecurityId, cbRequired, (LPCWSTR)dwReserved); if (pwszSecUrl2) delete[] pwszSecUrl2; if (pwszMarkURL) LocalFree(pwszMarkURL); } // got security URL } // got security URL else { // Got it from the cache. *pcbSecurityId = cbSecurityID; hr = S_OK; } } // delegate missing or wants us to do the work return hr; } // Helper functions to do generic UI from within ProcessUrlAction. struct ActionStrIDMap { DWORD dwAction; DWORD dwStrID; }; ActionStrIDMap actionAlertIDMap[] = { { URLACTION_DOWNLOAD_SIGNED_ACTIVEX, IDS_ACTION_DL_SIGNED_ACTIVEX }, { URLACTION_DOWNLOAD_UNSIGNED_ACTIVEX, IDS_ACTION_DL_ACTIVEX }, { URLACTION_ACTIVEX_RUN, IDS_ACTION_AX_RUN }, { URLACTION_ACTIVEX_OVERRIDE_OBJECT_SAFETY, IDS_ACTION_AX_OVERRIDE_SAFETY }, { URLACTION_ACTIVEX_OVERRIDE_DATA_SAFETY, IDS_ACTION_AX_OVERRIDE_DATA_SAFETY }, { URLACTION_ACTIVEX_OVERRIDE_SCRIPT_SAFETY, IDS_ACTION_AX_OVERRIDE_SCRIPT_SAFETY}, { URLACTION_ACTIVEX_CONFIRM_NOOBJECTSAFETY, IDS_ACTION_AX_CONFIRM_NOSAFETY }, { URLACTION_SCRIPT_RUN, IDS_ACTION_SCRIPT_RUN }, { URLACTION_SCRIPT_OVERRIDE_SAFETY, IDS_ACTION_SCRIPT_OVERRIDE_SAFETY }, { URLACTION_SCRIPT_JAVA_USE, IDS_ACTION_SCRIPT_JAVA }, { URLACTION_SCRIPT_SAFE_ACTIVEX, IDS_ACTION_SCRIPT_SAFE_ACTIVEX }, { URLACTION_CROSS_DOMAIN_DATA, IDS_ACTION_CROSS_DOMAIN_DATA }, { URLACTION_SCRIPT_PASTE, IDS_ACTION_SCRIPT_PASTE }, { URLACTION_HTML_SUBMIT_FORMS, IDS_ACTION_HTML_FORMS }, { URLACTION_HTML_SUBMIT_FORMS_FROM, IDS_ACTION_HTML_FORMS }, { URLACTION_HTML_SUBMIT_FORMS_TO, IDS_ACTION_HTML_FORMS }, { URLACTION_HTML_FONT_DOWNLOAD, IDS_ACTION_HTML_FONT_DL }, { URLACTION_HTML_JAVA_RUN, IDS_ACTION_HTML_JAVA }, { URLACTION_HTML_USERDATA_SAVE, IDS_ACTION_HTML_USERDATA }, { URLACTION_HTML_SUBFRAME_NAVIGATE, IDS_ACTION_HTML_SUBFRAME_NAVIGATE }, { URLACTION_SHELL_INSTALL_DTITEMS, IDS_ACTION_SHELL_INSTALL_DTITEMS }, { URLACTION_SHELL_MOVE_OR_COPY, IDS_ACTION_SHELL_MOVE_OR_COPY }, { URLACTION_SHELL_FILE_DOWNLOAD, IDS_ACTION_SHELL_FILE_DL }, { URLACTION_SHELL_VERB, IDS_ACTION_SHELL_VERB }, { URLACTION_SHELL_WEBVIEW_VERB, IDS_ACTION_SHELL_WEBVIEW_VERB }, { URLACTION_CREDENTIALS_USE, IDS_ACTION_NW_CREDENTIALS }, { URLACTION_AUTHENTICATE_CLIENT, IDS_ACTION_NW_AUTH_CLIENT }, { URLACTION_COOKIES, IDS_ACTION_NW_COOKIES }, { URLACTION_COOKIES_SESSION, IDS_ACTION_NW_COOKIES_SESSION }, }; ActionStrIDMap actionWarnIDMap[] = { { URLACTION_SHELL_INSTALL_DTITEMS, IDS_WARN_SHELL_INSTALL_DTITEMS }, { URLACTION_SHELL_MOVE_OR_COPY, IDS_WARN_SHELL_MOVE_OR_COPY }, { URLACTION_SHELL_FILE_DOWNLOAD, IDS_WARN_SHELL_FILE_DL }, { URLACTION_SHELL_VERB, IDS_WARN_SHELL_VERB }, { URLACTION_SHELL_WEBVIEW_VERB, IDS_WARN_SHELL_WEBVIEW_VERB }, { URLACTION_HTML_SUBMIT_FORMS, IDS_WARN_HTML_FORMS }, { URLACTION_HTML_SUBMIT_FORMS_FROM, IDS_WARN_HTML_FORMS }, { URLACTION_HTML_SUBMIT_FORMS_TO, IDS_WARN_HTML_FORMS }, }; STDMETHODIMP_(DWORD) CSecurityManager::GetAlertIdForAction(DWORD dwAction) { // The action should exist in our map. int count = ARRAYSIZE(actionAlertIDMap); for (int i = 0; i < count; i++) { if (actionAlertIDMap[i].dwAction == dwAction) { return actionAlertIDMap[i].dwStrID; } } // If we get here the Action was invalid or we are missing an // entry in the map. TransAssert(FALSE); return IDS_ACTION_UNKNOWN; } STDMETHODIMP_(DWORD) CSecurityManager::GetWarnIdForAction(DWORD dwAction) { // The action should exist in our map. int count = ARRAYSIZE(actionWarnIDMap); for (int i = 0; i < count; i++) { if (actionWarnIDMap[i].dwAction == dwAction) { return actionWarnIDMap[i].dwStrID; } } // If we get here the Action was invalid or we are missing an // entry in the map. TransAssert(FALSE); return IDS_WARN_UNKNOWN; } // INFRASTRUCTURE FOR REMEMBERING ANSWERS. // Are the answers for this action supposed to persist. i.e. if the user says 'Yes' or 'No' for // these actions we will not requery them for the same URL for the duration of the security // manager. BOOL CSecurityManager::CPersistAnswers::IsPersistentAnswerAction(DWORD dwAction) { switch (dwAction) { case URLACTION_ACTIVEX_RUN: // This shouldn't happen because it is an aggregator case URLACTION_ACTIVEX_OVERRIDE_OBJECT_SAFETY: // These should never get called because of KludgeMapAggregatePolicy case URLACTION_ACTIVEX_OVERRIDE_DATA_SAFETY: case URLACTION_ACTIVEX_OVERRIDE_SCRIPT_SAFETY: case URLACTION_SCRIPT_OVERRIDE_SAFETY: case URLACTION_ACTIVEX_CONFIRM_NOOBJECTSAFETY: case URLACTION_SCRIPT_RUN: case URLACTION_SCRIPT_JAVA_USE: case URLACTION_HTML_FONT_DOWNLOAD: case URLACTION_SCRIPT_SAFE_ACTIVEX: case URLACTION_CROSS_DOMAIN_DATA: return TRUE; default: return FALSE; } } // CAnswerEntry methods. CSecurityManager::CPersistAnswers::CAnswerEntry::CAnswerEntry(LPCWSTR pszUrl, DWORD dwAction, BOOL iAnswer) { m_pNext = NULL; m_dwAction = dwAction; m_iAnswer = iAnswer; m_pszUrl = StrDup(pszUrl); } CSecurityManager::CPersistAnswers::CAnswerEntry::~CAnswerEntry() { if (m_pszUrl) LocalFree(m_pszUrl); } BOOL CSecurityManager::CPersistAnswers::CAnswerEntry::MatchEntry(LPCWSTR pszUrl, DWORD dwAction) { return (dwAction == m_dwAction && (0 == StrCmp(pszUrl, m_pszUrl))); } // CPersistAnswers methods. CSecurityManager::CPersistAnswers::~CPersistAnswers() { // go through the CAnswerEntries and free them up. CAnswerEntry* pEntry = m_pAnswerEntry; while (pEntry) { CAnswerEntry* pDelete = pEntry; pEntry = pEntry->GetNext(); delete pDelete; } } // Returns TRUE if the user already answered this questions. FALSE otherwise. BOOL CSecurityManager::CPersistAnswers::GetPrevAnswer(LPCWSTR pszUrl, DWORD dwAction, INT* piAnswer) { BOOL bReturn = FALSE; CAnswerEntry* pAnswerEntry; if (!IsPersistentAnswerAction(dwAction)) return FALSE; for (pAnswerEntry = m_pAnswerEntry; pAnswerEntry != NULL; pAnswerEntry = pAnswerEntry->GetNext()) { if (pAnswerEntry->MatchEntry(pszUrl, dwAction)) { if (piAnswer) *piAnswer = pAnswerEntry->GetAnswer(); bReturn = TRUE; break; } } return bReturn; } VOID CSecurityManager::CPersistAnswers::RememberAnswer(LPCWSTR pszUrl, DWORD dwAction, BOOL iAnswer) { // Nothing to do if we are not supposed to be persisted. if (!IsPersistentAnswerAction(dwAction)) return; TransAssert(!GetPrevAnswer(pszUrl, dwAction, NULL)); CAnswerEntry* pNew = new CAnswerEntry(pszUrl, dwAction, iAnswer); // Just don't persist answers if we don't have memory. if (pNew == NULL || pNew->GetUrl() == NULL) return; pNew->SetNext(m_pAnswerEntry); m_pAnswerEntry = pNew; return; } // This is the dialog proc for the generic Zones Alert dialog. // IMPORTANT: This is an ANSI function in an otherwise unicode world. // BE EXTREMELY CAREFUL WHEN CALLING WINDOWS API FUNCTIONS. INT_PTR CSecurityManager::ZonesAlertDialogProc(HWND hDlg, UINT iMsg, WPARAM wParam, LPARAM lParam) { switch (iMsg) { case WM_INITDIALOG: { WCHAR rgszAlert[MAX_ALERT_SIZE]; LPDLGDATA lpDlgData = (LPDLGDATA)lParam; TransAssert(lpDlgData != NULL); DWORD dwStrId = GetAlertIdForAction(lpDlgData->dwAction); if (::LoadStringWrapW(g_hInst, dwStrId, rgszAlert, MAX_ALERT_SIZE) == 0) { TransAssert(FALSE); ::LoadStringWrapW(g_hInst, IDS_ACTION_UNKNOWN, rgszAlert, MAX_ALERT_SIZE); } HWND hwndAlertText = ::GetDlgItem(hDlg, IDC_ZONEALERTTEXT); TransAssert(hwndAlertText != NULL); ::SetWindowTextWrapW(hwndAlertText, rgszAlert); if (lpDlgData->dwFlags & PUAF_FORCEUI_FOREGROUND) { SetForegroundWindow(hDlg); } return TRUE; } case WM_COMMAND: switch (LOWORD(wParam)) { case IDOK: EndDialog(hDlg, ZALERT_YES); return TRUE; case IDCANCEL: EndDialog(hDlg, ZALERT_NO); return TRUE; } break; } /* end switch */ return FALSE; } // This is the dialog proc for the Alert displayed when information is posted over the net. // This is a special case, since it is the only dialog where we let the user persist their change. // DO NOT START ADDING OTHER SPECIAL CASES TO THIS. IF YOU NEED TO, CONSIDER CHANGING THE // THE TEMPLATE POLICY FOR THE ZONE TO BE "CUSTOM" BECAUSE THE ZONE WILL START DIVERGING FROM // THE TEMPLATE IT IS SUPPOSED TO BE BASED ON. BOOL CSecurityManager::IsFormsSubmitAction(DWORD dwAction) { return (dwAction == URLACTION_HTML_SUBMIT_FORMS || dwAction == URLACTION_HTML_SUBMIT_FORMS_FROM || dwAction == URLACTION_HTML_SUBMIT_FORMS_TO ); } INT_PTR CSecurityManager::FormsAlertDialogProc(HWND hDlg, UINT iMsg, WPARAM wParam, LPARAM lParam) { switch (iMsg) { case WM_INITDIALOG: { LPDLGDATA lpDlgData = (LPDLGDATA)lParam; TransAssert(lpDlgData != NULL); LPWSTR pstr = lpDlgData->pstr; if (pstr != NULL) { HWND hwnd = ::GetDlgItem(hDlg, IDC_ZONEALERTTEXT); TransAssert(hwnd != NULL); ::SetWindowTextWrapW(hwnd, pstr); } if (lpDlgData->dwFlags & PUAF_FORCEUI_FOREGROUND) { SetForegroundWindow(hDlg); } if (!(lpDlgData->dwFlags & PUAF_DONTCHECKBOXINDIALOG)) { CheckDlgButton(hDlg, IDC_DONT_WANT_WARNING, BST_CHECKED); } return TRUE; } case WM_COMMAND: switch (LOWORD(wParam)) { case IDOK: case IDYES: { DWORD dwRet; if (IsDlgButtonChecked(hDlg, IDC_DONT_WANT_WARNING) == BST_CHECKED) dwRet = ZALERT_YESPERSIST; else dwRet = ZALERT_YES; EndDialog(hDlg, dwRet); return TRUE; } case IDCANCEL: case IDNO: EndDialog(hDlg, ZALERT_NO); return TRUE; } break; } /* end switch */ return FALSE; } INT CSecurityManager::ShowFormsAlertDialog(HWND hwndParent, LPDLGDATA lpDlgData) { int nRet; TransAssert(lpDlgData != NULL); TransAssert(IsFormsSubmitAction(lpDlgData->dwAction)); // Compose the dialog string. LPWSTR pstr = NULL; WCHAR rgch[MAX_ALERT_SIZE]; ZONEATTRIBUTES zc; zc.cbSize = sizeof(zc); if (SUCCEEDED(m_pZoneManager->GetZoneAttributes(lpDlgData->dwZone, &zc))) { WCHAR rgchStr[MAX_ALERT_SIZE]; UINT uID = (lpDlgData->dwAction == URLACTION_HTML_SUBMIT_FORMS_FROM) ? IDS_ACTION_POST_FROM : IDS_ACTION_FORMS_POST; if (::LoadStringWrapW(g_hInst, uID, rgchStr, MAX_ALERT_SIZE) != 0) { if (wnsprintfW(rgch, MAX_ALERT_SIZE, rgchStr, zc.szDisplayName) != 0) pstr = rgch; } } lpDlgData->pstr = pstr; nRet = (int) ::DialogBoxParamWrapW( g_hInst, MAKEINTRESOURCEW(IDD_WARN_ON_POST), hwndParent, CSecurityManager::FormsAlertDialogProc, (LPARAM)lpDlgData ); return nRet; } INT_PTR CSecurityManager::ZonesWarnDialogProc(HWND hDlg, UINT iMsg, WPARAM wParam, LPARAM lParam) { switch (iMsg) { case WM_INITDIALOG: { WCHAR rgszWarn[MAX_ALERT_SIZE]; LPDLGDATA lpDlgData = (LPDLGDATA)lParam; TransAssert(lpDlgData != NULL); DWORD dwAction = lpDlgData->dwAction; DWORD dwStrId = GetWarnIdForAction(dwAction); if (::LoadStringWrapW(g_hInst, dwStrId, rgszWarn, MAX_ALERT_SIZE) == 0) { TransAssert(FALSE); ::LoadStringWrapW(g_hInst, IDS_WARN_UNKNOWN, rgszWarn, MAX_ALERT_SIZE); } HWND hwndWarnText = ::GetDlgItem(hDlg, IDC_ZONEALERTTEXT); TransAssert(hwndWarnText != NULL); ::SetWindowTextWrapW(hwndWarnText, rgszWarn); if (lpDlgData->dwFlags & PUAF_FORCEUI_FOREGROUND) { SetForegroundWindow(hDlg); } return TRUE; } case WM_COMMAND: switch (LOWORD(wParam)) { case IDOK: case IDCANCEL: EndDialog(hDlg, ZALERT_YES); return TRUE; } break; } /* end switch */ return FALSE; } STDMETHODIMP CSecurityManager::ProcessUrlAction ( LPCWSTR pwszUrl, DWORD dwAction, BYTE* pPolicy, DWORD cbPolicy, BYTE* pContext, DWORD cbContext, DWORD dwFlags, DWORD dwReserved ) { PerfDbgLog(tagCSecurityManager, this, "+CSecurityManager::ProcessUrlAction"); HRESULT hr = INET_E_DEFAULT_ACTION; DWORD dwZone = ZONEID_INVALID; // First check if the delegation interface wants to handle this. if (m_pDelegateSecMgr) { hr = m_pDelegateSecMgr->ProcessUrlAction(pwszUrl, dwAction, pPolicy, cbPolicy, pContext, cbContext, dwFlags, dwReserved); } if (hr != INET_E_DEFAULT_ACTION) { // Delegation interface processed the request. return hr; } if (!EnsureZoneManager()) { return E_UNEXPECTED; } // Increment our refcount so we don't get destroyed for the duration of this // function. AddRef(); if (SUCCEEDED(hr = MapUrlToZone(pwszUrl, &dwZone, dwFlags))) { DWORD dwPolicy; hr = m_pZoneManager->GetZoneActionPolicy(dwZone, dwAction, (BYTE*)&dwPolicy, sizeof(dwPolicy), URLZONEREG_DEFAULT); if (SUCCEEDED(hr)) { DWORD dwPermissions = GetUrlPolicyPermissions(dwPolicy); // Are we supposed to be showing any UI here? if (((dwPermissions == URLPOLICY_QUERY && (dwFlags & PUAF_NOUI) == 0) || (dwPermissions == URLPOLICY_DISALLOW && (dwFlags & PUAF_WARN_IF_DENIED) != 0)) && HIWORD(dwPolicy) == 0) { HWND hwndParent = NULL; // Show UI unless the host indicates otherwise. BOOL bShowUI = TRUE; if (m_pSite != NULL) { HRESULT hrGetWnd = m_pSite->GetWindow(&hwndParent); // Host doesn't want us to show UI. if (hrGetWnd == S_FALSE && hwndParent == INVALID_HANDLE_VALUE) bShowUI = FALSE; else if (FAILED(hrGetWnd)) hwndParent = NULL; // Disable any modeless dialog boxes m_pSite->EnableModeless(FALSE); } int nRet = -1; BOOL fRememberAnswer = FALSE; // structure used to pass information to the dialog proc's. DlgData dlgData; dlgData.dwAction = dwAction; dlgData.dwZone = dwZone; dlgData.pstr = NULL; dlgData.dwFlags = dwFlags | ((dwPolicy & URLPOLICY_DONTCHECKDLGBOX) ? PUAF_DONTCHECKBOXINDIALOG : 0); dwPolicy = dwPolicy & ~URLPOLICY_DONTCHECKDLGBOX; if (dwPermissions == URLPOLICY_QUERY) { // First check to see if the user already answered this question once. if (!m_persistAnswers.GetPrevAnswer(pwszUrl, dwAction, &nRet)) { fRememberAnswer = TRUE; if (!bShowUI) { // If we can't show UI just act as if the user said No. nRet = ZALERT_NO; } else if (IsFormsSubmitAction(dwAction)) { nRet = ShowFormsAlertDialog(hwndParent, &dlgData); } else { nRet = (int) ::DialogBoxParamWrapW( g_hInst, MAKEINTRESOURCEW(IDD_ZONE_ALERT), hwndParent, CSecurityManager::ZonesAlertDialogProc, (LPARAM)&dlgData ); } } } else { TransAssert(dwPermissions == URLPOLICY_DISALLOW); if (bShowUI) { nRet = (int) ::DialogBoxParamWrapW( g_hInst, MAKEINTRESOURCEW(IDD_WARN_ALERT), hwndParent, CSecurityManager::ZonesWarnDialogProc, (LPARAM)&dlgData ); } } // If we failed to show the dialog we should just return // the policies unmodified. if (dwPermissions == URLPOLICY_QUERY && nRet != -1) { // Change the policy to reflect the users choice. DWORD dwYesOnlyPolicy; dwYesOnlyPolicy = dwPolicy | URLPOLICY_DONTCHECKDLGBOX; // copy old policy before it is changed SetUrlPolicyPermissions(dwPolicy, nRet ? URLPOLICY_ALLOW : URLPOLICY_DISALLOW); if (fRememberAnswer) m_persistAnswers.RememberAnswer(pwszUrl, dwAction, nRet); // The only case where we should change the policy is we have a checkbox on the dialog // we popped up that says "Don't ask me again". Today the only thing that does that is // the forms submit form. // TODO: create a more generic category name "CanDlgChangePolicy" or something like that // instead of the specific IsFormsSubmitAction. if (IsFormsSubmitAction(dwAction) && ((nRet == ZALERT_YESPERSIST) || (nRet == ZALERT_YES))) { m_pZoneManager->SetZoneActionPolicy(dwZone, dwAction, (BYTE*)((nRet == ZALERT_YESPERSIST) ? &dwPolicy : &dwYesOnlyPolicy), sizeof(dwPolicy), URLZONEREG_DEFAULT); } } if (m_pSite != NULL) { m_pSite->EnableModeless(TRUE); } } TransAssert(cbPolicy == 0 || cbPolicy >= sizeof(DWORD)); if (cbPolicy >= sizeof(DWORD) && pPolicy != NULL) *(DWORD*)pPolicy = dwPolicy; // Code to check for allowed list of directx objects // for URLACTION_ACTIVEX_RUN if (dwAction == URLACTION_ACTIVEX_RUN && dwPolicy == URLPOLICY_ACTIVEX_CHECK_LIST) { DWORD dwValue = 0; hr = CSecurityManager::GetActiveXRunPermissions(pContext, dwValue); *(DWORD*)pPolicy = dwValue; } // normal allowed permissions else if (GetUrlPolicyPermissions(dwPolicy) == URLPOLICY_ALLOW) { hr = S_OK; } else { hr = S_FALSE; } } } Release(); return hr; } STDMETHODIMP CSecurityManager::QueryCustomPolicy ( LPCWSTR pwszUrl, REFGUID guidKey, BYTE** ppPolicy, DWORD* pcbPolicy, BYTE* pContext, DWORD cbContext, DWORD dwReserved ) { PerfDbgLog(tagCSecurityManager, this, "+CSecurityManager::QueryCustomPolicy"); HRESULT hr = INET_E_DEFAULT_ACTION; DWORD dwZone = ZONEID_INVALID; if (m_pDelegateSecMgr) { hr = m_pDelegateSecMgr->QueryCustomPolicy(pwszUrl, guidKey, ppPolicy, pcbPolicy, pContext, cbContext, dwReserved); } if (hr == INET_E_DEFAULT_ACTION) { if (!EnsureZoneManager()) { return E_UNEXPECTED; } if (SUCCEEDED(hr = MapUrlToZone(pwszUrl, &dwZone, NULL))) { hr = m_pZoneManager->GetZoneCustomPolicy(dwZone, guidKey, ppPolicy, pcbPolicy, URLZONEREG_DEFAULT); } else { hr = E_UNEXPECTED; } } return hr; } STDMETHODIMP CSecurityManager::GetSecuritySite ( IInternetSecurityMgrSite** ppSite ) { if (ppSite) { if (m_pSite) m_pSite->AddRef(); *ppSite = m_pSite; } return S_OK; } STDMETHODIMP CSecurityManager::SetSecuritySite ( IInternetSecurityMgrSite* pSite ) { if (m_pSite) { m_pSite->Release(); } if (m_pDelegateSecMgr) { m_pDelegateSecMgr->Release(); m_pDelegateSecMgr = NULL; } m_pSite = pSite; if (m_pSite) { m_pSite->AddRef(); IServiceProvider* pServiceProvider = NULL; if (SUCCEEDED(m_pSite->QueryInterface(IID_IServiceProvider, (void**)&pServiceProvider))) { TransAssert(pServiceProvider != NULL); if (SUCCEEDED(pServiceProvider->QueryService( SID_SInternetSecurityManager, IID_IInternetSecurityManager, (void**)&m_pDelegateSecMgr))) { TransAssert(m_pDelegateSecMgr != NULL); } else { m_pDelegateSecMgr = NULL; } pServiceProvider->Release(); } } return S_OK; } // Mapping related functions HRESULT CSecurityManager::SetZoneMapping ( DWORD dwZone, LPCWSTR pszPattern, DWORD dwFlags ) { PerfDbgLog(tagCSecurityManager, this, "+CSecurityManager::SetZoneMapping"); HRESULT hr = INET_E_DEFAULT_ACTION; // Increment the counter so any cached url to zone mappings are invalidated. IncrementGlobalCounter(); if (m_pDelegateSecMgr) { hr = m_pDelegateSecMgr->SetZoneMapping(dwZone, pszPattern, dwFlags); } if (hr == INET_E_DEFAULT_ACTION) { LPWSTR pwszSecPattern = NULL; hr = CoInternetGetSecurityUrl(pszPattern, &pwszSecPattern, PSU_DEFAULT, 0); if (SUCCEEDED(hr)) { CFreeStrPtr freeStr(pwszSecPattern); ZONEMAP_COMPONENTS zc; hr = zc.Crack(pwszSecPattern, PUAF_ACCEPT_WILDCARD_SCHEME, TRUE); if (hr != S_OK) return hr; ZONEATTRIBUTES za; za.cbSize = sizeof(za); if (!EnsureZoneManager()) return E_OUTOFMEMORY; // Don't allow adding not https entries if the zone requires server verification. if (!(dwFlags & SZM_DELETE) && SUCCEEDED(m_pZoneManager->GetZoneAttributes(dwZone, &za))) { if (za.dwFlags & ZAFLAGS_REQUIRE_VERIFICATION) { if (zc.nScheme != URL_SCHEME_HTTPS) { return E_ACCESSDENIED; } } } if (zc.fIPRange) { hr = AddDeleteIPRule(&zc, dwZone, dwFlags); return hr; } // Zone mappings for drive letters are hardcoded if (zc.fDrive) return E_INVALIDARG; // Guard against buffer overflow. if (CSTRLENW(SZDOMAINS) + zc.cchDomain + 1 + zc.cchSite + 1 >= MAX_PATH) return E_INVALIDARG; TCHAR szKeyName[MAX_PATH]; DWORD cchKeyName = CSTRLENW(SZDOMAINS); memcpy(szKeyName, SZDOMAINS, sizeof(TCHAR) * cchKeyName); if ((zc.cchDomain == 0 && zc.cchSite == 0) || (zc.cchProtocol == 0)) { return E_INVALIDARG; } if (zc.pszDomain) { memcpy(szKeyName + cchKeyName, zc.pszDomain, sizeof(TCHAR) * zc.cchDomain); // Null terminate for strchr. szKeyName[cchKeyName + zc.cchDomain] = TEXT('\0'); if (StrChr(szKeyName + cchKeyName, WILDCARD) != NULL) return E_INVALIDARG; cchKeyName += zc.cchDomain; } // Check for the simple wildcard case. // patterns such as *.microsoft.com where the only thing // after a * is the second-level domain. if (zc.pszSite[0] == WILDCARD && zc.cchSite == 1) { if (!zc.pszDomain) return E_INVALIDARG; } else { // Wildcards are only permitted at the begining of pattern. // So patterns such as *.foo.*.microsoft.com are invalid. if (zc.pszSite[0] == WILDCARD) { // We already know that zc.cchSite is greater than 1 // because we would have caught it in the outer 'if' clause // otherwise. if (zc.pszSite[1] != DOT) { return E_INVALIDARG; } // Skip over the leading *. and make sure there are no // other *'s in the string. if (StrRChr(zc.pszSite + 2, zc.pszSite + zc.cchSite, WILDCARD) != NULL) { return E_INVALIDARG; } } if (zc.pszDomain) // Add seperator only if we added a domain name. { szKeyName[cchKeyName++] = BACKSLASH; } else if (!IsOpaqueScheme(zc.nScheme) && (zc.pszSite[zc.cchSite - 1] == DOT || zc.pszSite[0] == DOT) ) { // Catches invalid cases such as http://ohserv. or http://.inetsdk. return E_INVALIDARG; } memcpy(szKeyName + cchKeyName, zc.pszSite, sizeof(TCHAR) * zc.cchSite); if (!IsOpaqueScheme(zc.nScheme)) { szKeyName[cchKeyName + zc.cchSite] = TEXT('\0'); } cchKeyName += zc.cchSite; } szKeyName[cchKeyName] = 0; CRegKey regMap; DWORD dwErr; if (dwFlags & SZM_DELETE) { // Delete mapping if one exists. if (ERROR_FILE_NOT_FOUND == regMap.Open(m_regZoneMap, szKeyName, KEY_WRITE)) return S_OK; // nothing to delete if ((dwErr = regMap.DeleteValue(zc.pszProtocol)) == ERROR_SUCCESS) { // Try reclaiming any registry key's which might be empty. regMap.Close(); m_regZoneMap.DeleteEmptyKey(szKeyName); if (zc.pszDomain) { DWORD cch = CSTRLENW(SZDOMAINS) + zc.cchDomain; szKeyName[cch] = TEXT('\0'); m_regZoneMap.DeleteEmptyKey(szKeyName); } return S_OK; } else { hr = HRESULT_FROM_WIN32(dwErr); } } else { // Creates new mapping. if ((dwErr = regMap.Create(m_regZoneMap, szKeyName, KEY_READ | KEY_WRITE)) == ERROR_SUCCESS) { DWORD dwZoneEntry; if (regMap.QueryValue(&dwZoneEntry, zc.pszProtocol) == ERROR_SUCCESS) { hr = HRESULT_FROM_WIN32(ERROR_FILE_EXISTS); } else if ((dwErr = regMap.SetValue(dwZone, zc.pszProtocol)) == ERROR_SUCCESS) { return S_OK; } else { hr = HRESULT_FROM_WIN32(dwErr); } } else { hr = HRESULT_FROM_WIN32(dwErr); } } } } return hr; } // Helper functions for GetZoneMappings // Given a site name and a domain name composes the string // site.domain.com HRESULT CSecurityManager::ComposeUrlSansProtocol ( LPCTSTR pszDomain, int cchDomain, LPCTSTR pszSite, int cchSite, LPTSTR* ppszRet, int* pcchUrlSansProtocol ) { if (ppszRet == NULL) { return E_INVALIDARG; } int cchUrlSansProtocol = cchSite + 1 /* . */ + cchDomain; // Create the part of the string without the protocol LPTSTR szUrlSansProtocol = new TCHAR[cchUrlSansProtocol + 1]; if (szUrlSansProtocol == NULL) { *ppszRet = NULL; if (pcchUrlSansProtocol) *pcchUrlSansProtocol = 0; return E_OUTOFMEMORY; } LPTSTR szCurrent = szUrlSansProtocol; // Copy over the specific parts of the name. if (pszSite != NULL) { memcpy(szCurrent, pszSite, cchSite * sizeof(TCHAR)); szCurrent += cchSite; memcpy(szCurrent, TEXT("."), 1 * sizeof(TCHAR)); szCurrent += 1; } memcpy(szCurrent, pszDomain, cchDomain * sizeof(TCHAR)); szCurrent += cchDomain; // Finally copy over the trailing zero. szCurrent[0] = TEXT('\0'); *ppszRet = szUrlSansProtocol; if (pcchUrlSansProtocol) *pcchUrlSansProtocol = cchUrlSansProtocol; return S_OK; } HRESULT CSecurityManager::ComposeUrl ( LPCTSTR pszUrlSansProt, int cchUrlSansProt, LPCTSTR pszProtocol, int cchProtocol, BOOL bAddWildCard, LPTSTR* ppszUrl, int* pcchUrl ) { if (ppszUrl == NULL) { return E_INVALIDARG; } BOOL bWildCardScheme = FALSE; BOOL bOpaqueScheme = FALSE; if (cchProtocol == 1 && pszProtocol[0] == WILDCARD) { bWildCardScheme = TRUE; bOpaqueScheme = FALSE; } else { // Figure out if this is an an opaque scheme. LPWSTR pszTemp = (LPWSTR)_alloca((cchProtocol + 2) * sizeof(TCHAR)); memcpy(pszTemp, pszProtocol, cchProtocol * sizeof(TCHAR)); pszTemp[cchProtocol] = TEXT(':'); pszTemp[cchProtocol + 1] = TEXT('\0'); PARSEDURL pu; pu.cbSize = sizeof(pu); HRESULT hr = ParseURL(pszTemp, &pu); if (SUCCEEDED(hr)) { bOpaqueScheme = IsOpaqueScheme(pu.nScheme); } else { bOpaqueScheme = TRUE; } } // cchUrl will have the eventual length of the string we will send out int cchUrl = cchUrlSansProt; if (bOpaqueScheme) { cchUrl += cchProtocol + 1; // we have to add prot: to the URL } else if (bWildCardScheme) { // If the scheme is a wildcard we don't add it to the eventual display. } else { cchUrl += cchProtocol + 3; // we have to add prot:// to the url. } // If we are not an opaque schema, we might need to add a wildcard character as well. if (!bOpaqueScheme && bAddWildCard) { cchUrl += 2; /* for *. */ } LPTSTR szUrl = new TCHAR[cchUrl + 1]; if (szUrl == NULL) { *ppszUrl = NULL; if (pcchUrl) *pcchUrl = 0; return E_OUTOFMEMORY; } LPTSTR szCurrent = szUrl; // if the scheme is wildcard we don't want to display the scheme at all. // i.e we will show *.microsoft.com and *:*.microsoft.com if (bWildCardScheme) { if (bAddWildCard) { memcpy(szCurrent, TEXT("*."), 2 * sizeof(TCHAR)); szCurrent += 2; } } else { memcpy(szCurrent, pszProtocol, cchProtocol * sizeof(TCHAR)); szCurrent += cchProtocol; if (bOpaqueScheme) { memcpy(szCurrent, TEXT(":"), 1 * sizeof(TCHAR)); szCurrent += 1; } else { memcpy(szCurrent, TEXT("://"), 3 * sizeof(TCHAR)); szCurrent += 3; if (bAddWildCard) { memcpy(szCurrent, TEXT("*."), 2 * sizeof(TCHAR)); szCurrent += 2; } } } memcpy(szCurrent, pszUrlSansProt, cchUrlSansProt * sizeof(TCHAR)); szCurrent += cchUrlSansProt; szCurrent[0] = TEXT('\0'); *ppszUrl = szUrl; if (pcchUrl) *pcchUrl = cchUrl; return S_OK; } HRESULT CSecurityManager::AddIPRulesToEnum ( DWORD dwZone, CEnumString* pEnumString ) { HRESULT hr = NOERROR; if ((HUSKEY)m_regZoneMap == NULL) return E_UNEXPECTED; CRegKey regRanges; DWORD cNumRanges = 0; if (ERROR_SUCCESS != regRanges.Open(m_regZoneMap, SZRANGES, KEY_READ) || ERROR_SUCCESS != regRanges.QuerySubKeyInfo(&cNumRanges, NULL, NULL) ) { return S_OK; // Nothing to add if we can't open the key. } if (cNumRanges == 0) return S_OK; DWORD cchMaxKey = 20; TCHAR szKeyName[20]; TCHAR rgchSansProtocol[MAX_PATH]; DWORD iItem; for (iItem = 0; iItem < cNumRanges; iItem++) { DWORD cbName, cbRange; CRegKey regItem; cbName = cchMaxKey; cbRange = sizeof(rgchSansProtocol) - 3 * sizeof(TCHAR); if (ERROR_SUCCESS == regRanges.EnumKey(iItem, szKeyName, &cbName) && ERROR_SUCCESS == regItem.Open(regRanges, szKeyName, KEY_READ) && ERROR_SUCCESS == regItem.QueryValue(rgchSansProtocol, SZRANGE, &cbRange) ) { LONG lRetProtocol = NOERROR; TCHAR rgchProtocol[MAX_PATH]; DWORD dwZoneRead = ZONEID_INVALID; DWORD dwType; for (DWORD dwIdxProt = 0, cchP = ARRAYSIZE(rgchProtocol), dwSizeZoneId = sizeof(dwZoneRead); (((lRetProtocol = regItem.EnumValue(dwIdxProt, rgchProtocol, &cchP, &dwType, &dwZoneRead, &dwSizeZoneId)) != ERROR_NO_MORE_ITEMS) && (hr == NOERROR)); dwIdxProt++, cchP = ARRAYSIZE(rgchProtocol), dwSizeZoneId = sizeof(dwZoneRead), dwZoneRead = ZONEID_INVALID ) { #ifdef unix if (lRetProtocol == ERROR_MORE_DATA) continue; #endif /* unix */ if (lRetProtocol != NOERROR) break; if (dwSizeZoneId == 0 || cchP == 0 || rgchProtocol[0] == TEXT('\0') || dwType != REG_DWORD || dwZoneRead == ZONEID_INVALID) continue; if (dwZone == dwZoneRead) { int cchProtocol = lstrlen(rgchProtocol); int cchRange = lstrlen(rgchSansProtocol); LPTSTR szUrl = NULL; if ((SUCCEEDED(ComposeUrl(rgchSansProtocol, cchRange, rgchProtocol, cchProtocol, FALSE, &szUrl, NULL))) && (SUCCEEDED(pEnumString->AddString(szUrl)))) { if (szUrl != NULL) delete[] szUrl; } else { if (szUrl != NULL) delete[] szUrl; hr = E_OUTOFMEMORY; break; } } } /* for each protocol */ } } /* for each range entry */ return hr; } // Given a Registry key and a part to the URL, this function looks through the // 'values' in the registry looking for a zone match. When it finds one it adds the // strings to the CEnumString class that is passed in. HRESULT CSecurityManager::AddUrlsToEnum ( CRegKey* pRegKey, DWORD dwZone, LPCTSTR pszUrlSansProt, int cchUrlSansProt, BOOL bAddWildCard, CEnumString* pEnumString ) { HRESULT hr = NOERROR; // Iterate over the values and make up the strings we need. LONG lRetProtocol = NOERROR; TCHAR rgszProtocol[MAX_PATH]; DWORD dwZoneRead = ZONEID_INVALID; DWORD dwType; for (DWORD dwIdxProt = 0, cchP = sizeof(rgszProtocol) / sizeof(TCHAR), dwSizeZoneId = sizeof(dwZoneRead); (((lRetProtocol = pRegKey->EnumValue(dwIdxProt, rgszProtocol, &cchP, &dwType, &dwZoneRead, &dwSizeZoneId)) != ERROR_NO_MORE_ITEMS) && (hr == NOERROR)); dwIdxProt++, cchP = sizeof(rgszProtocol) / sizeof(TCHAR), dwSizeZoneId = sizeof(dwZoneRead), dwZoneRead = ZONEID_INVALID ) { if (lRetProtocol != NO_ERROR) { // Break out of this loop but keep trying other sites. break; } if (dwSizeZoneId == 0 || cchP == 0 || rgszProtocol[0] == TEXT('\0') || dwType != REG_DWORD || dwZoneRead == ZONEID_INVALID) continue; // Yippeee, finally found a match. if (dwZone == dwZoneRead) { int cchProtocol = lstrlen(rgszProtocol); LPTSTR szUrl = NULL; // Compose the name of the URL. if ((SUCCEEDED(ComposeUrl(pszUrlSansProt, cchUrlSansProt, rgszProtocol, cchProtocol, bAddWildCard, &szUrl, NULL))) && (SUCCEEDED(pEnumString->AddString(szUrl)))) { // Both succeeded we have added this string to the enumeration. // Just free up the memory and move on. if (szUrl != NULL) delete[] szUrl; } else { if (szUrl != NULL) delete[] szUrl; hr = E_OUTOFMEMORY; break; } } } /* for each protocol */ return hr; } HRESULT CSecurityManager::GetZoneMappings ( DWORD dwZone, IEnumString** ppEnumString, DWORD dwFlags ) { PerfDbgLog(tagCSecurityManager, this, "+CSecurityManager::GetZoneMappings"); HRESULT hr = NOERROR; CEnumString* pEnumString = NULL; pEnumString = new CEnumString(); if (pEnumString == NULL) return E_OUTOFMEMORY; CRegKey regDomainRoot; // We setup three loops below. // for each domain name // for each site // for each protocol. // The one twist is that for each domain we also have to enumerate the sites // to deal with wildcards such as http://*.microsoft.com // BUGBUG: MAX_PATH is a safe assumption, but we should change this to get the // memory dynamically. TCHAR rgszDomain[MAX_PATH]; TCHAR rgszSite[MAX_PATH]; TCHAR rgszProtocol[MAX_PATH]; LONG lRetDomain = NOERROR; if (((HUSKEY)m_regZoneMap != NULL) && (regDomainRoot.Open(m_regZoneMap, SZDOMAINS, KEY_READ) == NOERROR) ) { // If we couldn't open the root, then no rules exist for any zone. // Return an empty enumerator for (DWORD dwIdxDomain = 0, cchD = sizeof(rgszDomain) / sizeof(TCHAR); (((lRetDomain = regDomainRoot.EnumKey(dwIdxDomain, rgszDomain, &cchD)) != ERROR_NO_MORE_ITEMS) && (hr == NOERROR)); dwIdxDomain++, cchD = sizeof(rgszDomain) / sizeof(TCHAR) ) { if (lRetDomain != NOERROR) { TransAssert(lRetDomain != ERROR_MORE_DATA); break; } TCHAR rgszSite[MAX_PATH]; LONG lRetSite = NOERROR; // Open the key to the domain. CRegKey regDomain; if (regDomain.Open(regDomainRoot, rgszDomain, KEY_READ) != NOERROR) { // We couldn't open this domain for some reason, but we will // keep trying the other domains. continue; } int cchDomain = lstrlen(rgszDomain); TransAssert((HUSKEY)regDomain != NULL); for (DWORD dwIdxSite = 0, cchS = sizeof(rgszSite) / sizeof(TCHAR); (((lRetSite = regDomain.EnumKey(dwIdxSite, rgszSite, &cchS)) != ERROR_NO_MORE_ITEMS) && (hr == NOERROR)); dwIdxSite++, cchS = sizeof(rgszSite) / sizeof(TCHAR) ) { if (lRetSite != NOERROR) { TransAssert(lRetSite != ERROR_MORE_DATA); break; // We will break out of this loop but keep trying other domains. } CRegKey regSite; if (regSite.Open(regDomain, rgszSite, KEY_READ) != NOERROR) { // Couldn't open the site but try other sites anyway. continue; } int cchSite = lstrlen(rgszSite); LPTSTR szUrlSansProtocol = NULL; int cchUrlSansProtocol = 0; // Get everything about the name figured out if ((FAILED(ComposeUrlSansProtocol(rgszDomain, cchDomain, rgszSite, cchSite, &szUrlSansProtocol, &cchUrlSansProtocol))) || szUrlSansProtocol == NULL) { hr = E_OUTOFMEMORY; break; } TransAssert(cchUrlSansProtocol != 0); hr = AddUrlsToEnum(®Site, dwZone, szUrlSansProtocol, cchUrlSansProtocol, FALSE, pEnumString); // Free up the memory we just allocated. delete[] szUrlSansProtocol; } /* for each site */ // At the domain level we need to look for any protocol defaults // An example string would look like http://*.microsoft.com LPTSTR szSiteWildCard = NULL; int cchSiteWildCard = 0; // If the string doesn't contain any .'s we didn't break it out as a domain/site // in the first place. We shouldn't add a *. wildcard in this case. BOOL bAddWildCard = (StrChr(rgszDomain, DOT) != NULL); if ((FAILED(ComposeUrlSansProtocol(rgszDomain, cchDomain, NULL, 0, &szSiteWildCard, &cchSiteWildCard))) || szSiteWildCard == NULL) { hr = E_OUTOFMEMORY; break; } TransAssert(cchSiteWildCard != 0); hr = AddUrlsToEnum(®Domain, dwZone, szSiteWildCard, cchSiteWildCard, bAddWildCard, pEnumString); delete[] szSiteWildCard; } }// opened domains root key // Finally add all the IP range entries to the structure. if (hr == NOERROR) { hr = AddIPRulesToEnum(dwZone, pEnumString); } // Finally call the strings if (hr == NOERROR) { // Pass back the Enumeration to the caller. if (ppEnumString) *ppEnumString = pEnumString; } else { // We need to free the object and return NULL to the caller. if (ppEnumString) *ppEnumString = NULL; delete pEnumString; } return hr; } // MapUrlToZone helper methods return S_OK if match found, otherwise S_FALSE HRESULT CSecurityManager::MapUrlToZone(ZONEMAP_COMPONENTS* pzc, DWORD* pdwZone, DWORD dwFlags, BOOL* pfMarked, LPWSTR* ppszMarkURL) { HRESULT hr; CRegKey regProtocols; BOOL fMarked = FALSE; // Guard against buffer overflow. if (CSTRLENW(SZDOMAINS) + pzc->cchDomain + 1 + pzc->cchSite + 1 >= MAX_PATH) goto default_zone; // do the Mark of the Web stuff, if we have a file: if (pzc->nScheme == URL_SCHEME_FILE) { LPWSTR pwszMarkURL = NULL; TCHAR* pszExt = PathFindExtension(pzc->pszSite); LPCTSTR pszPath; TCHAR szPath[MAX_PATH]; if (!pzc->fDrive) { StrCpy(szPath, TEXT("\\\\")); StrCatBuff(szPath, pzc->pszSite, ARRAYSIZE(szPath)); pszPath = szPath; } else pszPath = pzc->pszSite; // Don't look for the mark if flags say not to. // We only want to pursue the Mark of the Web for htm(l) files. // If Marked, we want to be sure we're not chasing our tail recursively. if (!(dwFlags & MUTZ_NOSAVEDFILECHECK) && (StrCmpI(pszExt, TEXT(".htm")) == 0 || StrCmpI(pszExt, TEXT(".html")) == 0) && FileBearsMarkOfTheWeb(pszPath, &pwszMarkURL) && StrCmp(pszPath, pwszMarkURL) != 0) { MapUrlToZone(pwszMarkURL, pdwZone, dwFlags | MUTZ_NOSAVEDFILECHECK | MUTZ_NOCACHE); // Don't take the mark's word for it if it's asserting local machine if (*pdwZone != URLZONE_LOCAL_MACHINE) { fMarked = TRUE; if (ppszMarkURL) { *ppszMarkURL = pwszMarkURL; pwszMarkURL = NULL; // give mark string to caller, don't free } } } if (pwszMarkURL) LocalFree(pwszMarkURL); if (fMarked) goto done; // if not marked, or ignoring the mark, process as normal. } if (pzc->fDrive) { switch (pzc->dwDriveType) { case 0: case 1: break; case DRIVE_REMOTE: TransAssert(FALSE); *pdwZone = URLZONE_INTRANET; goto done; default: { BOOL bCacheFile = IsFileInCacheDir(pzc->pszSite); *pdwZone = bCacheFile ? URLZONE_INTERNET : URLZONE_LOCAL_MACHINE; goto done; } } } else if (IsOpaqueScheme(pzc->nScheme)) { if (S_OK == CheckSiteAndDomainMappings(pzc, pdwZone, pzc->pszProtocol)) goto done; if (S_OK == CheckMKURL(pzc, pdwZone, pzc->pszProtocol)) goto done; } else { if (pzc->fAddr) { // Check name in form of IP address against range rules. if (S_OK == CheckAddressAgainstRanges(pzc, pdwZone, pzc->pszProtocol)) goto done; } if ((HUSKEY)m_regZoneMap) { // Check for a mapping for the site (or domain, if applicable) if (S_OK == CheckSiteAndDomainMappings(pzc, pdwZone, pzc->pszProtocol)) goto done; if (S_OK == CheckUNCAsIntranet(pzc, pdwZone, pzc->pszProtocol)) goto done; // Check for Local Intranet name rules. if (S_OK == CheckIntranetName(pzc, pdwZone, pzc->pszProtocol)) goto done; // Check for proxy bypass rule. if (S_OK == CheckProxyBypassRule(pzc, pdwZone, pzc->pszProtocol)) goto done; } } // Check for protocol defaults. if (ERROR_SUCCESS == regProtocols.Open(m_regZoneMap, SZPROTOCOLS, KEY_READ) && ERROR_SUCCESS == regProtocols.QueryValueOrWild(pdwZone, pzc->pszProtocol) ) { goto done; } default_zone: *pdwZone = URLZONE_INTERNET; done: if (pfMarked) *pfMarked = fMarked; hr = S_OK; return hr; } HRESULT CSecurityManager::ReadAllIPRules() { DWORD* pdwIndexes = NULL; EnterCriticalSection(&s_csectIP); if (s_pRanges != NULL) { delete[] s_pRanges; s_pRanges = NULL; s_cNumRanges = 0; } // We always start with the key "Range1" if nothing is found. s_dwNextRangeIndex = 1; CRegKey regRanges, regItem; if ((HUSKEY)m_regZoneMap == NULL) { if (ERROR_SUCCESS != m_regZoneMap.Open(NULL, SZZONEMAP, KEY_READ)) goto done; } DWORD cchMaxKey; // Read in ranges from registry. if (ERROR_SUCCESS != regRanges.Open(m_regZoneMap, SZRANGES, KEY_READ) || ERROR_SUCCESS != regRanges.QuerySubKeyInfo(&s_cNumRanges, &cchMaxKey, NULL) || 0 == s_cNumRanges ) { goto done; } // BUGBUG: TODO: Figure out why QuerySubKeyInfo is returning the wrong information. cchMaxKey = 20; // Calculate size of range item and allocate array (no alignment padding) s_cbRangeItem = sizeof(RANGE_ITEM) + sizeof(TCHAR) * (cchMaxKey + 1); s_pRanges = new BYTE[s_cbRangeItem * s_cNumRanges]; pdwIndexes = new DWORD[s_cNumRanges]; if (!s_pRanges || !pdwIndexes) { s_cNumRanges = 0; goto done; } // Loop through the ranges. TCHAR szRange[MAX_IPRANGE]; // 4x "###-###." RANGE_ITEM* pItem; DWORD iItem, cItem; pItem = (RANGE_ITEM*)s_pRanges; cItem = s_cNumRanges; s_cNumRanges = 0; for (iItem = 0; iItem < cItem; iItem++) { // Reset output buffer sizes. DWORD cbName, cbRange; cbName = cchMaxKey; cbRange = sizeof(szRange); // Get range from next key. if (ERROR_SUCCESS != regRanges.EnumKey(iItem, pItem->szName, &cbName) || ERROR_SUCCESS != regItem.Open(regRanges, pItem->szName, KEY_READ) || ERROR_SUCCESS != regItem.QueryValue(szRange, SZRANGE, &cbRange) ) { break; } // Figure out the index for the named Range entry. Ignore it is not of the // form Range followed by Number. Range#### DWORD chRange = lstrlen(SZRANGEPREFIX); if (0 == StrCmpNI(pItem->szName, SZRANGEPREFIX, chRange)) { pdwIndexes[iItem] = StrToInt(pItem->szName + chRange); } if (!ReadIPRule(szRange, pItem->bLow, pItem->bHigh)) continue; // Advance to next range item in array. pItem = (RANGE_ITEM*)(((LPBYTE)pItem) + s_cbRangeItem); s_cNumRanges++; } // Find an empty slot or if we don't find one for (s_dwNextRangeIndex = 1; s_dwNextRangeIndex <= cItem; s_dwNextRangeIndex++) { DWORD i; // Go through the entries and see if the index exists. for (i = 0; i < cItem; i++) { if (pdwIndexes[i] == s_dwNextRangeIndex) break; } if (i == cItem) // This range item is available. break; } TransAssert(s_dwNextRangeIndex >= 1 && s_dwNextRangeIndex <= (cItem + 1)); delete[] pdwIndexes; done: LeaveCriticalSection(&s_csectIP); return S_OK; } HRESULT CSecurityManager::AddDeleteIPRule (ZONEMAP_COMPONENTS* pzc, DWORD dwZone, DWORD dwFlags) { HRESULT hr = S_OK; DWORD dwError = ERROR_SUCCESS; BOOL bFoundItem = FALSE; TCHAR szItemName[MAX_PATH]; TransAssert(s_dwNextRangeIndex != 0); TransAssert(pzc->fIPRange); if (s_dwNextRangeIndex == 0) return E_UNEXPECTED; EnterCriticalSection(&s_csectIP); RANGE_ITEM* pItem = (RANGE_ITEM*)s_pRanges; // First figure out if this item already exists in our list. // This is useful in both the delete and add case. for (DWORD iRange = 0; iRange < s_cNumRanges; iRange++, pItem = (RANGE_ITEM*)(((LPBYTE)pItem) + s_cbRangeItem)) { if ((0 == memcmp(pItem->bLow, pzc->rangeItem.bLow, sizeof(pItem->bLow))) && (0 == memcmp(pItem->bHigh, pzc->rangeItem.bHigh, sizeof(pItem->bHigh))) ) { break; } } // If we have a valid "named" entry in the registry. if (iRange < s_cNumRanges && pItem->szName[0] != TEXT('\0')) { bFoundItem = TRUE; StrCpy(szItemName, SZRANGES); StrCat(szItemName, pItem->szName); } else { bFoundItem = FALSE; pItem = NULL; } // Are we trying to do an add or a delete. if (dwFlags & SZM_DELETE) { // If we have a valid "named" entry in the registry delete it now. if (bFoundItem) { TransAssert(pItem != NULL); CRegKey regItem; if ((dwError = regItem.Open(m_regZoneMap, szItemName, KEY_READ | KEY_WRITE)) != ERROR_SUCCESS) { hr = HRESULT_FROM_WIN32(dwError); } else { // Get the protocol name and delete the protocol related value. if ((dwError = regItem.DeleteValue(pzc->pszProtocol)) == ERROR_SUCCESS) { DWORD dwNumValues = 0; // Is this the last entry for this range? If so delete the range & nuke the key. if (ERROR_SUCCESS == regItem.QuerySubKeyInfo(NULL, NULL, &dwNumValues) && dwNumValues == 1 && ERROR_SUCCESS == regItem.DeleteValue(SZRANGE) ) { regItem.Close(); m_regZoneMap.DeleteEmptyKey(szItemName); } } else { hr = HRESULT_FROM_WIN32(dwError); } } } else { hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); } } else { if (bFoundItem) { TransAssert(pItem != NULL); // See if an entry with the given name already CRegKey regItem; if ((dwError = regItem.Open(m_regZoneMap, szItemName, KEY_READ | KEY_WRITE)) != ERROR_SUCCESS) { hr = HRESULT_FROM_WIN32(dwError); } else { DWORD dwZoneExists; // If we were able to read the value, fail because entry already exists. if (regItem.QueryValue(&dwZoneExists, pzc->pszProtocol) == ERROR_SUCCESS) { hr = HRESULT_FROM_WIN32(ERROR_FILE_EXISTS); } } } else { // Create the new item name. StrCpy(szItemName, SZRANGES); StrCat(szItemName, SZRANGEPREFIX); if (!DwToWchar(s_dwNextRangeIndex, szItemName + lstrlen(SZRANGES) + lstrlen(SZRANGEPREFIX), 10)) { TransAssert(FALSE); hr = E_UNEXPECTED; } } // Okay to go ahead and create the entry. if (SUCCEEDED(hr)) { TCHAR szIPRule[MAX_IPRANGE]; // We shouldn't have any domain part for IP Rules. TransAssert(pzc->pszDomain == NULL); memcpy(szIPRule, pzc->pszSite, sizeof(TCHAR) * pzc->cchSite); szIPRule[pzc->cchSite] = TEXT('\0'); CRegKey regMap; // Now add the entry to the registry. if (((dwError = regMap.Create(m_regZoneMap, szItemName, KEY_WRITE)) == ERROR_SUCCESS) && ((dwError = regMap.SetValue(dwZone, pzc->pszProtocol)) == ERROR_SUCCESS) && ((dwError = regMap.SetValue(szIPRule, SZRANGE)) == ERROR_SUCCESS) ) { hr = S_OK; } else { hr = HRESULT_FROM_WIN32(dwError); } } } if (SUCCEEDED(hr)) { hr = ReadAllIPRules(); } LeaveCriticalSection(&s_csectIP); return hr; } HRESULT CSecurityManager::CheckAddressAgainstRanges (ZONEMAP_COMPONENTS* pzc, DWORD* pdwZone, LPCTSTR pszProt) { TCHAR szKeyName[MAX_PATH]; const DWORD cchRanges = CSTRLENW(SZRANGES); memcpy(szKeyName, SZRANGES, sizeof(TCHAR) * cchRanges); CRegKey regItem; EnterCriticalSection(&s_csectIP); RANGE_ITEM* pItem = (RANGE_ITEM*)s_pRanges; for (DWORD iRange = 0; iRange < s_cNumRanges; iRange++) { for (DWORD iByte = 0; iByte < 4; iByte++) { if (pzc->bAddr[iByte] < pItem->bLow[iByte] || pzc->bAddr[iByte] > pItem->bHigh[iByte] ) { goto next_range; // much cleaner than a break and test } } StrCpyW(szKeyName + cchRanges, pItem->szName); if (ERROR_SUCCESS == regItem.Open(m_regZoneMap, szKeyName, KEY_READ) && ERROR_SUCCESS == regItem.QueryValueOrWild(pdwZone, pszProt) ) { LeaveCriticalSection(&s_csectIP); return S_OK; } next_range: pItem = (RANGE_ITEM*)(((LPBYTE)pItem) + s_cbRangeItem); } LeaveCriticalSection(&s_csectIP); return S_FALSE; } HRESULT CSecurityManager::CheckSiteAndDomainMappings (ZONEMAP_COMPONENTS* pzc, DWORD* pdwZone, LPCTSTR pszProt) { CRegKey regDomain, regSite; DWORD dwRegErr; TCHAR szKeyName[MAX_PATH]; const DWORD cchKeyName = CSTRLENW(SZDOMAINS); memcpy(szKeyName, SZDOMAINS, sizeof(TCHAR) * cchKeyName); TransAssert(!pzc->fDrive); if (pzc->pszDomain) { // First, look for domain rule. memcpy(szKeyName + cchKeyName, pzc->pszDomain, sizeof(TCHAR) * pzc->cchDomain); szKeyName[cchKeyName + pzc->cchDomain] = 0; if (ERROR_SUCCESS != regDomain.Open(m_regZoneMap, szKeyName, KEY_READ)) return S_FALSE; // Now add the site. memcpy(szKeyName, pzc->pszSite, sizeof(TCHAR) * pzc->cchSite); szKeyName[pzc->cchSite] = 0; dwRegErr = regSite.Open(regDomain, szKeyName, KEY_READ); // For IE5.0 we support wildcard's beyond the second level domain. // For example if you had an intranet address www.internal.mycorp.com // you can specify a zone mapping for *.internal.mycorp.com. // In IE4 we would have flagged this as an error because we allowed // wildcards only at the second level domain. // IE5 since we lifted this restriction, we have to search the sub-keys // and look for strings such as "*.internal" under the mycorp.com key. // If we find one we see if the wildcard pattern matches the site whose // zone we are trying to determine. if (dwRegErr != ERROR_SUCCESS) { TCHAR rgchSubKeyName[MAX_PATH]; LONG lRet = NOERROR; for (DWORD dwIndex = 0, cchSubKey = ARRAYSIZE(rgchSubKeyName); ((lRet = regDomain.EnumKey(dwIndex, rgchSubKeyName, &cchSubKey)) != ERROR_NO_MORE_ITEMS); dwIndex++, cchSubKey = ARRAYSIZE(rgchSubKeyName) ) { if (lRet != NOERROR) { TransAssert(lRet != ERROR_MORE_DATA); break; } // For patterns that finish with a *. we will do a suffix // match to see if the wildcard sequence is valid. if (cchSubKey > 2 && rgchSubKeyName[0] == WILDCARD && rgchSubKeyName[1] == DOT) { // First condition // for xyz.foo.microsoft.com to match *.foo.microsoft.com // www.foo has to be greater than or equal to foo.microsoft.com // note that we allow just foo.microsoft.com as well. // Second condition // cchSubkey is the length of *.foo, therefore the last // cchSubKey - 2 characters of the two strings should match. if (pzc->cchSite >= (cchSubKey - 2) && (StrCmpNI(rgchSubKeyName + 2, /* skip *. */ pzc->pszSite + pzc->cchSite - cchSubKey + 2, cchSubKey - 2 ) == 0 ) ) { dwRegErr = regSite.Open(regDomain, rgchSubKeyName, KEY_READ); break; } } } } } else { // There was no domain. Look for a site rule. memcpy(szKeyName + cchKeyName, pzc->pszSite, sizeof(TCHAR) * pzc->cchSite); szKeyName[cchKeyName + pzc->cchSite] = 0; dwRegErr = regSite.Open(m_regZoneMap, szKeyName, KEY_READ); } // Look for matching protocols under site key. if (ERROR_SUCCESS == dwRegErr && ERROR_SUCCESS == regSite.QueryValueOrWild(pdwZone, pszProt) ) { return S_OK; } // Now fall back to domain if there was one. else if (pzc->pszDomain && ERROR_SUCCESS == regDomain.QueryValueOrWild(pdwZone, pszProt) ) { return S_OK; } else return S_FALSE; } HRESULT CSecurityManager::CheckUNCAsIntranet (ZONEMAP_COMPONENTS* pzc, DWORD* pdwZone, LPCTSTR pszProt) { HRESULT hr = S_FALSE; DWORD dwUNCAsIntranet; TransAssert(!pzc->fDrive); TransAssert(!pzc->fIPRange); if (pzc->fAddr || pzc->nScheme != URL_SCHEME_FILE) return hr; if (ERROR_SUCCESS != m_regZoneMap.QueryValue(&dwUNCAsIntranet, SZUNCASINTRANET)) return hr; if (dwUNCAsIntranet == 0) { hr = S_OK; if (pdwZone) *pdwZone = URLZONE_INTERNET; } return hr; } HRESULT CSecurityManager::CheckIntranetName (ZONEMAP_COMPONENTS* pzc, DWORD* pdwZone, LPCTSTR pszProt) { HRESULT hr = S_FALSE; DWORD dwZone = ZONEID_INVALID; TransAssert(!pzc->fDrive); TransAssert(!pzc->fIPRange); if (pzc->fAddr) return hr; // Check if there is a local intranet rule. if (ERROR_SUCCESS != m_regZoneMap.QueryValue(&dwZone, SZINTRANETNAME)) return hr; if (dwZone != URLZONE_INTRANET) { TransAssert(FALSE); dwZone = URLZONE_INTRANET; } if (pzc->pszSite && !pzc->pszDomain) { BOOL bFoundDot = FALSE; for (DWORD dwIndex = 0; dwIndex < pzc->cchSite; dwIndex++) { if (pzc->pszSite[dwIndex] == DOT) { bFoundDot = TRUE; break; } } hr = bFoundDot ? S_FALSE : S_OK; } if (hr == S_OK && pdwZone) *pdwZone = dwZone; return hr; } HRESULT CSecurityManager::CheckProxyBypassRule (ZONEMAP_COMPONENTS* pzc, DWORD* pdwZone, LPCTSTR pszProt) { TransAssert(!pzc->fDrive); // Check if there is a proxy bypass rule. if (ERROR_SUCCESS != m_regZoneMap.QueryValue(pdwZone, SZPROXYBYPASS)) return S_FALSE; // Calculate length of hostname = site (+ . + domain) DWORD cchTotal; cchTotal = pzc->cchSite; if (pzc->cchDomain) cchTotal += 1 + pzc->cchDomain; // Convert from unicode to ansi. char szHost[MAX_PATH]; DWORD cbHost; cbHost = WideCharToMultiByte(CP_ACP, 0, pzc->pszSite, cchTotal, szHost, sizeof(szHost), NULL, NULL); if (!cbHost) return S_FALSE; // WideCharToMultiByte won't null terminate szHost, // IsHostInProxyBypassList shouldn't need it, // but just do it anyway to play it safe. szHost[cbHost] = 0; INTERNET_SCHEME tScheme; BOOL bCheckByPassRules = TRUE; switch (pzc->nScheme) { case URL_SCHEME_HTTP: tScheme = INTERNET_SCHEME_HTTP; break; case URL_SCHEME_HTTPS: tScheme = INTERNET_SCHEME_HTTPS; break; case URL_SCHEME_GOPHER: tScheme = INTERNET_SCHEME_GOPHER; break; case URL_SCHEME_FTP: tScheme = INTERNET_SCHEME_FTP; break; default: bCheckByPassRules = FALSE; break; } return bCheckByPassRules && IsHostInProxyBypassList(tScheme, szHost, cbHost) ? S_OK : S_FALSE; } HRESULT CSecurityManager::CheckMKURL (ZONEMAP_COMPONENTS* pzc, DWORD* pdwZone, LPCTSTR pszProt) { HRESULT hr = S_FALSE; DWORD dwZone = ZONEID_INVALID; TransAssert(!pzc->fDrive); // First check if it looks like a valid mk: string. if (pzc->nScheme == URL_SCHEME_MK && pzc->pszDomain == NULL && pzc->pszSite != NULL && pzc->pszSite[0] == AT) { // look for a : in the domain string. LPTSTR pszColon = StrChr(pzc->pszSite, COLON); if (pszColon != NULL) { CRegKey regProtocols; *pszColon = TEXT('\0'); // Temporarily overwrite the colon. if ((ERROR_SUCCESS == regProtocols.Open(m_regZoneMap, SZPROTOCOLS, KEY_READ)) && (ERROR_SUCCESS == regProtocols.QueryValue(&dwZone, pzc->pszSite)) ) { *pdwZone = dwZone; hr = S_OK; } *pszColon = COLON; // Set the domain string back to its original state. } } return hr; } CSecurityManager::CSecMgrCache::CSecMgrCache(void) { InitializeCriticalSection(&m_csectZoneCache); // single static object, so this only gets inited once per // process. s_hMutexCounter = CreateMutexA(NULL, FALSE, "ZonesCounterMutex"); m_iAdd = 0; } CSecurityManager::CSecMgrCache::~CSecMgrCache(void) { Flush(); DeleteCriticalSection(&m_csectZoneCache); CloseHandle(s_hMutexCounter); } BOOL CSecurityManager::CSecMgrCache::Lookup(LPCWSTR pwszURL, DWORD* pdwZone, BOOL* pfMarked, BYTE* pbSecurityID, DWORD* pcbSecurityID, LPCWSTR pwszDocDomain) { BOOL fFound = FALSE; EnterCriticalSection(&m_csectZoneCache); if (!IsCounterEqual()) { Flush(); } else { int i; fFound = FindCacheEntry(pwszURL, i); if (fFound) { if (pbSecurityID) { TransAssert(pcbSecurityID); if (m_asmce[i].m_pbSecurityID && ((m_asmce[i].m_pwszDocDomain == NULL && pwszDocDomain == NULL) || /* both are NULL */ (m_asmce[i].m_pwszDocDomain && pwszDocDomain && (0 == StrCmpW(m_asmce[i].m_pwszDocDomain, pwszDocDomain)) /* the strings match */ ) ) && m_asmce[i].m_cbSecurityID <= *pcbSecurityID) { memcpy(pbSecurityID, m_asmce[i].m_pbSecurityID, m_asmce[i].m_cbSecurityID); *pcbSecurityID = m_asmce[i].m_cbSecurityID; } else *pcbSecurityID = 0; } if (pdwZone) { *pdwZone = m_asmce[i].m_dwZone; if (pfMarked) *pfMarked = m_asmce[i].m_fMarked; } } } LeaveCriticalSection(&m_csectZoneCache); return fFound; } void CSecurityManager::CSecMgrCache::Add(LPCWSTR pwszURL, DWORD dwZone, BOOL fMarked, const BYTE* pbSecurityID, const DWORD cbSecurityID, LPCWSTR pwszDocDomain) { int i; BOOL fFound; EnterCriticalSection(&m_csectZoneCache); if (!IsCounterEqual()) Flush(); fFound = FindCacheEntry(pwszURL, i); // found or not, i will be the right place to set it. m_asmce[i].Set(pwszURL, dwZone, fMarked, pbSecurityID, cbSecurityID, pwszDocDomain); if (!fFound) m_iAdd = (m_iAdd + 1) % MAX_SEC_MGR_CACHE; SetToCurrentCounter(); // validate this cache. LeaveCriticalSection(&m_csectZoneCache); } void CSecurityManager::CSecMgrCache::Flush(void) { int i; EnterCriticalSection(&m_csectZoneCache); for (i = 0; i < MAX_SEC_MGR_CACHE; i++) m_asmce[i].Flush(); m_iAdd = 0; LeaveCriticalSection(&m_csectZoneCache); } // Is the counter we saved with the cache entry, equal to the current counter. BOOL CSecurityManager::CSecMgrCache::IsCounterEqual() const { CExclusiveLock lock(s_hMutexCounter); LPDWORD lpdwCounter = (LPDWORD)g_SharedMem.GetPtr(SM_SECMGRCHANGE_COUNTER); // If we couldn't create the shared memory for some reason, we just assume our cache is up to date. if (lpdwCounter == NULL) return TRUE; return (m_dwPrevCounter == *lpdwCounter); } VOID CSecurityManager::CSecMgrCache::SetToCurrentCounter() { CExclusiveLock lock(s_hMutexCounter); LPDWORD lpdwCounter = (LPDWORD)g_SharedMem.GetPtr(SM_SECMGRCHANGE_COUNTER); if (lpdwCounter == NULL) return; m_dwPrevCounter = *lpdwCounter; } VOID CSecurityManager::CSecMgrCache::IncrementGlobalCounter() { CExclusiveLock lock(s_hMutexCounter); LPDWORD lpdwCounter = (LPDWORD)g_SharedMem.GetPtr(SM_SECMGRCHANGE_COUNTER); if (lpdwCounter == NULL) return; (*lpdwCounter)++; } BOOL CSecurityManager::CSecMgrCache::FindCacheEntry(LPCWSTR pwszURL, int& riEntry) { BOOL fFound = FALSE; riEntry = m_iAdd - 1 % MAX_SEC_MGR_CACHE; // our cache is a circular buffer. We scan it from the last entry // we added backwards to the next slot to add to, createing a quasi- // MRU. if (riEntry < 0) riEntry = MAX_SEC_MGR_CACHE + riEntry; // check below us, starting with the most recent addition, if any. for (; riEntry >= 0; riEntry--) { if (m_asmce[riEntry].m_pwszURL && StrCmpW(m_asmce[riEntry].m_pwszURL, pwszURL) == 0) { fFound = TRUE; break; } } if (!fFound) { for (riEntry = MAX_SEC_MGR_CACHE - 1; riEntry >= m_iAdd; riEntry--) { if (m_asmce[riEntry].m_pwszURL == NULL) break; // hasn't been used yet. else if (m_asmce[riEntry].m_pwszURL && StrCmpW(m_asmce[riEntry].m_pwszURL, pwszURL) == 0) { fFound = TRUE; break; } } } if (!fFound) riEntry = m_iAdd; return fFound; } void CSecurityManager::CSecMgrCache::CSecMgrCacheEntry::Set(LPCWSTR pwszURL, DWORD dwZone, BOOL fMarked, const BYTE* pbSecurityID, DWORD cbSecurityID, LPCWSTR pwszDocDomain) { if (pwszURL) { // Only replace if the string has changed. // We may see the same string if the entry is // set by MapUrlToZone before GetSecurityID is called. if (m_pwszURL && StrCmpW(pwszURL, m_pwszURL)) { delete[] m_pwszURL; m_pwszURL = NULL; } if (!m_pwszURL) { int cchURL = lstrlenW(pwszURL); m_pwszURL = new WCHAR[cchURL + 1]; if (m_pwszURL) StrCpyW(m_pwszURL, pwszURL); else return; } } // We always set the url zone mark first, then come back later and // add the security ID, than means that on any set operation, we're // either changing the url or adding the security ID. Either way, if // we have a security ID, its invalid now, so flush it. if (m_pbSecurityID) { delete[] m_pbSecurityID; m_pbSecurityID = NULL; m_cbSecurityID = 0; } if (m_pwszDocDomain) { delete[] m_pwszDocDomain; m_pwszDocDomain = NULL; } if (pbSecurityID) { m_pbSecurityID = new BYTE[cbSecurityID]; if (m_pbSecurityID) { memcpy(m_pbSecurityID, pbSecurityID, cbSecurityID); if (pwszDocDomain) { m_pwszDocDomain = new WCHAR[lstrlenW(pwszDocDomain) + 1]; if (m_pwszDocDomain != NULL) { StrCpyW(m_pwszDocDomain, pwszDocDomain); } else { // If we don't have memory for the Document's domain property // we better not remember the security ID either. delete[] m_pbSecurityID; m_pbSecurityID = NULL; cbSecurityID = 0; } } } else { cbSecurityID = 0; } m_cbSecurityID = cbSecurityID; } if (dwZone != URLZONE_INVALID) { m_dwZone = dwZone; m_fMarked = fMarked; } } void CSecurityManager::CSecMgrCache::CSecMgrCacheEntry::Flush(void) { if (m_pwszURL) delete[] m_pwszURL; m_pwszURL = NULL; if (m_pbSecurityID) delete[] m_pbSecurityID; m_pbSecurityID = NULL; m_cbSecurityID = 0; if (m_pwszDocDomain) { delete[] m_pwszDocDomain; m_pwszDocDomain = NULL; } m_dwZone = URLZONE_INVALID; m_fMarked = FALSE; } BOOL CSecurityManager::EnsureListReady(BOOL bForce) // Make sure the list of allowed controls is ready // Returns whether or not the list had to be made // bForce is whether to force a reinitialization { if (CSecurityManager::s_clsidAllowedList == NULL || bForce == TRUE) { CSecurityManager::IntializeAllowedControls(); return TRUE; } else return FALSE; } void CSecurityManager::IntializeAllowedControls() { DWORD i = 0; DWORD dwNumKeys = 0; DWORD dwMaxLen = 0; DWORD dwNumValues = 0; // this buffer size should be long enough to hold a string-form // CLSID, plus the two end braces, plus a null terminator TCHAR szValueName[40]; DWORD dwNameLength = 40; DWORD dwType = 0; DWORD dwData = 0; DWORD dwDataLength = sizeof(DWORD); // In case we somehow get multiply initialized if (CSecurityManager::s_clsidAllowedList != NULL) { delete[] CSecurityManager::s_clsidAllowedList; CSecurityManager::s_clsidAllowedList = NULL; } CSecurityManager::s_dwNumAllowedControls = 0; //open key // look at HKLM only, first CRegKey* prkey_AllowedControls; CRegKey rkey_AllowedControls(TRUE); CRegKey rkey_AllowedControlsCU(FALSE); LONG lRes = rkey_AllowedControls.Open(NULL, ALLOWED_CONTROLS_KEY, KEY_READ); if (lRes != ERROR_SUCCESS) { // List not found in HKLM, check HKCU lRes = rkey_AllowedControlsCU.Open(NULL, ALLOWED_CONTROLS_KEY, KEY_READ); if (lRes != ERROR_SUCCESS) { // AllowedControls Key not able to be opened return; } else { prkey_AllowedControls = &rkey_AllowedControlsCU; } } else { prkey_AllowedControls = &rkey_AllowedControls; } lRes = prkey_AllowedControls->QuerySubKeyInfo(&dwNumKeys, &dwMaxLen, &dwNumValues); if (lRes != ERROR_SUCCESS) return; // prepare space in data structure // array will not need to be resized, since the maximum number of allowed // CLSIDs is the number of values in the key CSecurityManager::s_clsidAllowedList = new CLSID[dwNumValues]; if (CSecurityManager::s_clsidAllowedList == NULL) // new failed return; // loop through all values in the key for (i = 0; i < dwNumValues; i++) { // at every loop, these values get changed and must be reset to the // length of the name and data buffers, respectively dwNameLength = ARRAYSIZE(szValueName); dwDataLength = sizeof(DWORD); // Get the (DWORD) value for the current value name LONG lResult = prkey_AllowedControls->EnumValue(i, szValueName, &dwNameLength, &dwType, &dwData, &dwDataLength); if (lResult == ERROR_SUCCESS && dwType == REG_DWORD && GetUrlPolicyPermissions(dwData) == URLPOLICY_ALLOW) { // found a value for the CLSID given, and it is set to allow the CLSID // add the CLSID to the list CLSID* p_id = CSecurityManager::s_clsidAllowedList + //pointer + CSecurityManager::s_dwNumAllowedControls;//offset HRESULT hr = CLSIDFromString(szValueName, p_id); if (hr != NOERROR) continue; CSecurityManager::s_dwNumAllowedControls++; } } } HRESULT CSecurityManager::GetControlPermissions(BYTE* raw_CLSID, DWORD& dwPerm) { CLSID* id = (CLSID*)(raw_CLSID); dwPerm = 0; // If the list is not initialized (something's wrong) leave function if (CSecurityManager::s_clsidAllowedList == NULL) { return E_UNEXPECTED; } DWORD index = 0; // Search for the given CLSID in the list of allowed Controls for (index = 0; index < CSecurityManager::s_dwNumAllowedControls; index++) { if (*id == (CSecurityManager::s_clsidAllowedList[index])) { dwPerm = URLPOLICY_ALLOW; // not necesarry, since currently only allowed controls // are in the list, but this may change later return S_OK; } } // Not found, return false to indicate not in list return S_FALSE; } HRESULT CSecurityManager::GetActiveXRunPermissions(BYTE* raw_CLSID, DWORD& dwPerm) { HRESULT hr = S_FALSE; DWORD dwValue; EnterCriticalSection(&s_csectAList); // Initialize the Allowed Controls list if it is not already CSecurityManager::EnsureListReady(FALSE); // get the list permission for pContext, if it is in the list HRESULT permHR = CSecurityManager::GetControlPermissions(raw_CLSID, dwValue); LeaveCriticalSection(&s_csectAList); // interpret results, (zone dependent interpretation not yet implemented) if (SUCCEEDED(permHR)) { if (permHR == S_OK) // found in list { if (dwValue == URLPOLICY_ALLOW) { hr = S_OK; dwPerm = URLPOLICY_ALLOW; } else { hr = S_FALSE; dwPerm = URLPOLICY_DISALLOW; } } else // not in list; default is to disallow { hr = S_FALSE; dwPerm = URLPOLICY_DISALLOW; } } else // Unknown error. Disallow by default { hr = S_FALSE; dwPerm = URLPOLICY_DISALLOW; } return hr; }