Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

4788 lines
148 KiB

// 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
#include <commctrl.h>
#include "shfusion.h"
#include "advpub.h"
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('%')
extern const LPWSTR RtlpWin32NtRoot = L"\\\\?";
extern const LPWSTR RtlpWin32NtUncRoot = L"\\\\?\\UNC";
#define MAX_IPRANGE 32
extern BOOL g_bUseImprovedZoneCheck;
extern IInternetSecurityManager* g_pSecurityManager;
HRESULT EnsureSecurityManager ();
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)
#define SAFEREGOPERATION(dw, r) \
dw = r; \
if(dw == ERROR_KEY_DELETED) \
{\
dw = m_regZoneMap.Open (NULL, SZZONEMAP, KEY_READ); \
if( dw == ERROR_SUCCESS) \
dw = r; \
}
#ifndef ARRAY_ELEMENTS
#define ARRAY_ELEMENTS(array) \
(sizeof(array)/sizeof((array)[0]))
#endif
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;
LPCTSTR pszSiteUrl = pszScan;
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
if(nScheme == URL_SCHEME_FILE)
{
const TCHAR UncRoot1[] = TEXT("\\\\?\\");
const TCHAR UncRoot2[] = TEXT("\\\\.\\");
const TCHAR szUnc[] = TEXT("UNC\\");
if (0 == StrCmpNICW(pszScan, UncRoot1, ARRAYSIZE(UncRoot1)-1) ||
0 == StrCmpNICW(pszScan, UncRoot2, ARRAYSIZE(UncRoot2)-1))
{
// If the url starts with \\?\ or \\.\ .
pszScan += ARRAYSIZE(UncRoot1) - 1;
// First check if it is in the form of \\?\c:. If it is, don't move pszScan
if (pszScan[0] == 0)
return E_INVALIDARG;
else if (pszScan[1] != TEXT(':'))
{
if (0 == StrCmpNICW(pszScan, szUnc, ARRAYSIZE(szUnc) - 1))
pszScan += ARRAYSIZE(szUnc) - 1;
else
return E_INVALIDARG;
}
}
else
{
DWORD dwSize = ARRAYSIZE(szSiteBuf);
HRESULT hr = PathCreateFromUrl( pszSiteUrl, szSiteBuf, &dwSize, NULL);
if(SUCCEEDED(hr))
{
pszScan = szSiteBuf;
}
}
}
// 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;
}
// Windows Bug# 553626:
// Note: IP address can be expressed in octal or hex, but we don't support them:
if(cchSite < ( sizeof(szIPAddr) / sizeof(TCHAR) ))
{
StrCpyNW(szIPAddr, pszSite, cchSite + 1);
// Check for names that are form of an IP address.
fAddr = ReadAddress (szIPAddr, 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;
m_fHardened = IEHardened();
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;
}
HRESULT ReduceUrl(LPWSTR pwszSecUrl)
{
HRESULT hr = S_OK;
// Reduce the URL here. Need to do this up to two times.
for (int i = 1; i <= 2; i++)
{
if (StrChr(pwszSecUrl, PERCENT))
{
DWORD dwSecUrl = lstrlenW(pwszSecUrl) + 1;
UrlUnescapeW(pwszSecUrl, NULL, 0, URL_UNESCAPE_INPLACE);
if (g_bUseImprovedZoneCheck
&& (i == 1)
&& (!StrCmpNIW(pwszSecUrl, L"http:", 5)
|| !StrCmpNIW(pwszSecUrl, L"https:", 6)))
{
//For Windows 546559
//Imitate wininet behavior
DWORD cchIn = dwSecUrl +2; //two for the "//"
LPWSTR pwzTemp = new WCHAR[cchIn];
if (!pwzTemp)
{
hr = E_OUTOFMEMORY;
goto quit;
}
CFreeStrPtr freeStr(pwzTemp);
WCHAR ch;
int j=0;
do
{
ch = (pwzTemp[j] = pwszSecUrl[j]);
++j;
}
while (ch != L':');
if (StrCmpNIW(pwszSecUrl+j, L"//", 2))
StrCpyNW(pwzTemp+j, L"//", cchIn-j);
else
pwzTemp[j] = L'\0';
StrCatBuffW(pwzTemp, pwszSecUrl+j, cchIn);
HRESULT hrLocal = UrlGetPartW(pwzTemp, pwszSecUrl, &dwSecUrl, URL_PART_HOSTNAME, URL_PARTFLAG_KEEPSCHEME);
DEBUG_PRINT(19,
INFO,
("called UrlGetPartW %.100wq, %.100wq, hrLocal=0x%x\n",
pwzTemp, pwszSecUrl, hrLocal
));
if (FAILED(hrLocal))
{
hr = hrLocal;
goto quit;
}
}
}
else
break;
}
quit:
return hr;
}
HRESULT CSecurityManager::WrapMapUrlToZone(LPCWSTR pwszSecUrl, ZONEMAP_COMPONENTS* pzc, DWORD* pdwZone, DWORD dwFlags,
BOOL *pfMarked, LPWSTR *ppszMarkURL)
{
DEBUG_ENTER((DBG_API,
Hresult,
"CSecurityManager::WrapMapUrlToZone",
"%.100wq, %#x, *pdwZone=%#x, dwFlags=%#x",
pwszSecUrl, (pdwZone ? *pdwZone: 0), dwFlags
));
HRESULT hr = S_OK;
TransAssert (hr == S_OK);
if (g_bUseImprovedZoneCheck
&& StrChr(pwszSecUrl, PERCENT)
&& (!StrCmpNIW(pwszSecUrl, L"http:", 5)
|| !StrCmpNIW(pwszSecUrl, L"https:", 6)))
{
//NOTICE: Windows 555438
//Find the zone of twice unescaped url
//to prevent zone inconsistencies,
//between this and MapUrlToZone codepath
ZONEMAP_COMPONENTS zc2;
LPWSTR pwszRedSecUrl;
DWORD dwSecUrl = lstrlenW(pwszSecUrl);
pwszRedSecUrl = new WCHAR[dwSecUrl+1];
StrCpyW(pwszRedSecUrl, pwszSecUrl);
if (!pwszRedSecUrl)
{
DEBUG_LEAVE(E_OUTOFMEMORY);
return E_OUTOFMEMORY;
}
CFreeStrPtr freeStr(pwszRedSecUrl);
hr = ReduceUrl(pwszRedSecUrl);
if (FAILED(hr))
{
DEBUG_LEAVE(hr);
return hr;
}
hr = zc2.Crack(pwszRedSecUrl, dwFlags);
if (FAILED(hr))
{
//consistent with behavior in GetSecurityId.
DEBUG_LEAVE(E_INVALIDARG);
return E_INVALIDARG;
}
hr = MapUrlToZone (&zc2, pdwZone, dwFlags, pfMarked, ppszMarkURL);
TransAssert (hr == S_OK);
}
else
{
hr = MapUrlToZone (pzc, pdwZone, dwFlags, pfMarked, ppszMarkURL);
TransAssert (hr == S_OK);
}
DEBUG_LEAVE(((hr == S_OK) ? *pdwZone : hr));
return hr;
}
// 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
)
{
DEBUG_ENTER((DBG_TRANS,
Hresult,
"CSecurityManager::MapUrlToZone",
"%.100wq, %#x, *pdwZone=%#x, dwFlags=%#x",
pwszUrl, (pdwZone ? *pdwZone: 0), 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 (dwFlags & MUTZ_ENFORCERESTRICTED)
{
*pdwZone = URLZONE_UNTRUSTED;
hr = S_OK;
}
else if (!s_smcache.Lookup(pwszUrl, pdwZone))
{
hr = CoInternetGetSecurityUrl(pwszUrl, &pwszSecUrl, PSU_DEFAULT, 0);
if (SUCCEEDED(hr))
{
TransAssert(pwszSecUrl);
CFreeStrPtr freeStr(pwszSecUrl);
hr = ReduceUrl(pwszSecUrl);
if (FAILED(hr))
{
goto quit;
}
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;
}
}
quit:
PerfDbgLog1(tagCSecurityManager, this, "-CSecurityManager::MapUrlToZone (hr:%lx)", hr);
DEBUG_LEAVE( (SUCCEEDED(hr) ? *pdwZone : 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
)
{
DEBUG_ENTER((DBG_API,
Hresult,
"CSecurityManager::GetSecurityId",
"%.100wq",
pwszUrl
));
PerfDbgLog(tagCSecurityManager, this, "Called CSecurityManager::GetSecurityId");
HRESULT hr = INET_E_DEFAULT_ACTION;
DWORD dwZone;
// Check args ...
if (pwszUrl == NULL || !pwszUrl[0] || !pcbSecurityId )
{
DEBUG_LEAVE(E_INVALIDARG);
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 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))
{
DEBUG_LEAVE(E_INVALIDARG);
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 = WrapMapUrlToZone (pwszSecUrl, &zc, &dwZone, 0, &fMarked, &pwszMarkURL);
if (hr != S_OK)
{
DEBUG_LEAVE(hr);
return hr;
}
// 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);
DEBUG_LEAVE(E_INVALIDARG);
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)
{
hr = HRESULT_FROM_WIN32(GetLastError());
DEBUG_LEAVE(hr);
return hr;
}
}
DWORD cbRequired = zc.cchProtocol + 1 + cbSite + cbTail;
if (*pcbSecurityId < cbRequired)
{
*pcbSecurityId = cbRequired;
hr = HRESULT_FROM_WIN32 (ERROR_INSUFFICIENT_BUFFER);
DEBUG_LEAVE(hr);
return hr;
}
// 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)
{
DEBUG_LEAVE(E_INVALIDARG);
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
DEBUG_LEAVE(((hr == S_OK)? dwZone : hr));
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_HTML_META_REFRESH, IDS_ACTION_HTML_META_REFRESH },
{ URLACTION_HTML_MIXED_CONTENT, IDS_ACTION_HTML_MIXED_CONTENT },
{ 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 },
{ URLACTION_CLIENT_CERT_PROMPT, IDS_ACTION_NW_CLIENT_CERT_PROMPT },
{ URLACTION_COOKIES_THIRD_PARTY, IDS_ACTION_NW_COOKIES_THIRD_PARTY },
{ URLACTION_COOKIES_SESSION_THIRD_PARTY, IDS_ACTION_NW_COOKIES_SESSION_THIRD_PARTY },
};
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;
}
BOOL
CSecurityManager::IsScriptOrActiveXHardenedInternet(DWORD dwAction, DWORD dwPermissions, DWORD dwZone)
{
if
(
IEHardened() &&
(dwAction == URLACTION_ACTIVEX_RUN || dwAction == URLACTION_SCRIPT_RUN) &&
dwPermissions == URLPOLICY_DISALLOW &&
dwZone == URLZONE_INTERNET
)
{
// check if trust dialog enabled:
DWORD dwValue = TRUE; // default value of TRUSTDLG is TRUE
HKEY hKeyIE = 0;
// For applications, check only machine key:
if (RegOpenKeyEx(IsBrowserHosted()? HKEY_CURRENT_USER: HKEY_LOCAL_MACHINE,
REGSTR_PATH_IE_MAIN, 0, KEY_READ, &hKeyIE) == ERROR_SUCCESS)
{
DWORD dwLen = sizeof(dwValue);
RegQueryValueEx(hKeyIE, REGVAL_TRUSTDLG_ENABLED, NULL,
NULL, (LPBYTE)&dwValue, &dwLen);
RegCloseKey(hKeyIE);
}
return dwValue;
}
return FALSE;
}
BOOL
CSecurityManager::IsSiteInZone(LPCWSTR pszUrl, DWORD dwZone, IInternetSecurityManager *pSecMgr)
{
BOOL fRet = FALSE;
DWORD dwMappedZone;
if (pSecMgr && SUCCEEDED(pSecMgr->MapUrlToZone(pszUrl, &dwMappedZone, 0)))
{
fRet = (dwZone == dwMappedZone);
}
return fRet;
}
BOOL
CSecurityManager::ShowAddToSitesList(HWND hwnd, LPCWSTR pszUrl, DWORD dwZone)
{
BOOL fRet = FALSE;
HMODULE hmod = LoadLibrary(TEXT("inetcpl.cpl"));
if(hmod)
{
BOOL (*pfnAddSitesDlg)(HWND, DWORD, LPCWSTR) =
(BOOL (*)(HWND, DWORD, LPCWSTR))GetProcAddress(hmod, "LaunchAddSitesDialog");
if(pfnAddSitesDlg)
{
fRet = pfnAddSitesDlg(hwnd, dwZone, pszUrl);
}
FreeLibrary(hmod);
}
return fRet;
}
BOOL
GetAddSitesDisplayUrl(LPCWSTR pszUrl, LPWSTR pszUrlDisplay, DWORD cchUrlDisplay)
{
BOOL fRet = FALSE;
HMODULE hmod = LoadLibrary(TEXT("inetcpl.cpl"));
if(hmod)
{
BOOL (*pfnGetAddSitesDisplayUrl)(LPCWSTR, LPWSTR, DWORD) =
(BOOL (*)(LPCWSTR, LPWSTR, DWORD))GetProcAddress(hmod, "GetAddSitesDisplayUrl");
if(pfnGetAddSitesDisplayUrl)
{
fRet = pfnGetAddSitesDisplayUrl(pszUrl, pszUrlDisplay, cchUrlDisplay);
}
FreeLibrary(hmod);
}
return fRet;
}
// Encapsulate common functionality:
HWND CSecurityManager::SetControlText(HWND hDlg, UINT ctrlID, DWORD dwStrId, LPCWSTR pszDomain)
{
WCHAR rgszAlert[MAX_ALERT_SIZE];
if (::LoadStringWrapW(g_hInst, dwStrId, rgszAlert, MAX_ALERT_SIZE) == 0)
{
TransAssert(FALSE);
::LoadStringWrapW(g_hInst, IDS_ACTION_UNKNOWN, rgszAlert, MAX_ALERT_SIZE);
}
if(pszDomain != NULL)
{
WCHAR rgszAlert2[MAX_ALERT_SIZE];
wcsncpy(rgszAlert2, rgszAlert, ARRAYSIZE(rgszAlert2) - 1);
wnsprintf(rgszAlert, ARRAYSIZE(rgszAlert) - 1, rgszAlert2, pszDomain);
}
HWND hwndAlertText = ::GetDlgItem(hDlg, ctrlID);
TransAssert(hwndAlertText != NULL);
::SetWindowTextWrapW(hwndAlertText, rgszAlert);
return hwndAlertText;
}
BOOL CSecurityManager::IsBrowserHosted()
{
return (GetModuleHandle(TEXT("iexplore.exe")) != NULL
|| GetModuleHandle(TEXT("explorer.exe")) != NULL);
}
void
CSecurityManager::ShowUrlInNewBrowserInstance(LPCWSTR pwszUrl)
{
SHOWURLINNEWBROWSERINSTANCE pfSHOWURLINNEWBROWSERINSTANCE = NULL;
HINSTANCE hShdocvwLib;
if ( ( hShdocvwLib = LoadLibrary( L"shdocvw.dll" ) ) != NULL )
{
if ( pfSHOWURLINNEWBROWSERINSTANCE = (SHOWURLINNEWBROWSERINSTANCE)
GetProcAddress( hShdocvwLib, (LPSTR)SHOWURLINNEWBROWSERINSTANCE_ORDINAL) )
{
pfSHOWURLINNEWBROWSERINSTANCE(pwszUrl);
}
FreeLibrary( hShdocvwLib );
}
}
INT_PTR
CSecurityManager::TrustAlertDialogProc(HWND hDlg, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
LPDLGDATA lpDlgData;
HKEY hKeyIE = 0;
switch (iMsg)
{
case WM_INITDIALOG:
{
lpDlgData = (LPDLGDATA)lParam;
TransAssert(lpDlgData != NULL);
// Squirrel away the DlgData for later use as initialization data for the Trusted Sites dialog
SetWindowLongPtr(
hDlg,
DWLP_USER,
(LONG_PTR)lpDlgData);
UINT uID;
BOOL bShowCheckbox;
if(IsBrowserHosted())
{
if(!lpDlgData->bFromShdocvw)
{
uID = IDS_TRUST_DLG_MSG_STRING_URLMON_IE_1;
// checkbox always checked:
bShowCheckbox = TRUE;
}
else
{
// browser hosted shdocvw:
uID = IDS_TRUST_DLG_MSG_STRING_SHDOCVW_IE_1;
// Hide the checkbox:
bShowCheckbox = FALSE;
}
}
else // Non-browser hosted weboc:
{
TransAssert(!lpDlgData->bFromShdocvw);
uID = IDS_TRUST_DLG_MSG_STRING_URLMON_NONIE_1;
// Hide the checkbox:
bShowCheckbox = FALSE;
}
SetControlText(hDlg, IDC_TRUSTDLGTEXT1, uID);
if(bShowCheckbox)
{
CheckDlgButton(hDlg, IDC_ENABLE_TRUSTDLG, BST_CHECKED);
}
else
{
ShowWindow(GetDlgItem(hDlg, IDC_ENABLE_TRUSTDLG), SW_HIDE);
}
SetControlText(hDlg, IDC_TRUSTDLGTEXT2, IDS_TRUST_DLG_MSG_STRING_URLMON_SHDOCVW_IE_NONIE_2);
SetControlText(hDlg, IDC_TRUSTDLGTEXT3, IDS_TRUST_DLG_MSG_STRING_URLMON_SHDOCVW_IE_NONIE_WARNING);
SetControlText(hDlg, IDC_URL, IDS_TRUST_DLG_MSG_STRING_URLMON_SHDOCVW_IE_NONIE_URL, lpDlgData->pstr);
if(g_bUseHKLMOnly)
{
BOOL fEnable = TRUE;
HINSTANCE hinstAdvPack = LoadLibraryA("ADVPACK.DLL");
if (hinstAdvPack)
{
ISNTADMIN pfnIsNTAdmin = (ISNTADMIN)GetProcAddress(hinstAdvPack, achISNTADMIN);
if (pfnIsNTAdmin)
{
fEnable = pfnIsNTAdmin(0, NULL);
}
FreeLibrary(hinstAdvPack);
}
EnableWindow(GetDlgItem(hDlg, IDC_ADD_TRUSTED_SITES), fEnable);
}
if (lpDlgData->dwFlags & PUAF_FORCEUI_FOREGROUND)
{
SetForegroundWindow(hDlg);
}
// ensure that this window is topmost
SetWindowPos(hDlg, HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
return TRUE;
}
case WM_NOTIFY:
if (IDC_ESC_HELP == LOWORD(wParam))
{
NMHDR *pnmh = (NMHDR*) lParam;
if ((NM_CLICK == pnmh->code) || (NM_RETURN == pnmh->code))
{
ShowUrlInNewBrowserInstance(L"res://shdoclc.dll/IESechelp.htm");
}
}
break;
case WM_COMMAND:
switch (LOWORD(wParam))
{
case IDOK:
case IDCANCEL:
if( IsWindowVisible(GetDlgItem(hDlg, IDC_ENABLE_TRUSTDLG)) )
{
// persist value in registry:
hKeyIE = 0;
if (RegOpenKeyEx(HKEY_CURRENT_USER, REGSTR_PATH_IE_MAIN, 0, KEY_SET_VALUE, &hKeyIE) == ERROR_SUCCESS)
{
DWORD dwValue = IsDlgButtonChecked(hDlg, IDC_ENABLE_TRUSTDLG) == BST_CHECKED;
RegSetValueEx(hKeyIE, REGVAL_TRUSTDLG_ENABLED, NULL,
REG_DWORD, (LPBYTE)&dwValue, sizeof(dwValue));
RegCloseKey(hKeyIE);
}
}
// Check if site added to trusted list, if so return ZALERT_YES, to allow URLAction to succeed:
lpDlgData = (LPDLGDATA)GetWindowLongPtr(hDlg, DWLP_USER);
TransAssert(NULL != lpDlgData && NULL != lpDlgData->pstr && (lpDlgData->bFromShdocvw || NULL != lpDlgData->pSecurityManager));
EndDialog(hDlg,
// check if the site was actually added to the trusted zone:
IsSiteInZone(lpDlgData->pstr, URLZONE_TRUSTED, lpDlgData->pSecurityManager)?
ZALERT_YES: ZALERT_NO);
return TRUE;
case IDC_ADD_TRUSTED_SITES:
// Show Trusted Sites dialog.
lpDlgData = (LPDLGDATA)GetWindowLongPtr(hDlg, DWLP_USER);
TransAssert(NULL != lpDlgData && NULL != lpDlgData->pstr);
ShowAddToSitesList(hDlg, lpDlgData->pstr, URLZONE_TRUSTED);
// check if the site was actually added to the trusted zone:
if (IsSiteInZone(lpDlgData->pstr, URLZONE_TRUSTED, lpDlgData->pSecurityManager))
{
EndDialog(hDlg, ZALERT_YES);
}
return TRUE;
}
break;
} /* end switch */
return FALSE;
}
STDAPI_(int)
ShowTrustAlertDialog(HWND hwndParent, IInternetSecurityManager *pSecurityManager, LPVOID lpDlgDataIn, LPCWSTR pwszUrl )
{
LPDLGDATA lpDlgData;
DlgData dlgData;
int nRet;
WCHAR szUrl[MAX_ZONE_PATH];
if(!lpDlgDataIn)
{
ZeroMemory(&dlgData, sizeof(dlgData));
lpDlgData = &dlgData;
}
else
{
lpDlgData = (LPDLGDATA)lpDlgDataIn;
}
if (!pSecurityManager && SUCCEEDED(EnsureSecurityManager()) )
pSecurityManager = g_pSecurityManager;
lpDlgData->pSecurityManager = pSecurityManager;
lpDlgData->bFromShdocvw = !lpDlgDataIn;
if (GetAddSitesDisplayUrl(pwszUrl, szUrl, ARRAYSIZE(szUrl)))
{
lpDlgData->pstr = szUrl;
}
else
{
// Should never reach here
TransAssert(FALSE);
lpDlgData->pstr = L"";
}
ULONG_PTR uCookie = 0;
SHActivateContext(&uCookie);
nRet = (int) ::DialogBoxParamWrapW (
g_hInst,
MAKEINTRESOURCEW(IDD_TRUST_ALERT),
hwndParent,
CSecurityManager::TrustAlertDialogProc,
(LPARAM)lpDlgData
);
if (uCookie)
{
SHDeactivateContext(uCookie);
}
return nRet;
}
// 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);
SetControlText(hDlg, IDC_ZONEALERTTEXT, GetAlertIdForAction(lpDlgData->dwAction));
if (lpDlgData->dwFlags & PUAF_FORCEUI_FOREGROUND)
{
SetForegroundWindow(hDlg);
}
// ensure that this window is topmost
SetWindowPos(hDlg, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
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);
LPCWSTR 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;
ULONG_PTR uCookie = 0;
SHActivateContext(&uCookie);
nRet = (int) ::DialogBoxParamWrapW (
g_hInst,
MAKEINTRESOURCEW(IDD_WARN_ON_POST),
hwndParent,
CSecurityManager::FormsAlertDialogProc,
(LPARAM)lpDlgData
);
if (uCookie)
{
SHDeactivateContext(uCookie);
}
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;
SetControlText(hDlg, IDC_ZONEALERTTEXT, GetWarnIdForAction(dwAction));
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;
}
// INETCPL calls this during _GetAddSitesDisplayUrl
// to get the correct file://UNC URL domain
// NOTE: The input URL pszUrl has to be atleast MAX_ZONE_PATH in size
STDAPI GetAddSitesFileUrl(LPWSTR /* [in, out] */ pszUrl)
{
HRESULT hr = S_FALSE;
ZONEMAP_COMPONENTS zc;
// Conversion for UNC paths:
if(SUCCEEDED(zc.Crack (pszUrl, 0 /* dwFlags */)) && zc.nScheme == URL_SCHEME_FILE
&& !zc.cchDomain && !zc.fDrive && zc.pszSite && zc.cchSite
)
{
// Guard against buffer overflow.
if (ARRAYSIZE(L"file://") + zc.cchSite >= MAX_ZONE_PATH)
{
hr = E_INVALIDARG;
}
else
{
// ZONEMAP_COMPONENTS components could be pointing directly into pszUrl,
// so create a new string to hold the result:
DWORD nSizeNewFileUrl = ARRAYSIZE(L"file://") + zc.cchSite + 1;
LPWSTR pwzNewFileUrl = new WCHAR [nSizeNewFileUrl];
if ( pwzNewFileUrl != NULL )
{
// convert to the form file://UNC
StrCpyNW(pwzNewFileUrl, L"file://", ARRAYSIZE(L"file://") /* No of chars to copy incl NULL */);
StrNCatW(pwzNewFileUrl, zc.pszSite, zc.cchSite + 1 /* Incl NULL */);
StrCpyNW(pszUrl, pwzNewFileUrl, nSizeNewFileUrl /* No of chars to copy incl NULL */);
delete [] pwzNewFileUrl;
hr = S_OK;
}
else
{
hr = E_OUTOFMEMORY;
}
}
}
return hr;
}
STDMETHODIMP
CSecurityManager::ProcessUrlAction
(
LPCWSTR pwszUrl,
DWORD dwAction,
BYTE * pPolicy,
DWORD cbPolicy,
BYTE * pContext,
DWORD cbContext,
DWORD dwFlags,
DWORD dwReserved
)
{
DEBUG_ENTER((DBG_API,
Hresult,
"CSecurityManager::ProcessUrlAction",
"%.100wq, %#x, dwFlags=%#x, dwReserved=%#x",
pwszUrl, dwAction, dwFlags, 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)
{
if (pwszUrl && wcsncmp(pwszUrl, L"about:security_", 15) == 0)
{
hr = m_pDelegateSecMgr->ProcessUrlAction(L"about:blank", dwAction, pPolicy, cbPolicy, pContext, cbContext, dwFlags, dwReserved);
}
else
{
hr = m_pDelegateSecMgr->ProcessUrlAction(pwszUrl, dwAction, pPolicy, cbPolicy, pContext, cbContext, dwFlags, dwReserved);
}
}
if (hr != INET_E_DEFAULT_ACTION)
{
// Delegation interface processed the request.
DEBUG_LEAVE(hr);
return hr;
}
if (!EnsureZoneManager())
{
DEBUG_LEAVE(E_UNEXPECTED);
return E_UNEXPECTED;
}
// Increment our refcount so we don't get destroyed for the duration of this
// function.
AddRef();
if (dwFlags & PUAF_ENFORCERESTRICTED)
{
dwZone = URLZONE_UNTRUSTED;
hr = S_OK;
}
else
{
hr = MapUrlToZone(pwszUrl, &dwZone, dwFlags);
}
if (SUCCEEDED(hr))
{
DWORD dwPolicy;
hr = m_pZoneManager->GetZoneActionPolicy(dwZone, dwAction, (BYTE *)&dwPolicy, sizeof(dwPolicy), URLZONEREG_DEFAULT);
if (SUCCEEDED(hr))
{
DWORD dwPermissions = GetUrlPolicyPermissions(dwPolicy);
BOOL isScriptOrActiveXHardenedInternet = IsScriptOrActiveXHardenedInternet(dwAction, dwPermissions, dwZone);
if(isScriptOrActiveXHardenedInternet)
{
// Make it a query:
dwPermissions = URLPOLICY_QUERY;
}
// 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
{
if(isScriptOrActiveXHardenedInternet)
{
nRet = ShowTrustAlertDialog(hwndParent, (IInternetSecurityManager *)this, &dlgData, pwszUrl );
}
else
{
ULONG_PTR uCookie = 0;
SHActivateContext(&uCookie);
nRet = (int) ::DialogBoxParamWrapW (
g_hInst,
MAKEINTRESOURCEW(IDD_ZONE_ALERT),
hwndParent,
CSecurityManager::ZonesAlertDialogProc,
(LPARAM)&dlgData
);
if (uCookie)
{
SHDeactivateContext(uCookie);
}
}
}
}
}
else
{
TransAssert(dwPermissions == URLPOLICY_DISALLOW);
if (bShowUI)
{
ULONG_PTR uCookie = 0;
SHActivateContext(&uCookie);
nRet = (int) ::DialogBoxParamWrapW (
g_hInst,
MAKEINTRESOURCEW(IDD_WARN_ALERT),
hwndParent,
CSecurityManager::ZonesWarnDialogProc,
(LPARAM)&dlgData
);
if (uCookie)
{
SHDeactivateContext(uCookie);
}
}
}
// 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();
DEBUG_LEAVE( (SUCCEEDED(hr) ? *((DWORD*)pPolicy) : hr) );
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)
{
BOOL bUseHardenedZone;
if(bUseHardenedZone = (URLZONE_ESC_FLAG & dwZone) )
{
// Remove the special ESC flag bit to convert it to a regular URLZONE_XXX:
dwZone &= ~URLZONE_ESC_FLAG;
}
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;
// IEHardening workaround: The "Flags" for the Trusted zone require "Server Authentication" by default for softened mode.
// In other words, only HTTPS sites can be added. But the default is to _not_ require https for hardened mode.
// Since we don't have 2 sets of flags, and since the zone template is not readily accessible, we assume that
// http sites can in fact be added for URLZONE_ESC_FLAG | URLZONE_TRUSTED cases
// Don't allow adding not https entries if the zone requires server verification.
if( !(bUseHardenedZone && URLZONE_TRUSTED == dwZone && !IEHardened()) )
{
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;
TCHAR szKeyName[MAX_PATH];
DWORD cchKeyName = CSTRLENW(SZDOMAINS);
if(bUseHardenedZone || m_fHardened)
{
cchKeyName = CSTRLENW(SZHARDENEDDOMAINS);
memcpy (szKeyName, SZHARDENEDDOMAINS, sizeof(TCHAR) * cchKeyName);
}
else
{
cchKeyName = CSTRLENW(SZDOMAINS);
memcpy (szKeyName, SZDOMAINS, sizeof(TCHAR) * cchKeyName);
}
// Guard against buffer overflow.
if (cchKeyName + zc.cchDomain + 1 + zc.cchSite + 1 >= MAX_PATH)
return E_INVALIDARG;
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;
}
else
{
/*We need to catch the following here:
1. *.com
2. www.*
3. www.*.*
4. *.co.uk
All these show up if zc.pszDomain is NULL.
*/
if (StrChr(zc.pszSite, WILDCARD) != NULL)
return E_INVALIDARG;
}
// 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;
if(bUseHardenedZone || m_fHardened)
{
cch = CSTRLENW(SZHARDENEDDOMAINS) + zc.cchDomain;
}
else
{
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)
{
regMap.Close();
if ((dwErr = regMap.Open(m_regZoneMap, szKeyName, KEY_READ | KEY_WRITE)) != ERROR_SUCCESS)
{
return HRESULT_FROM_WIN32(GetLastError());
}
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;
TCHAR szKeyName[MAX_PATH] = SZRANGES;
if(m_fHardened)
{
StrCpy(szKeyName, SZESCRANGES);
}
if ( ERROR_SUCCESS != regRanges.Open(m_regZoneMap, szKeyName, 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 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;
TCHAR szKeyName[MAX_PATH];
if(m_fHardened)
{
lstrcpy (szKeyName, SZHARDENEDDOMAINS);
}
else
{
lstrcpy (szKeyName, SZDOMAINS);
}
if ( ((HUSKEY)m_regZoneMap != NULL) &&
(regDomainRoot.Open(m_regZoneMap, szKeyName, 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(&regSite, 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(&regDomain, 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
//
// RETURN value is ONLY S_OK.
// Be careful about returning anything else since some calling functions assume this is the only return value.
HRESULT
CSecurityManager::MapUrlToZone (ZONEMAP_COMPONENTS* pzc, DWORD* pdwZone, DWORD dwFlags,
BOOL *pfMarked, LPWSTR *ppszMarkURL)
{
HRESULT hr;
CRegKey regProtocols;
BOOL fMarked = FALSE;
if (dwFlags & MUTZ_ENFORCERESTRICTED)
{
*pdwZone = URLZONE_UNTRUSTED;
hr = S_OK;
goto done;
}
// Guard against buffer overflow.
DWORD dwLength = CSTRLENW(SZDOMAINS);
if(m_fHardened)
{
dwLength = CSTRLENW(SZHARDENEDDOMAINS);
}
if (dwLength + pzc->cchDomain + 1 + pzc->cchSite + 1 >= MAX_PATH)
goto default_zone;
if (pzc->fDrive)
{
switch (pzc->dwDriveType)
{
case DRIVE_UNKNOWN:
case DRIVE_NO_ROOT_DIR:
break;
case DRIVE_REMOTE:
TransAssert(FALSE);
*pdwZone = URLZONE_INTRANET;
goto done;
default:
{
BOOL bCacheFile = IsFileInCacheDir(pzc->pszSite);
*pdwZone = bCacheFile ? URLZONE_INTERNET : URLZONE_LOCAL_MACHINE;
if(bCacheFile)
goto done;
bCacheFile = IsFileInCookieDir(pzc->pszSite);
*pdwZone = bCacheFile ? URLZONE_UNTRUSTED : URLZONE_LOCAL_MACHINE;
if(bCacheFile)
goto done;
// do the Mark of the Web stuff, if we have a local non-cache file:
LPWSTR pwszMarkURL = NULL;
TCHAR *pszExt = PathFindExtension(pzc->pszSite);
LPCTSTR 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 );
fMarked = TRUE;
if (ppszMarkURL)
{
*ppszMarkURL = pwszMarkURL;
pwszMarkURL = NULL; // give mark string to caller, don't free
}
}
if (pwszMarkURL)
LocalFree(pwszMarkURL);
goto done;
}
}
}
else if (IsOpaqueScheme(pzc->nScheme))
{
if(m_fHardened)
{
if (S_OK == CheckSiteAndDomainMappings (pzc, pdwZone, pzc->pszProtocol, TRUE))
goto done;
}
else
{
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(m_fHardened)
{
if (S_OK == CheckSiteAndDomainMappings (pzc, pdwZone, pzc->pszProtocol, TRUE))
goto done;
}
else
{
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;
TCHAR szKeyName[MAX_PATH];
if(m_fHardened)
{
StrCpy (szKeyName, SZESCRANGES);
}
else
{
StrCpy (szKeyName, SZRANGES);
}
// Read in ranges from registry.
if ( ERROR_SUCCESS != regRanges.Open (m_regZoneMap, szKeyName, 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;
if(m_fHardened)
{
StrCpy(szItemName, SZESCRANGES);
}
else
{
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.
DWORD dwLen = 0;
if(m_fHardened)
{
StrCpy(szItemName, SZESCRANGES);
dwLen = lstrlen(SZESCRANGES);
}
else
{
StrCpy(szItemName, SZRANGES);
dwLen = lstrlen(SZRANGES);
}
StrCat(szItemName, SZRANGEPREFIX);
if (!DwToWchar(s_dwNextRangeIndex, szItemName + dwLen + 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 = m_fHardened? CSTRLENW(SZESCRANGES) : CSTRLENW(SZRANGES);
if(m_fHardened)
{
memcpy (szKeyName, SZESCRANGES, sizeof(TCHAR) * cchRanges);
}
else
{
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, BOOL fCheckHardenKey)
{
CRegKey regDomain, regSite;
DWORD dwRegErr;
TCHAR szKeyName[MAX_PATH];
DWORD cchKeyName;
if(fCheckHardenKey)
{
cchKeyName = CSTRLENW(SZHARDENEDDOMAINS);
memcpy (szKeyName, SZHARDENEDDOMAINS, sizeof(TCHAR) * cchKeyName);
}
else
{
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;
SAFEREGOPERATION(dwRegErr, regDomain.Open (m_regZoneMap, szKeyName, KEY_READ));
if ((dwRegErr != ERROR_SUCCESS)
&& (szKeyName[cchKeyName + pzc->cchDomain -1] == DOT)//check if this is a terminal-dotted-site
&& ((pzc->nScheme == URL_SCHEME_HTTP)
|| (pzc->nScheme == URL_SCHEME_HTTPS)
|| (pzc->nScheme == URL_SCHEME_FILE)))
{
szKeyName[cchKeyName + pzc->cchDomain -1] = 0;
dwRegErr = regDomain.Open(m_regZoneMap, szKeyName, KEY_READ);
}
if(dwRegErr != ERROR_SUCCESS)
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;
SAFEREGOPERATION(dwRegErr, regSite.Open (m_regZoneMap, szKeyName, KEY_READ));
if ((dwRegErr != ERROR_SUCCESS)
&& (szKeyName[cchKeyName + pzc->cchSite -1] == DOT) //check if this is a terminal-dotted-site
&& ((pzc->nScheme == URL_SCHEME_HTTP)
|| (pzc->nScheme == URL_SCHEME_HTTPS)
|| (pzc->nScheme == URL_SCHEME_FILE)))
{
szKeyName[cchKeyName + pzc->cchSite -1] = 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;
DWORD dwRegErr;
TransAssert(!pzc->fDrive);
TransAssert(!pzc->fIPRange);
if (pzc->fAddr || pzc->nScheme != URL_SCHEME_FILE)
return hr;
SAFEREGOPERATION(dwRegErr, m_regZoneMap.QueryValue(&dwUNCAsIntranet, SZUNCASINTRANET));
if(ERROR_SUCCESS != dwRegErr)
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;
DWORD dwRegErr;
TransAssert(!pzc->fDrive);
TransAssert(!pzc->fIPRange);
if (pzc->fAddr)
return hr;
// Check if there is a local intranet rule.
SAFEREGOPERATION(dwRegErr, m_regZoneMap.QueryValue(&dwZone, SZINTRANETNAME));
if(ERROR_SUCCESS != dwRegErr)
return hr;
if (dwZone != URLZONE_INTRANET)
{
TransAssert(FALSE);
if(pdwZone)
{
*pdwZone = URLZONE_INTERNET;
}
hr = E_FAIL;
return hr;
}
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);
DWORD dwRegErr;
DWORD dwValue = 0;
// Check if there is a proxy bypass rule.
SAFEREGOPERATION(dwRegErr, m_regZoneMap.QueryValue(&dwValue, SZPROXYBYPASS));
if(ERROR_SUCCESS != dwRegErr || !dwValue)
return S_FALSE;
*pdwZone = URLZONE_INTRANET;
// 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;
}