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.
 
 
 
 
 
 

1199 lines
41 KiB

/*****************************************************************************\
FILE: AutoDiscBase.cpp
DESCRIPTION:
This is the Autmation Object to AutoDiscover account information.
BryanSt 10/3/1999
Copyright (C) Microsoft Corp 1999-1999. All rights reserved.
\*****************************************************************************/
#include "priv.h"
#include <cowsite.h>
#include <atlbase.h>
#include <crypto\md5.h>
#include "AutoDiscover.h"
#include "INStoXML.h"
//#define SZ_WININET_AGENT_AUTO_DISCOVER TEXT("Microsoft(r) Windows(tm) Account AutoDiscovery Agent")
#define SZ_WININET_AGENT_AUTO_DISCOVER TEXT("Mozilla/4.0 (compatible; MSIE.5.01; Windows.NT.5.0)")
// BUGBUG: Ditch default.asp
#define SZ_ADSERVER_XMLFILE "/AutoDiscover/default.xml"
#define SZ_PATH_AUTODISCOVERY L"AutoDiscovery"
#define SZ_FILEEXTENSION L".xml"
#define SZ_TEMPEXTENSION L".tmp"
// this is how long we wait for the UI thread to create the progress hwnd before giving up
#define WAIT_AUTODISCOVERY_STARTUP_HWND 10*1000 // ten seconds
// The FILETIME structure is a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601
#define SECONDS_IN_ONE_DAY (60/*seconds*/ * 60/*minutes*/ * 24/*hrs*/)
//===========================
// *** Class Internals & Helpers ***
//===========================
HRESULT GetTempPathHr(IN DWORD cchSize, IN LPTSTR pszPath)
{
HRESULT hr = S_OK;
DWORD cchSizeNeeded = GetTempPath(cchSize, pszPath);
if ((0 == cchSizeNeeded) || (cchSizeNeeded > cchSize))
{
hr = E_FAIL;
}
return hr;
}
HRESULT GetTempFileNameHr(IN LPCTSTR lpPathName, IN LPCTSTR lpPrefixString, IN UINT uUnique, IN LPTSTR lpTempFileName)
{
if (0 == GetTempFileName(lpPathName, lpPrefixString, uUnique, lpTempFileName))
{
return HRESULT_FROM_WIN32(GetLastError());
}
return S_OK;
}
HRESULT CreateXMLTempFile(IN BSTR bstrXML, IN LPTSTR pszPath, IN DWORD cchSize)
{
TCHAR szTemp[MAX_PATH];
HRESULT hr = GetTempPathHr(ARRAYSIZE(szTemp), szTemp);
AssertMsg((MAX_PATH <= cchSize), "You need to be at least MAX_PATH. Required by GetTempFileName()");
if (SUCCEEDED(hr))
{
hr = GetTempFileNameHr(szTemp, TEXT("AD_"), 0, pszPath);
if (SUCCEEDED(hr))
{
HANDLE hFile;
hr = CreateFileHrWrap(pszPath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL, &hFile);
if (SUCCEEDED(hr))
{
LPSTR pszAnsiXML = AllocStringFromBStr(bstrXML);
if (pszAnsiXML)
{
DWORD cchWritten;
hr = WriteFileWrap(hFile, pszAnsiXML, (lstrlenA(pszAnsiXML) + 1), &cchWritten, NULL);
LocalFree(pszAnsiXML);
}
else
{
hr = E_OUTOFMEMORY;
}
CloseHandle(hFile);
}
if (FAILED(hr))
{
DeleteFile(pszPath);
}
}
}
return hr;
}
/*****************************************************************************\
DESCRIPTION:
This function will see if pbstrXML is valid AutoDiscovery XML or is
in the .INS/.ISP format that can be converted to valid XML. It will then look
for a redirect URL and return on if one exists.
\*****************************************************************************/
HRESULT CAccountDiscoveryBase::_VerifyValidXMLResponse(IN BSTR * pbstrXML, IN LPWSTR pszRedirURL, IN DWORD cchSize)
{
IXMLDOMDocument * pXMLDOMDoc;
bool fConverted = false;
HRESULT hr = XMLDOMFromBStr(*pbstrXML, &pXMLDOMDoc);
TCHAR szPath[MAX_PATH];
pszRedirURL[0] = 0;
if (FAILED(hr))
{
// It may have failed if it was an .INS or .ISP formatted
// file. Since we need to be compatible with these
// file formats, check for it and convert it if it
// is in that format.
hr = CreateXMLTempFile(*pbstrXML, szPath, ARRAYSIZE(szPath));
if (SUCCEEDED(hr))
{
fConverted = true;
if (IsINSFile(szPath))
{
hr = ConvertINSToXML(szPath);
if (SUCCEEDED(hr))
{
hr = XMLDOMFromFile(szPath, &pXMLDOMDoc);
}
}
else
{
hr = E_FAIL;
}
}
}
if (SUCCEEDED(hr))
{
IXMLDOMElement * pXMLElementMessage = NULL;
hr = pXMLDOMDoc->get_documentElement(&pXMLElementMessage);
if (S_FALSE == hr)
hr = HRESULT_FROM_WIN32(ERROR_NOT_FOUND);
else if (SUCCEEDED(hr))
{
// This is only valid XML if the root tag is "AUTODISCOVERY".
// The case is not important.
hr = XMLElem_VerifyTagName(pXMLElementMessage, SZ_XMLELEMENT_AUTODISCOVERY);
if (SUCCEEDED(hr))
{
// Now we are in search for a redirect URL.
IXMLDOMNode * pXMLReponse;
// Enter the <RESPONSE> tag.
if (SUCCEEDED(XMLNode_GetChildTag(pXMLElementMessage, SZ_XMLELEMENT_RESPONSE, &pXMLReponse)))
{
IXMLDOMElement * pXMLElementMessage;
if (SUCCEEDED(pXMLReponse->QueryInterface(IID_PPV_ARG(IXMLDOMElement, &pXMLElementMessage))))
{
IXMLDOMNodeList * pNodeListAccounts;
// Iterate thru the list of <ACCOUNT> tags...
if (SUCCEEDED(XMLElem_GetElementsByTagName(pXMLElementMessage, SZ_XMLELEMENT_ACCOUNT, &pNodeListAccounts)))
{
DWORD dwIndex = 0;
IXMLDOMNode * pXMLNodeAccount = NULL;
// We are going to look thru each one for one of them with <TYPE>email</TYPE>
while (S_OK == XMLNodeList_GetChild(pNodeListAccounts, dwIndex, &pXMLNodeAccount))
{
// FUTURE: We could support redirects or error messages here depending on
// <ACTION> redirect | message </ACTION>
if (XML_IsChildTagTextEqual(pXMLNodeAccount, SZ_XMLELEMENT_TYPE, SZ_XMLTEXT_EMAIL) &&
XML_IsChildTagTextEqual(pXMLNodeAccount, SZ_XMLELEMENT_ACTION, SZ_XMLTEXT_REDIRECT))
{
CComBSTR bstrRedirURL;
// This file may or may not settings to contact the server. However in either case
// it may contain an INFOURL tag. If it does, then the URL in side will point to a
// web page.
// <INFOURL> xxx </INFOURL>
if (SUCCEEDED(XMLNode_GetChildTagTextValue(pXMLNodeAccount, SZ_XMLELEMENT_REDIRURL, &bstrRedirURL)))
{
StrCpyNW(pszRedirURL, bstrRedirURL, cchSize);
break;
}
}
// No, so keep looking.
ATOMICRELEASE(pXMLNodeAccount);
dwIndex++;
}
ATOMICRELEASE(pXMLNodeAccount);
pNodeListAccounts->Release();
}
pXMLElementMessage->Release();
}
pXMLReponse->Release();
}
}
pXMLElementMessage->Release();
}
if (true == fConverted)
{
if (SUCCEEDED(hr))
{
// It only succeeded after the conversion, so we need to move the
// XML from the temp file to pbstrXML.
SysFreeString(*pbstrXML);
*pbstrXML = NULL;
hr = XMLBStrFromDOM(pXMLDOMDoc, pbstrXML);
}
}
pXMLDOMDoc->Release();
}
if (true == fConverted)
{
DeleteFile(szPath);
}
return hr;
}
typedef HINSTANCE (STDAPICALLTYPE *PFNMLLOADLIBARY)(LPCSTR lpLibFileName, HMODULE hModule, DWORD dwCrossCodePage);
static const char c_szShlwapiDll[] = "shlwapi.dll";
static const char c_szDllGetVersion[] = "DllGetVersion";
HINSTANCE LoadLangDll(HINSTANCE hInstCaller, LPCSTR szDllName, BOOL fNT)
{
char szPath[MAX_PATH];
HINSTANCE hinstShlwapi;
PFNMLLOADLIBARY pfn;
DLLGETVERSIONPROC pfnVersion;
int iEnd;
DLLVERSIONINFO info;
HINSTANCE hInst = NULL;
hinstShlwapi = LoadLibraryA(c_szShlwapiDll);
if (hinstShlwapi != NULL)
{
pfnVersion = (DLLGETVERSIONPROC)GetProcAddress(hinstShlwapi, c_szDllGetVersion);
if (pfnVersion != NULL)
{
info.cbSize = sizeof(DLLVERSIONINFO);
if (SUCCEEDED(pfnVersion(&info)))
{
if (info.dwMajorVersion >= 5)
{
pfn = (PFNMLLOADLIBARY)GetProcAddress(hinstShlwapi, MAKEINTRESOURCEA(377));
if (pfn != NULL)
hInst = pfn(szDllName, hInstCaller, (ML_NO_CROSSCODEPAGE));
}
}
}
FreeLibrary(hinstShlwapi);
}
if ((NULL == hInst) && (GetModuleFileNameA(hInstCaller, szPath, ARRAYSIZE(szPath))))
{
if (PathRemoveFileSpecA(szPath) && PathAppendA(szPath, szDllName))
{
hInst = LoadLibraryA(szPath);
}
}
return hInst;
}
#define SZ_DLL_OE_ACCTRES_DLL "acctres.dll"
HRESULT CAccountDiscoveryBase::_SendStatusMessage(UINT nStringID, LPCWSTR pwzArg)
{
HRESULT hr = S_OK;
if (m_hwndAsync && IsWindow(m_hwndAsync))
{
WCHAR szMessage[MAX_URL_STRING*3];
WCHAR szTemplate[MAX_URL_STRING*3];
// Our DLL has these message.
LoadString(HINST_THISDLL, nStringID, szTemplate, ARRAYSIZE(szTemplate));
HINSTANCE hInstOE = LoadLangDll(GetModuleHandleA(NULL), SZ_DLL_OE_ACCTRES_DLL, IsOSNT());
if (hInstOE)
{
// We prefer to get the string from OE because it will be localized based on the installed
// language.
LoadString(hInstOE, nStringID, szTemplate, ARRAYSIZE(szTemplate));
FreeLibrary(hInstOE);
}
if (pwzArg)
{
wnsprintfW(szMessage, ARRAYSIZE(szMessage), szTemplate, pwzArg);
}
else
{
StrCpyN(szMessage, szTemplate, ARRAYSIZE(szMessage));
}
DWORD cchSize = (lstrlenW(szMessage) + 1);
LPWSTR pszString = (LPWSTR) LocalAlloc(LPTR, cchSize * sizeof(szMessage[0]));
if (pszString)
{
StrCpyN(pszString, szMessage, cchSize);
PostMessage(m_hwndAsync, (m_wMsgAsync + 1), (WPARAM)pszString, (LPARAM)0);
}
else
{
hr = E_OUTOFMEMORY;
}
}
return hr;
}
HRESULT CAccountDiscoveryBase::_UrlToComponents(IN LPCWSTR pszURL, IN BOOL * pfHTTPS, IN LPWSTR pszDomain, IN DWORD cchSize, IN LPSTR pszURLPath, IN DWORD cchSizeURLPath)
{
HRESULT hr = S_OK;
WCHAR szScheme[INTERNET_MAX_SCHEME_LENGTH];
WCHAR szURLPath[INTERNET_MAX_PATH_LENGTH];
URL_COMPONENTS urlComponents = {0};
urlComponents.dwStructSize = sizeof(urlComponents);
urlComponents.lpszScheme = szScheme;
urlComponents.dwSchemeLength = ARRAYSIZE(szScheme);
urlComponents.lpszHostName = pszDomain;
urlComponents.dwHostNameLength = cchSize;
urlComponents.lpszUrlPath = szURLPath;
urlComponents.dwUrlPathLength = ARRAYSIZE(szURLPath);
*pfHTTPS = ((INTERNET_SCHEME_HTTPS == urlComponents.nScheme) ? TRUE : FALSE);
if (!InternetCrackUrlW(pszURL, 0, 0, &urlComponents))
{
hr = HRESULT_FROM_WIN32(GetLastError());
}
else
{
SHUnicodeToAnsi(szURLPath, pszURLPath, cchSizeURLPath);
}
return hr;
}
HRESULT CAccountDiscoveryBase::_GetInfoFromDomain(IN BSTR bstrXMLRequest, IN BSTR bstrEmail, IN LPCWSTR pwzDomain, IN BOOL fHTTPS, IN BOOL fPost, IN LPCSTR pszURLPath, OUT BSTR * pbstrXML)
{
HRESULT hr = E_OUTOFMEMORY;
DWORD cbToSend = (lstrlenW(bstrXMLRequest));
LPSTR pszPostData = (LPSTR) LocalAlloc(LPTR, (cbToSend + 1) * sizeof(bstrXMLRequest[0]));
TCHAR szRedirectURL[MAX_URL_STRING];
szRedirectURL[0] = 0;
if (pszPostData)
{
HINTERNET hInternetHTTPConnect = NULL;
SHUnicodeToAnsi(bstrXMLRequest, pszPostData, (cbToSend + 1));
_SendStatusMessage(IDS_STATUS_CONNECTING_TO, pwzDomain);
// We may want to use INTERNET_FLAG_KEEP_CONNECTION.
hr = InternetConnectWrap(m_hInternetSession, FALSE, pwzDomain, (fHTTPS ? INTERNET_DEFAULT_HTTPS_PORT : INTERNET_DEFAULT_HTTP_PORT),
NULL, NULL, INTERNET_SERVICE_HTTP, 0, NULL, &hInternetHTTPConnect);
if (SUCCEEDED(hr))
{
HINTERNET hInternetHTTPRequest = NULL;
DWORD cbBytesRead;
// NOTE: The web server may want to redirect to an https URL for additional security.
// We need to pass the INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS to HttpOpenRequest
// or HttpSendRequest() will fail with ERROR_INTERNET_HTTP_TO_HTTPS_ON_REDIR
// NOTE: We may need to split the URL into lpszReferer + lpszObjectName.
hr = HttpOpenRequestWrap(hInternetHTTPConnect, (fPost ? SZ_HTTP_VERB_POST : NULL), pszURLPath, HTTP_VERSIONA,
/*pszReferer*/ NULL, NULL, INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS, NULL, &cbBytesRead, &hInternetHTTPRequest);
if (SUCCEEDED(hr))
{
hr = HttpSendRequestWrap(hInternetHTTPRequest, NULL, 0, (fPost ? pszPostData : NULL), (fPost ? cbToSend : 0));
if (SUCCEEDED(hr))
{
_SendStatusMessage(IDS_STATUS_DOWNLOADING, pwzDomain);
hr = InternetReadIntoBSTR(hInternetHTTPRequest, pbstrXML);
if (SUCCEEDED(hr))
{
hr = _VerifyValidXMLResponse(pbstrXML, szRedirectURL, ARRAYSIZE(szRedirectURL));
if (FAILED(hr))
{
SysFreeString(*pbstrXML);
*pbstrXML = NULL;
}
}
if (SUCCEEDED(hr))
{
hr = InternetCloseHandleWrap(hInternetHTTPRequest);
}
else
{
InternetCloseHandleWrap(hInternetHTTPRequest);
}
}
InternetCloseHandleWrap(hInternetHTTPRequest);
}
InternetCloseHandleWrap(hInternetHTTPConnect);
}
LocalFree(pszPostData);
}
// Did the caller want to redirect to another server?
if (szRedirectURL[0])
{
// Yes, so do that now via recursion.
WCHAR szDomain[INTERNET_MAX_HOST_NAME_LENGTH];
CHAR szURLPath[INTERNET_MAX_PATH_LENGTH];
SysFreeString(*pbstrXML);
*pbstrXML = NULL;
hr = _UrlToComponents(szRedirectURL, &fHTTPS, szDomain, ARRAYSIZE(szDomain), szURLPath, ARRAYSIZE(szURLPath));
if (SUCCEEDED(hr))
{
hr = _GetInfoFromDomain(bstrXMLRequest, bstrEmail, szDomain, fHTTPS, TRUE, szURLPath, pbstrXML);
}
}
return hr;
}
#define SZ_XML_NOTFOUNDRESULTS L"<?xml version=\"1.0\"?><AUTODISCOVERY><NOFOUND /></AUTODISCOVERY>"
HRESULT CAccountDiscoveryBase::_GetInfoFromDomainWithSubdirAndCacheCheck(IN BSTR bstrXMLRequest, IN BSTR bstrEmail, IN LPCWSTR pwzDomain, IN BSTR * pbstrXML, IN DWORD dwFlags, IN LPCSTR pszURLPath)
{
HRESULT hr;
WCHAR wzCacheURL[INTERNET_MAX_HOST_NAME_LENGTH];
if (dwFlags & ADDN_SKIP_CACHEDRESULTS)
{
hr = E_FAIL;
}
else
{
hr = _CheckInCacheAndAddHash(pwzDomain, bstrEmail, pszURLPath, wzCacheURL, ARRAYSIZE(wzCacheURL), bstrXMLRequest, pbstrXML);
}
if (FAILED(hr))
{
hr = _GetInfoFromDomain(bstrXMLRequest, bstrEmail, pwzDomain, FALSE, FALSE, pszURLPath, pbstrXML);
if (SUCCEEDED(hr))
{
// Put the data into the cache for the next time.
_CacheResults(wzCacheURL, *pbstrXML);
}
else
{
// We want to make a blank entry so we don't keep hitting the server
_CacheResults(wzCacheURL, SZ_XML_NOTFOUNDRESULTS);
}
}
// Did we find a blank entry?
if (SUCCEEDED(hr) && pbstrXML && *pbstrXML && !StrCmpIW(*pbstrXML, SZ_XML_NOTFOUNDRESULTS))
{
// Yes, so we didn't get a successful results, so fail.
// This way we will try other sources.
hr = E_FAIL;
SysFreeString(*pbstrXML);
*pbstrXML = NULL;
}
return hr;
}
BOOL IsExpired(FILETIME ftExpireTime)
{
BOOL fIsExpired = TRUE;
SYSTEMTIME stCurrentTime;
FILETIME ftCurrentTime;
GetSystemTime(&stCurrentTime);
SystemTimeToFileTime(&stCurrentTime, &ftCurrentTime);
// It is not expired if the current time is before the expired time.
if (-1 == CompareFileTime(&ftCurrentTime, &ftExpireTime))
{
fIsExpired = FALSE;
}
return fIsExpired;
}
#define SZ_HASHSTR_HEADER L"MD5"
HRESULT GenerateHashStr(IN LPCWSTR pwzHashData, IN LPWSTR pwzHashStr, IN DWORD cchSize)
{
HRESULT hr = E_FAIL;
MD5_CTX md5;
DWORD * pdwHashChunk = (DWORD *)&md5.digest;
MD5Init(&md5);
MD5Update(&md5, (const unsigned char *) pwzHashData, (lstrlenW(pwzHashData) * sizeof(OLECHAR)));
MD5Final(&md5);
StrCpyNW(pwzHashStr, SZ_HASHSTR_HEADER, cchSize);
// Break the hash into 64 bit chunks and turn them into strings.
// pwzHashStr will then contain the header and each chunk concatinated.
for (int nIndex = 0; nIndex < (sizeof(md5.digest) / sizeof(*pdwHashChunk)); nIndex++)
{
WCHAR szNumber[MAX_PATH];
wnsprintfW(szNumber, ARRAYSIZE(szNumber), L"%08lX", pdwHashChunk[nIndex]);
StrCatBuffW(pwzHashStr, szNumber, cchSize);
}
return hr;
}
HRESULT CAccountDiscoveryBase::_CheckInCacheAndAddHash(IN LPCWSTR pwzDomain, IN BSTR bstrEmail, IN LPCSTR pszSubdir, IN LPWSTR pwzCacheURL, IN DWORD cchSize, IN BSTR bstrXMLRequest, OUT BSTR * pbstrXML)
{
WCHAR szHash[MAX_PATH];
// We add the MD5 of the XML request to the URL so that the different XML requests to the
// same server are cached separately
GenerateHashStr(bstrXMLRequest, szHash, ARRAYSIZE(szHash));
wnsprintfW(pwzCacheURL, cchSize, L"http://%ls.%ls%hs/%ls.xml", szHash, pwzDomain, pszSubdir, bstrEmail);
return _CheckInCache(pwzCacheURL, pbstrXML);
}
HRESULT CAccountDiscoveryBase::_CheckInCache(IN LPWSTR pwzCacheURL, OUT BSTR * pbstrXML)
{
HINTERNET hOpenUrlSession;
DWORD cbSize = (sizeof(INTERNET_CACHE_ENTRY_INFO) + 4048);
LPINTERNET_CACHE_ENTRY_INFO lpCacheEntryInfo = (LPINTERNET_CACHE_ENTRY_INFO) LocalAlloc(LPTR, cbSize);
HRESULT hr = E_FAIL;
if (lpCacheEntryInfo)
{
// HACKHACK: I wish InternetOpenUrlWrap() would respect the INTERNET_FLAG_FROM_CACHE flag but
// it doesn't. Therefore I call GetUrlCacheEntryInfo() to check and check the expired
// myself.
lpCacheEntryInfo->dwStructSize = cbSize;
if (GetUrlCacheEntryInfo(pwzCacheURL, lpCacheEntryInfo, &cbSize))
{
if (!IsExpired(lpCacheEntryInfo->ExpireTime))
{
hr = InternetOpenUrlWrap(m_hInternetSession, pwzCacheURL, NULL, 0, INTERNET_FLAG_FROM_CACHE, NULL, &hOpenUrlSession);
if (SUCCEEDED(hr))
{
hr = InternetReadIntoBSTR(hOpenUrlSession, pbstrXML);
InternetCloseHandleWrap(hOpenUrlSession);
}
}
}
else
{
hr = HRESULT_FROM_WIN32(GetLastError());
}
LocalFree(lpCacheEntryInfo);
}
return hr;
}
#define AUTODISC_EXPIRE_TIME 7 /*days*/
HRESULT GetModifiedAndExpiredDates(IN FILETIME * pftExpireTime, IN FILETIME * pftLastModifiedTime)
{
SYSTEMTIME stCurrentUTC;
ULARGE_INTEGER uliTimeMath;
ULARGE_INTEGER uliExpireTime;
GetSystemTime(&stCurrentUTC);
SystemTimeToFileTime(&stCurrentUTC, pftLastModifiedTime);
*pftExpireTime = *pftLastModifiedTime;
uliTimeMath.HighPart = pftExpireTime->dwHighDateTime;
uliTimeMath.LowPart = pftExpireTime->dwLowDateTime;
uliExpireTime.QuadPart = 1000000; // One Second;
uliExpireTime.QuadPart *= (SECONDS_IN_ONE_DAY * AUTODISC_EXPIRE_TIME);
uliTimeMath.QuadPart += uliExpireTime.QuadPart;
pftExpireTime->dwHighDateTime = uliTimeMath.HighPart;
pftExpireTime->dwLowDateTime = uliTimeMath.LowPart;
return S_OK;
}
HRESULT CAccountDiscoveryBase::_CacheResults(IN LPCWSTR pwzCacheURL, IN BSTR bstrXML)
{
HRESULT hr = S_OK;
WCHAR wzPath[MAX_PATH];
hr = CreateUrlCacheEntryWrap(pwzCacheURL, (lstrlenW(bstrXML) + 1), L"xml", wzPath, 0);
if (SUCCEEDED(hr))
{
HANDLE hFile;
hr = CreateFileHrWrap(wzPath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL, &hFile);
if (SUCCEEDED(hr))
{
LPSTR pszAnsiXML = AllocStringFromBStr(bstrXML);
if (pszAnsiXML)
{
DWORD cchWritten;
hr = WriteFileWrap(hFile, pszAnsiXML, (lstrlenA(pszAnsiXML) + 1), &cchWritten, NULL);
LocalFree(pszAnsiXML);
}
else
{
hr = E_OUTOFMEMORY;
}
CloseHandle(hFile);
if (SUCCEEDED(hr))
{
FILETIME ftExpireTime;
FILETIME ftLastModifiedTime;
GetModifiedAndExpiredDates(&ftExpireTime, &ftLastModifiedTime);
hr = CommitUrlCacheEntryWrap(pwzCacheURL, wzPath, ftExpireTime, ftLastModifiedTime, NORMAL_CACHE_ENTRY, NULL, 0, NULL, pwzCacheURL);
}
}
}
return hr;
}
LPCWSTR _GetNextDomain(IN LPCWSTR pwszDomain)
{
LPCWSTR pwzNext = NULL;
pwszDomain = StrChrW(pwszDomain, CH_EMAIL_DOMAIN_SEPARATOR);
if (pwszDomain) // We did find the next one.
{
pwszDomain = CharNext(pwszDomain); // Skip past '.'
if (StrChrW(pwszDomain, CH_EMAIL_DOMAIN_SEPARATOR)) // is this the primary domain "com"?
{
// No, so that's good. Because we can't search for JoeUser@com.
pwzNext = pwszDomain;
}
}
return pwzNext;
}
#define SZ_HTTP_SCHEME L"http://"
HRESULT GetDomainFromURL(IN LPCWSTR pwzURL, IN LPWSTR pwzDomain, IN int cchSize)
{
StrCpyNW(pwzDomain, pwzURL, cchSize);
if (!StrCmpNIW(SZ_HTTP_SCHEME, pwzDomain, (ARRAYSIZE(SZ_HTTP_SCHEME) - 1)))
{
StrCpyNW(pwzDomain, &pwzURL[(ARRAYSIZE(SZ_HTTP_SCHEME) - 1)], cchSize);
LPWSTR pszRemovePath = StrChrW(pwzDomain, L'/');
if (pszRemovePath)
{
pszRemovePath[0] = 0;
}
}
return S_OK;
}
HRESULT CAccountDiscoveryBase::_UseOptimizedService(IN LPCWSTR pwzServiceURL, IN LPCWSTR pwzDomain, IN BSTR * pbstrXML, IN DWORD dwFlags)
{
WCHAR szURL[MAX_URL_STRING];
HINTERNET hOpenUrlSession;
wnsprintfW(szURL, ARRAYSIZE(szURL), L"%lsDomain=%ls", pwzServiceURL, pwzDomain);
HRESULT hr = _CheckInCache(szURL, pbstrXML);
if (FAILED(hr))
{
WCHAR szDomain[MAX_PATH];
if (SUCCEEDED(GetDomainFromURL(szURL, szDomain, ARRAYSIZE(szDomain))))
{
_SendStatusMessage(IDS_STATUS_CONNECTING_TO, szDomain);
}
// NOTE: The web server may want to redirect to an https URL for additional security.
// We need to pass the INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS to HttpOpenRequest
// or HttpSendRequest() will fail with ERROR_INTERNET_HTTP_TO_HTTPS_ON_REDIR
// INTERNET_FLAG_IGNORE_CERT_CN_INVALID is another option we may want to use.
hr = InternetOpenUrlWrap(m_hInternetSession, szURL, NULL, 0, INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS, NULL, &hOpenUrlSession);
if (SUCCEEDED(hr))
{
hr = InternetReadIntoBSTR(hOpenUrlSession, pbstrXML);
if (SUCCEEDED(hr))
{
DWORD cbSize = (sizeof(INTERNET_CACHE_ENTRY_INFO) + 4048);
LPINTERNET_CACHE_ENTRY_INFO lpCacheEntryInfo = (LPINTERNET_CACHE_ENTRY_INFO) LocalAlloc(LPTR, cbSize);
HRESULT hr = E_FAIL;
if (lpCacheEntryInfo)
{
lpCacheEntryInfo->dwStructSize = cbSize;
if (GetUrlCacheEntryInfo(szURL, lpCacheEntryInfo, &cbSize))
{
lpCacheEntryInfo->CacheEntryType |= CACHE_ENTRY_EXPTIME_FC;
GetModifiedAndExpiredDates(&(lpCacheEntryInfo->ExpireTime), &(lpCacheEntryInfo->LastModifiedTime));
SetUrlCacheEntryInfo(szURL, lpCacheEntryInfo, (CACHE_ENTRY_EXPTIME_FC | CACHE_ENTRY_MODTIME_FC));
}
else
{
hr = HRESULT_FROM_WIN32(GetLastError());
}
LocalFree(lpCacheEntryInfo);
}
}
InternetCloseHandleWrap(hOpenUrlSession);
}
}
return hr;
}
// We turn this off because JoshCo said that it would make
// it hard to turn it into an international standard.
// There are cases where [email protected] may trust
// organization.co.uk but not co.uk.
//#define FEATURE_WALK_UP_DOMAIN
HRESULT CAccountDiscoveryBase::_GetInfo(IN BSTR bstrXMLRequest, IN BSTR bstrEmail, IN BSTR * pbstrXML, IN DWORD dwFlags)
{
HRESULT hr = E_INVALIDARG;
LPCWSTR pwszDomain = StrChrW(bstrEmail, CH_EMAIL_AT);
if (pwszDomain)
{
pwszDomain = CharNext(pwszDomain); // Skip past the '@'
IAutoDiscoveryProvider * pProviders;
hr = _getPrimaryProviders(bstrEmail, &pProviders);
if (SUCCEEDED(hr))
{
long nTotal = 0;
VARIANT varIndex;
varIndex.vt = VT_I4;
hr = pProviders->get_length(&nTotal);
hr = E_FAIL;
for (varIndex.lVal = 0; FAILED(hr) && (varIndex.lVal < nTotal); varIndex.lVal++)
{
CComBSTR bstrDomain;
hr = pProviders->get_item(varIndex, &bstrDomain);
if (SUCCEEDED(hr))
{
hr = _GetInfoFromDomainWithSubdirAndCacheCheck(bstrXMLRequest, bstrEmail, bstrDomain, pbstrXML, dwFlags, SZ_ADSERVER_XMLFILE);
}
}
pProviders->Release();
}
// Do we still need to find the settings and should we fall back
// to trying public internet servers that can try to find the email mappings?
// We also only want to try one of the public servers if the domain is not an internet
// domain because we don't want to send intranet email server names outside of
// the corp-net to public servers. We detect intranet type servers by the lack
// of a 'period' in the name. For Example: JustUser@internetemailserver vs
// [email protected].
if (FAILED(hr) && (ADDN_CONFIGURE_EMAIL_FALLBACK & dwFlags) &&
(SHRegGetBoolUSValue(SZ_REGKEY_AUTODISCOVERY, SZ_REGVALUE_TEST_INTRANETS, FALSE, /*default:*/FALSE) ||
StrChrW(pwszDomain, CH_EMAIL_DOMAIN_SEPARATOR)))
{
hr = _getSecondaryProviders(bstrEmail, &pProviders, dwFlags);
if (SUCCEEDED(hr))
{
long nTotal = 0;
VARIANT varIndex;
varIndex.vt = VT_I4;
hr = pProviders->get_length(&nTotal);
hr = E_FAIL;
for (varIndex.lVal = 0; FAILED(hr) && (varIndex.lVal < nTotal); varIndex.lVal++)
{
CComBSTR bstrURL;
hr = pProviders->get_item(varIndex, &bstrURL);
if (SUCCEEDED(hr))
{
hr = _UseOptimizedService(bstrURL, pwszDomain, pbstrXML, dwFlags);
}
}
pProviders->Release();
}
}
}
return hr;
}
// We turn this off because JoshCo said that it would make
// it hard to turn it into an international standard.
// There are cases where [email protected] may trust
// organization.co.uk but not co.uk.
//#define FEATURE_WALK_UP_DOMAIN
HRESULT CAccountDiscoveryBase::_getPrimaryProviders(IN LPCWSTR pwzEmailAddress, OUT IAutoDiscoveryProvider ** ppProviders)
{
HRESULT hr = E_INVALIDARG;
if (ppProviders)
{
*ppProviders = NULL;
if (!m_hdpaPrimary && pwzEmailAddress)
{
LPCWSTR pwszDomain = StrChrW(pwzEmailAddress, CH_EMAIL_AT);
if (pwszDomain)
{
pwszDomain = CharNext(pwszDomain); // Skip past the "@"
if (pwszDomain[0])
{
// While we still have a domain and it's at least a second level domain...
if (pwszDomain)
{
WCHAR wzDomain[INTERNET_MAX_HOST_NAME_LENGTH];
// First we try "AutoDiscovery.<domainname>". That way, if admins receive a large amount
// of traffic, they can change their DNS to have a "AutoDiscovery" alias that points to
// a web server of their choosing to handle this traffic.
wnsprintfW(wzDomain, ARRAYSIZE(wzDomain), L"autodiscover.%ls", pwszDomain);
if (SUCCEEDED(AddHDPA_StrDup(wzDomain, &m_hdpaPrimary)))
{
// Add ballback server here. If the administrators don't want to do all the work
// of having another machine or creating a DNS alias, we will try the main server.
AddHDPA_StrDup(pwszDomain, &m_hdpaPrimary);
}
}
}
}
}
if (m_hdpaPrimary)
{
hr = CADProviders_CreateInstance(m_hdpaPrimary, SAFECAST(this, IObjectWithSite *), ppProviders);
}
}
return hr;
}
HRESULT CAccountDiscoveryBase::_getSecondaryProviders(IN LPCWSTR pwzEmailAddress, OUT IAutoDiscoveryProvider ** ppProviders, IN DWORD dwFlags)
{
HRESULT hr = E_INVALIDARG;
if (ppProviders)
{
*ppProviders = NULL;
if (!m_hdpaSecondary && pwzEmailAddress)
{
LPCWSTR pwszDomain = StrChrW(pwzEmailAddress, CH_EMAIL_AT);
if (pwszDomain)
{
pwszDomain = CharNext(pwszDomain); // Skip past the "@"
if (pwszDomain[0])
{
hr = HRESULT_FROM_WIN32(ERROR_NOT_FOUND);
BOOL fUseGlobalService = SHRegGetBoolUSValue(SZ_REGKEY_AUTODISCOVERY, SZ_REGVALUE_SERVICES_POLICY, FALSE, /*default:*/TRUE);
if (fUseGlobalService)
{
// If this policy is set, then we only want to use the Global Service for certain (i.e. Microsoft Owned)
// domains. If people don't feel confortable with us providing settings for non-Microsoft
// email providers, then we can turn this on and only provide them for Microsoft providers.
if (SHRegGetBoolUSValue(SZ_REGKEY_AUTODISCOVERY, SZ_REGVALUE_MS_ONLY_ADDRESSES, FALSE, /*default:*/FALSE))
{
fUseGlobalService = SHRegGetBoolUSValue(SZ_REGKEY_SERVICESALLOWLIST, pwszDomain, FALSE, /*default:*/FALSE);
}
}
if (fUseGlobalService)
{
HKEY hKey;
DWORD dwError = RegOpenKeyExW(HKEY_LOCAL_MACHINE, SZ_REGKEY_GLOBALSERVICES, 0, KEY_READ, &hKey);
if (ERROR_SUCCESS == dwError)
{
WCHAR szServiceURL[MAX_PATH];
int nIndex = 0;
do
{
WCHAR szValue[MAX_PATH];
DWORD cchValueSize = ARRAYSIZE(szValue);
DWORD dwType = REG_SZ;
DWORD cbDataSize = sizeof(szServiceURL);
dwError = RegEnumValueW(hKey, nIndex, szValue, &cchValueSize, NULL, &dwType, (unsigned char *)szServiceURL, &cbDataSize);
if (ERROR_SUCCESS == dwError)
{
// FEATURE_OPTIMIZED_SERVICE: We can either pass the entire XML request or just put the domain name
// in the QueryString. The QueryString is faster for the server and they can optimize by using it.
AddHDPA_StrDup(szServiceURL, &m_hdpaSecondary);
}
else
{
break;
}
nIndex++;
}
while (1);
RegCloseKey(hKey);
}
}
}
}
}
if (m_hdpaSecondary)
{
hr = CADProviders_CreateInstance(m_hdpaSecondary, SAFECAST(this, IObjectWithSite *), ppProviders);
}
}
return hr;
}
HRESULT CAccountDiscoveryBase::_PerformAutoDiscovery(IN BSTR bstrEmailAddress, IN DWORD dwFlags, IN BSTR bstrXMLRequest, OUT IXMLDOMDocument ** ppXMLResponse)
{
HRESULT hr = E_INVALIDARG;
*ppXMLResponse = NULL;
if (bstrEmailAddress)
{
hr = InternetOpenWrap(SZ_WININET_AGENT_AUTO_DISCOVER, PRE_CONFIG_INTERNET_ACCESS, NULL, NULL, 0, &m_hInternetSession);
if (SUCCEEDED(hr))
{
BSTR bstrXML;
hr = _GetInfo(bstrXMLRequest, bstrEmailAddress, &bstrXML, dwFlags);
if (SUCCEEDED(hr))
{
hr = XMLDOMFromBStr(bstrXML, ppXMLResponse);
SysFreeString(bstrXML);
}
InternetCloseHandleWrap(m_hInternetSession);
m_hInternetSession = NULL;
}
}
return hr;
}
HRESULT CAccountDiscoveryBase::_InternalDiscoverNow(IN BSTR bstrEmailAddress, IN DWORD dwFlags, IN BSTR bstrXMLRequest, OUT IXMLDOMDocument ** ppXMLResponse)
{
HRESULT hr = E_INVALIDARG;
*ppXMLResponse = NULL;
if (bstrEmailAddress)
{
// Does the caller want this done async?
if (m_hwndAsync)
{
// No, so cache the params so we can use them when async.
SysFreeString(m_bstrEmailAsync);
hr = HrSysAllocString(bstrEmailAddress, &m_bstrEmailAsync);
if (SUCCEEDED(hr))
{
SysFreeString(m_bstrXMLRequest);
hr = HrSysAllocString(bstrXMLRequest, &m_bstrXMLRequest);
if (SUCCEEDED(hr))
{
DWORD idThread;
m_dwFlagsAsync = dwFlags;
AddRef();
HANDLE hThread = CreateThread(NULL, 0, CAccountDiscoveryBase::AutoDiscoveryUIThreadProc, this, 0, &idThread);
if (hThread)
{
// We wait WAIT_AUTODISCOVERY_STARTUP_HWND for the new thread to create the COM object
if (m_hCreatedBackgroundTask)
{
DWORD dwRet = WaitForSingleObject(m_hCreatedBackgroundTask, WAIT_AUTODISCOVERY_STARTUP_HWND);
ASSERT(dwRet != WAIT_TIMEOUT);
}
hr = m_hrSuccess;
CloseHandle(hThread);
}
else
{
Release();
}
}
}
}
else
{
// Yes.
hr = _PerformAutoDiscovery(bstrEmailAddress, dwFlags, bstrXMLRequest, ppXMLResponse);
}
}
return hr;
}
DWORD CAccountDiscoveryBase::_AutoDiscoveryUIThreadProc(void)
{
m_hrSuccess = CoInitialize(NULL);
// We need to make sure that the API is installed and
// accessible before we can continue.
if (SUCCEEDED(m_hrSuccess))
{
IXMLDOMDocument * pXMLResponse;
BSTR bstrXMLResponse = NULL;
// Signal the main thread that we have successfully started
if (m_hCreatedBackgroundTask)
SetEvent(m_hCreatedBackgroundTask);
// we give up the remainder of our timeslice here so that our parent thread has time to run
// and will notice that we have signaled the m_hCreatedBackgroundTask event and can therefore return
Sleep(0);
m_hrSuccess = _PerformAutoDiscovery(m_bstrEmailAsync, m_dwFlagsAsync, m_bstrXMLRequest, &pXMLResponse);
if (SUCCEEDED(m_hrSuccess))
{
m_hrSuccess = XMLBStrFromDOM(pXMLResponse, &bstrXMLResponse);
pXMLResponse->Release();
}
_AsyncParseResponse(bstrXMLResponse);
// Whether we succeeded or failed, inform the caller of our results.
if (IsWindow(m_hwndAsync))
{
PostMessage(m_hwndAsync, m_wMsgAsync, m_hrSuccess, (LPARAM)bstrXMLResponse);
}
else
{
SysFreeString(bstrXMLResponse);
}
CoUninitialize();
}
else
{
// Signal the main thread that they can wake up to find that we
// failed to start the async operation.
if (m_hCreatedBackgroundTask)
SetEvent(m_hCreatedBackgroundTask);
}
Release();
return 0;
}
HRESULT CAccountDiscoveryBase::_WorkAsync(IN HWND hwnd, IN UINT wMsg)
{
m_hwndAsync = hwnd;
m_wMsgAsync = wMsg;
return S_OK;
}
//===========================
// *** IUnknown Interface ***
//===========================
ULONG CAccountDiscoveryBase::AddRef()
{
m_cRef++;
return m_cRef;
}
ULONG CAccountDiscoveryBase::Release()
{
ASSERT(m_cRef > 0);
m_cRef--;
if (m_cRef > 0)
return m_cRef;
delete this;
return 0;
}
HRESULT CAccountDiscoveryBase::QueryInterface(REFIID riid, void **ppvObj)
{
static const QITAB qit[] = {
QITABENT(CAccountDiscoveryBase, IObjectWithSite),
{ 0 },
};
return QISearch(this, qit, riid, ppvObj);
}
//===========================
// *** Class Methods ***
//===========================
CAccountDiscoveryBase::CAccountDiscoveryBase() : m_cRef(1)
{
// DllAddRef(); // Done by our inheriting class
// This needs to be allocated in Zero Inited Memory.
// Assert that all Member Variables are inited to Zero.
ASSERT(!m_hInternetSession);
ASSERT(!m_hwndAsync);
ASSERT(!m_wMsgAsync);
ASSERT(!m_dwFlagsAsync);
ASSERT(!m_bstrEmailAsync);
ASSERT(!m_bstrXMLRequest);
ASSERT(S_OK == m_hrSuccess);
ASSERT(!m_hdpaPrimary);
ASSERT(!m_hdpaSecondary);
// We use this event to signal the primary thread that the hwnd was created on the UI thread.
m_hCreatedBackgroundTask = CreateEvent(NULL, FALSE, FALSE, NULL);
}
CAccountDiscoveryBase::~CAccountDiscoveryBase()
{
SysFreeString(m_bstrEmailAsync);
SysFreeString(m_bstrXMLRequest);
if (m_hCreatedBackgroundTask)
CloseHandle(m_hCreatedBackgroundTask);
if (m_hdpaPrimary)
{
DPA_DestroyCallback(m_hdpaPrimary, DPALocalFree_Callback, NULL);
}
if (m_hdpaSecondary)
{
DPA_DestroyCallback(m_hdpaSecondary, DPALocalFree_Callback, NULL);
}
//DllRelease(); // Done by our inheriting class
}