/*****************************************************************************\ FILE: inettime.cpp DESCRIPTION: This file contains the code used to display UI allowing the user to control the feature that updates the computer's clock from an internet NTP time server. BryanSt 3/22/2000 Copyright (C) Microsoft Corp 2000-2000. All rights reserved. \*****************************************************************************/ #include "timedate.h" #include "inettime.h" #include "rc.h" #include #include #include // For DsGetDcName #include // For IDH_DATETIME_* #include // For NetGetJoinInformation() and NETSETUP_JOIN_STATUS #include // For NetGetJoinInformation() and NETSETUP_JOIN_STATUS #include #include #include // For Time APIs #include #include #include #define DECL_CRTFREE #include #define DISALLOW_Assert // Force to use ASSERT instead of Assert #define DISALLOW_DebugMsg // Force to use TraceMsg instead of DebugMsg #include // Reg Keys and Values #define SZ_REGKEY_DATETIME TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\DateTime") #define SZ_REGKEY_DATETIME_SERVERS TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\DateTime\\Servers") #define SZ_REGKEY_W32TIME_SERVICE TEXT("System\\CurrentControlSet\\Services\\W32Time") #define SZ_REGKEY_W32TIME_SERVICE_NTPCLIENT SZ_REGKEY_W32TIME_SERVICE TEXT("\\TimeProviders\\NtpClient") #define SZ_REGKEY_W32TIME_SERVICE_PARAMETERS SZ_REGKEY_W32TIME_SERVICE TEXT("\\Parameters") #define SZ_REGVALUE_INTERNET_FEATURE_AVAILABLE TEXT("Support Internet Time") #define SZ_REGVALUE_TEST_SIMULATENODC TEXT("TestSimulateNoDC") // Used to simulate home scenarios while we have a domain controller #define SZ_REGVALUE_W32TIME_SYNCFROMFLAGS TEXT("Type") #define SZ_REGVALUE_W32TIME_STARTSERVICE TEXT("Start") #define SZ_REGVALUE_NTPSERVERLIST TEXT("NtpServer") // Was "ManualPeerList" in Whistler for a little while. #define SZ_DEFAULT_NTP_SERVER TEXT("time.windows.gov") #define SZ_INDEXTO_CUSTOMHOST TEXT("0") #define SZ_SERVICE_W32TIME TEXT("w32time") #define SZ_DIFFERENT_SYNCFREQUENCY TEXT(",0x1") #define SZ_SYNC_BOTH TEXT("AllSync") #define SZ_SYNC_DS TEXT("NT5DS") #define SZ_SYNC_NTP TEXT("NTP") #define SZ_SYNC_NONE TEXT("NoSync") #define SYNC_ONCE_PER_WEEK 0x93A80 // This is once a week. (Sync every this many seconds) // Flags for "System\CurrentControlSet\Services\W32Time\TimeProviders\NtpClient","SyncFromFlags" #define NCSF_ManualPeerList 0x01 // This means use internet SNTP servers. #define NCSF_DomainHierarchy 0x02 // This means get the time // this feature is turned off temporarily until we can get the perf hit reduced. // The problem is that the call to DsGetDcName() can take up to 15 seconds if // a domain controller can not be found. #define FEATURE_INTERNET_TIME TRUE #define MAX_URL_STRING INTERNET_MAX_URL_LENGTH #define WMUSER_UPDATED_STATUS_TEXT (WM_USER + 11) // If we aren't using the new w32timep.h, then define it our selves. // TODO: Nuke this after \nt\ds\ RIs into main. #ifndef TimeSyncFlag_SoftResync #define TimeSyncFlag_ReturnResult 0x02 #define TimeSyncFlag_Rediscover 0x04 #define ResyncResult_Success 0x00 #define ResyncResult_ChangeTooBig 0x04 typedef struct _W32TIME_NTP_PEER_INFO { unsigned __int32 ulSize; unsigned __int32 ulResolveAttempts; unsigned __int64 u64TimeRemaining; unsigned __int64 u64LastSuccessfulSync; unsigned __int32 ulLastSyncError; unsigned __int32 ulLastSyncErrorMsgId; unsigned __int32 ulValidDataCounter; unsigned __int32 ulAuthTypeMsgId; #ifdef MIDL_PASS [string, unique] wchar_t *wszUniqueName; #else // MIDL_PASS LPWSTR wszUniqueName; #endif // MIDL_PASS unsigned char ulMode; unsigned char ulStratum; unsigned char ulReachability; unsigned char ulPeerPollInterval; unsigned char ulHostPollInterval; } W32TIME_NTP_PEER_INFO, *PW32TIME_NTP_PEER_INFO; typedef struct _W32TIME_NTP_PROVIDER_DATA { unsigned __int32 ulSize; unsigned __int32 ulError; unsigned __int32 ulErrorMsgId; unsigned __int32 cPeerInfo; #ifdef MIDL_PASS [size_is(cPeerInfo)] #endif // MIDL_PASS W32TIME_NTP_PEER_INFO *pPeerInfo; } W32TIME_NTP_PROVIDER_DATA, *PW32TIME_NTP_PROVIDER_DATA; #endif // TimeSyncFlag_SoftResync //============================================================================================================ // *** Globals *** //============================================================================================================ const static DWORD aInternetTimeHelpIds[] = { DATETIME_AUTOSETFROMINTERNET, IDH_DATETIME_AUTOSETFROMINTERNET, DATETIME_INTERNET_SERVER_LABLE, IDH_DATETIME_SERVER_EDIT, DATETIME_INTERNET_SERVER_EDIT, IDH_DATETIME_SERVER_EDIT, DATETIME_INFOTEXTTOP, IDH_DATETIME_INFOTEXT, DATETIME_INFOTEXTPROXY, IDH_DATETIME_INFOTEXT, DATETIME_INTERNET_ERRORTEXT, IDH_DATETIME_INFOTEXT, DATETIME_INTERNET_UPDATENOW, IDH_DATETIME_UPDATEFROMINTERNET, 0, 0 }; #define SZ_HELPFILE_INTERNETTIME TEXT("windows.hlp") #define HINST_THISDLL g_hInst BOOL g_fCustomServer; BOOL g_fWriteAccess = FALSE; // Does the user have the ACLs set correctly to change the HKLM setting to turn the service on/off or change the server hostname? HINSTANCE g_hInstW32Time = NULL; void _LoadW32Time(void) { if (!g_hInstW32Time) { g_hInstW32Time = LoadLibrary(TEXT("w32time.dll")); } } HRESULT StrReplaceToken(IN LPCTSTR pszToken, IN LPCTSTR pszReplaceValue, IN LPTSTR pszString, IN DWORD cchSize) { HRESULT hr = S_OK; LPTSTR pszTempLastHalf = NULL; LPTSTR pszNextToken = pszString; while (0 != (pszNextToken = StrStrI(pszNextToken, pszToken))) { // We found one. LPTSTR pszPastToken = pszNextToken + lstrlen(pszToken); Str_SetPtr(&pszTempLastHalf, pszPastToken); // Keep a copy because we will overwrite it. pszNextToken[0] = 0; // Remove the rest of the string. StrCatBuff(pszString, pszReplaceValue, cchSize); StrCatBuff(pszString, pszTempLastHalf, cchSize); pszNextToken += lstrlen(pszReplaceValue); } Str_SetPtr(&pszTempLastHalf, NULL); return hr; } typedef DWORD (* PFNW32TimeSyncNow) (IN LPCWSTR pwszServer, IN ULONG ulWaitFlag, IN ULONG ulFlags); typedef DWORD (* PFNW32TimeQueryNTPProviderStatus) (IN LPCWSTR pwszServer, IN DWORD dwFlags, IN LPWSTR pwszProvider, OUT W32TIME_NTP_PROVIDER_DATA **ppNTPProviderData); typedef void (* PFNW32TimeBufferFree) (IN LPVOID pvBuffer); typedef HRESULT (* PFNW32TimeQueryConfig) (IN DWORD dwProperty, OUT DWORD * pdwType, IN OUT BYTE * pbConfig, IN OUT DWORD * pdwSize); typedef HRESULT (* PFNW32TimeSetConfig) (IN DWORD dwProperty, IN DWORD dwType, IN BYTE * pbConfig, IN DWORD dwSize); DWORD W32TimeSyncNowDDLoad(IN LPCWSTR pwszServer, IN ULONG ulWaitFlag, IN ULONG ulFlags) { DWORD dwReturn = ERROR_UNEXP_NET_ERR; // Hand delay load until DS RIs. _LoadW32Time(); if (g_hInstW32Time) { PFNW32TimeSyncNow pfnFunction = (PFNW32TimeSyncNow) GetProcAddress(g_hInstW32Time, "W32TimeSyncNow"); if (pfnFunction) { dwReturn = pfnFunction(pwszServer, ulWaitFlag, ulFlags); } } return dwReturn; } DWORD W32TimeQueryNTPProviderStatusDDload(IN LPCWSTR pwszServer, IN DWORD dwFlags, IN LPWSTR pwszProvider, OUT W32TIME_NTP_PROVIDER_DATA **ppNTPProviderData) { DWORD dwReturn = ERROR_UNEXP_NET_ERR; *ppNTPProviderData = NULL; // Hand delay load until DS RIs. _LoadW32Time(); if (g_hInstW32Time) { PFNW32TimeQueryNTPProviderStatus pfnFunction = (PFNW32TimeQueryNTPProviderStatus) GetProcAddress(g_hInstW32Time, "W32TimeQueryNTPProviderStatus"); if (pfnFunction) { dwReturn = pfnFunction(pwszServer, dwFlags, pwszProvider, ppNTPProviderData); } } return dwReturn; } void W32TimeBufferFreeDDload(IN LPVOID pvBuffer) { // Hand delay load until DS RIs. _LoadW32Time(); if (g_hInstW32Time) { PFNW32TimeBufferFree pfnFunction = (PFNW32TimeBufferFree) GetProcAddress(g_hInstW32Time, "W32TimeBufferFree"); if (pfnFunction) { pfnFunction(pvBuffer); } } } HRESULT W32TimeQueryConfigDDload(IN DWORD dwProperty, OUT DWORD * pdwType, IN OUT BYTE * pbConfig, IN OUT DWORD * pdwSize) { HRESULT hr = ResultFromWin32(ERROR_PROC_NOT_FOUND); // Hand delay load until DS RIs. _LoadW32Time(); if (g_hInstW32Time) { PFNW32TimeQueryConfig pfnFunction = (PFNW32TimeQueryConfig) GetProcAddress(g_hInstW32Time, "W32TimeQueryConfig"); if (pfnFunction) { hr = pfnFunction(dwProperty, pdwType, pbConfig, pdwSize); } } return hr; } HRESULT W32TimeSetConfigDDload(IN DWORD dwProperty, IN DWORD dwType, IN BYTE * pbConfig, IN DWORD dwSize) { HRESULT hr = ResultFromWin32(ERROR_PROC_NOT_FOUND); // Hand delay load until DS RIs. _LoadW32Time(); if (g_hInstW32Time) { PFNW32TimeSetConfig pfnFunction = (PFNW32TimeSetConfig) GetProcAddress(g_hInstW32Time, "W32TimeSetConfig"); if (pfnFunction) { hr = pfnFunction(dwProperty, dwType, pbConfig, dwSize); } } return hr; } // Turn this on when vbl1 RIs and w32timep.h contains W32TimeQueryConfig, W32TimeSetConfig //#define W32TIME_NEWAPIS HRESULT GetW32TimeServer(BOOL fRemoveJunk, LPWSTR pszServer, DWORD cchSize) { DWORD dwType = REG_SZ; DWORD cbSize = (sizeof(pszServer[0]) * cchSize); HRESULT hr = W32TimeQueryConfigDDload(W32TIME_CONFIG_MANUAL_PEER_LIST, &dwType, (BYTE *) pszServer, &cbSize); if (ResultFromWin32(ERROR_PROC_NOT_FOUND) == hr) { DWORD dwError = SHGetValue(HKEY_LOCAL_MACHINE, SZ_REGKEY_W32TIME_SERVICE_PARAMETERS, SZ_REGVALUE_NTPSERVERLIST, NULL, (BYTE *)pszServer, &cbSize); hr = ResultFromWin32(dwError); } if (SUCCEEDED(hr) && fRemoveJunk) { LPWSTR pszJunk = StrStr(pszServer, L",0x"); if (pszJunk) { pszJunk[0] = 0; } pszJunk = StrStr(pszServer, L" "); if (pszJunk) { pszJunk[0] = 0; } } return hr; } HRESULT RemoveDuplicateServers(LPWSTR pszServer, DWORD cchSize) { TCHAR szResults[MAX_URL_STRING]; StrCpyN(szResults, pszServer, ARRAYSIZE(szResults)); LPTSTR pszBeginning = szResults; LPTSTR pszEnd; while (NULL != (pszEnd = StrChr(pszBeginning, TEXT(' ')))) { TCHAR szSearchStr[MAX_PATH]; StrCpyN(szSearchStr, pszBeginning, (DWORD)min(ARRAYSIZE(szSearchStr), (pszEnd - pszBeginning + 1))); pszEnd++; // Skip past the space StrCatBuff(szSearchStr, L" ", ARRAYSIZE(szSearchStr)); StrReplaceToken(szSearchStr, TEXT(""), pszEnd, (ARRAYSIZE(szResults) - (DWORD)(pszEnd - pszBeginning))); szSearchStr[lstrlen(szSearchStr)-1] = 0; StrReplaceToken(szSearchStr, TEXT(""), pszEnd, (ARRAYSIZE(szResults) - (DWORD)(pszEnd - pszBeginning))); pszBeginning = pszEnd; } PathRemoveBlanks(szResults); StrCpyN(pszServer, szResults, cchSize); return S_OK; } BOOL ComparePeers(LPTSTR szPeer1, LPTSTR szPeer2) { BOOL fResult; LPTSTR szFlags1; LPTSTR szFlags2; TCHAR szSave1; TCHAR szSave2; szFlags1 = StrChr(szPeer1, TEXT(',')); if (NULL == szFlags1) { szFlags1 = StrChr(szPeer1, TEXT(' ')); } szFlags2 = StrChr(szPeer2, TEXT(',')); if (NULL == szFlags2) { szFlags2 = StrChr(szPeer2, TEXT(' ')); } if (NULL != szFlags1) { szSave1 = szFlags1[0]; szFlags1[0] = TEXT('\0'); } if (NULL != szFlags2) { szSave2 = szFlags2[0]; szFlags2[0] = TEXT('\0'); } fResult = 0 == StrCmpI(szPeer1, szPeer2); if (NULL != szFlags1) { szFlags1[0] = szSave1; } if (NULL != szFlags2) { szFlags2[0] = szSave2; } return fResult; } BOOL ContainsServer(LPWSTR pwszServerList, LPWSTR pwszServer) { DWORD dwNextServerOffset = 0; LPWSTR pwszNext = pwszServerList; while (NULL != pwszNext) { pwszNext += dwNextServerOffset; if (ComparePeers(pwszNext, pwszServer)) { return TRUE; } pwszNext = StrChr(pwszNext, TEXT(' ')); dwNextServerOffset = 1; } return FALSE; } HRESULT AddTerminators(LPWSTR pszServer, DWORD cchSize) { TCHAR szServer[MAX_URL_STRING]; TCHAR szTemp[MAX_URL_STRING]; szTemp[0] = 0; StrCpyN(szServer, pszServer, ARRAYSIZE(szServer)); LPTSTR pszBeginning = szServer; LPTSTR pszEnd; while (NULL != (pszEnd = StrChr(pszBeginning, TEXT(' ')))) { TCHAR szTemp2[MAX_PATH]; pszEnd[0] = 0; if (!StrStrI(pszBeginning, L",0x")) { wnsprintf(szTemp2, ARRAYSIZE(szTemp2), TEXT("%s,0x1 "), pszBeginning); StrCatBuff(szTemp, szTemp2, ARRAYSIZE(szTemp)); } else { StrCatBuff(szTemp, pszBeginning, ARRAYSIZE(szTemp)); StrCatBuff(szTemp, L" ", ARRAYSIZE(szTemp)); } pszBeginning = &pszEnd[1]; } StrCatBuff(szTemp, pszBeginning, ARRAYSIZE(szTemp)); if (pszBeginning[0] && szTemp[0] && !StrStrI(pszBeginning, L",0x")) { // We need to indicate which kind of sync method StrCatBuff(szTemp, L",0x1", ARRAYSIZE(szTemp)); } StrCpyN(pszServer, szTemp, cchSize); return S_OK; } HRESULT SetW32TimeServer(LPCWSTR pszServer) { TCHAR szServer[MAX_PATH]; StrCpyN(szServer, pszServer, ARRAYSIZE(szServer)); AddTerminators(szServer, ARRAYSIZE(szServer)); RemoveDuplicateServers(szServer, ARRAYSIZE(szServer)); DWORD cbSize = ((lstrlen(szServer) + 1) * sizeof(szServer[0])); HRESULT hr = W32TimeSetConfigDDload(W32TIME_CONFIG_MANUAL_PEER_LIST, REG_SZ, (BYTE *) szServer, cbSize); if (ResultFromWin32(ERROR_PROC_NOT_FOUND) == hr) { DWORD dwError = SHSetValue(HKEY_LOCAL_MACHINE, SZ_REGKEY_W32TIME_SERVICE_PARAMETERS, SZ_REGVALUE_NTPSERVERLIST, REG_SZ, (BYTE *)szServer, cbSize); hr = ResultFromWin32(dwError); } return hr; } HRESULT SetW32TimeSyncFreq(DWORD dwSyncFreq) { #ifndef W32TIME_NEWAPIS return S_OK; #else // W32TIME_NEWAPIS return W32TimeSetConfig(W32TIME_CONFIG_SPECIAL_POLL_INTERVAL, REG_DWORD, (BYTE *) dwSyncFreq, sizeof(dwSyncFreq)); #endif // W32TIME_NEWAPIS } enum eBackgroundThreadAction { eBKAWait = 0, // The bkthread should wait for a command eBKAGetInfo, // The bkthread should get the info on the last sync eBKAUpdate, // The bkthread should start synching eBKAUpdating, // The bkthread is synching now eBKAQuit, // The bkthread should shut down }; class CInternetTime { public: // Forground methods BOOL IsInternetTimeAvailable(void); HRESULT AddInternetPage(void); INT_PTR _AdvancedDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); CInternetTime(HWND hDlg, HWND hwndDate); virtual ~CInternetTime(void); // Background methods void AsyncCheck(void); private: // Private Member Variables HWND m_hDlg; // hwnd of parent (property sheet). HWND m_hwndDate; // Date tab hwnd HWND m_hwndInternet; // InternetTime tab hwnd HCURSOR m_hCurOld; // The old cursor while we display the wait cursor LPTSTR m_pszStatusString; // CROSS-THREAD: This is used to pass between threads. LPTSTR m_pszNextSyncTime; // CROSS-THREAD: This is used to pass between threads. eBackgroundThreadAction m_eAction; // CROSS-THREAD: This is used to pass between threads. // Private Member Functions // Forground methods HRESULT _InitAdvancedPage(HWND hDlg); HRESULT _OnUpdateButton(void); HRESULT _OnUpdateStatusString(void); HRESULT _OnSetFeature(HWND hDlg, BOOL fOn); HRESULT _OnToggleFeature(HWND hDlg); HRESULT _GoGetHelp(void); HRESULT _ResetServer(void); HRESULT _PersistInternetTimeSettings(HWND hDlg); HRESULT _StartServiceAndRefresh(BOOL fStartIfOff); INT_PTR _OnCommandAdvancedPage(HWND hDlg, WPARAM wParam, LPARAM lParam); INT_PTR _OnNotifyAdvancedPage(HWND hDlg, NMHDR * pNMHdr, int idControl); // Background methods HRESULT _ProcessBkThreadActions(void); HRESULT _SyncNow(BOOL fOnlyUpdateInfo); HRESULT _CreateW32TimeSuccessErrorString(DWORD dwError, LPTSTR pszString, DWORD cchSize, LPTSTR pszNextSync, DWORD cchNextSyncSize, LPTSTR pszServerToQuery); HRESULT _GetDateTimeString(FILETIME * pftTimeDate, BOOL fDate, LPTSTR pszString, DWORD cchSize); }; CInternetTime * g_pInternetTime = NULL; // This function is called on the forground thread. CInternetTime::CInternetTime(HWND hDlg, HWND hwndDate) { m_hDlg = hDlg; m_hwndDate = hwndDate; m_eAction = eBKAGetInfo; m_pszStatusString = NULL; m_hwndInternet = NULL; m_hCurOld = NULL; m_pszStatusString = NULL; m_pszNextSyncTime = NULL; } CInternetTime::~CInternetTime() { ENTERCRITICAL; if (m_pszStatusString) { LocalFree(m_pszStatusString); m_pszStatusString = NULL; } if (m_pszNextSyncTime) { LocalFree(m_pszNextSyncTime); m_pszNextSyncTime = NULL; } if (m_hCurOld) { SetCursor(m_hCurOld); m_hCurOld = NULL; } LEAVECRITICAL; } HRESULT FormatMessageWedge(LPCWSTR pwzTemplate, LPWSTR pwzStrOut, DWORD cchSize, ...) { va_list vaParamList; HRESULT hr = S_OK; va_start(vaParamList, cchSize); if (0 == FormatMessageW(FORMAT_MESSAGE_FROM_STRING, pwzTemplate, 0, 0, pwzStrOut, cchSize, &vaParamList)) { hr = ResultFromLastError(); } va_end(vaParamList); return hr; } ///////////////////////////////////////////////////////////////////// // Private Internal Helpers ///////////////////////////////////////////////////////////////////// // This function is called on the forground thread. HRESULT CInternetTime::_OnSetFeature(HWND hDlg, BOOL fOn) { HWND hwndOnOffCheckbox = GetDlgItem(m_hwndInternet, DATETIME_AUTOSETFROMINTERNET); HWND hwndServerLable = GetDlgItem(m_hwndInternet, DATETIME_INTERNET_SERVER_LABLE); HWND hwndServerEdit = GetDlgItem(m_hwndInternet, DATETIME_INTERNET_SERVER_EDIT); CheckDlgButton(hDlg, DATETIME_AUTOSETFROMINTERNET, (fOn ? BST_CHECKED : BST_UNCHECKED)); EnableWindow(hwndOnOffCheckbox, g_fWriteAccess); EnableWindow(hwndServerLable, (g_fWriteAccess ? fOn : FALSE)); EnableWindow(hwndServerEdit, (g_fWriteAccess ? fOn : FALSE)); EnableWindow(GetDlgItem(m_hwndInternet, DATETIME_INTERNET_ERRORTEXT), (g_fWriteAccess ? fOn : FALSE)); EnableWindow(GetDlgItem(m_hwndInternet, DATETIME_INTERNET_UPDATENOW), (g_fWriteAccess ? fOn : FALSE)); EnableWindow(GetDlgItem(m_hwndInternet, DATETIME_INFOTEXTTOP), (g_fWriteAccess ? fOn : FALSE)); if (fOn) { // If the user just turned on the feature and the editbox is empty, // replace the text with the default server name. TCHAR szServer[INTERNET_MAX_HOST_NAME_LENGTH]; if (!GetWindowText(hwndServerEdit, szServer, ARRAYSIZE(szServer)) || !szServer[0]) { SetWindowText(hwndServerEdit, SZ_DEFAULT_NTP_SERVER); } } return S_OK; } // This function is called on the forground thread. HRESULT CInternetTime::_OnToggleFeature(HWND hDlg) { BOOL fIsFeatureOn = ((BST_CHECKED == IsDlgButtonChecked(hDlg, DATETIME_AUTOSETFROMINTERNET)) ? TRUE : FALSE); PropSheet_Changed(GetParent(hDlg), hDlg); // Say we need to enable the apply button return _OnSetFeature(hDlg, fIsFeatureOn); } // This function is called on the forground thread. HRESULT AddServerList(HWND hwndCombo, HKEY hkey, int nIndex) { TCHAR szServer[INTERNET_MAX_HOST_NAME_LENGTH]; DWORD cbSizeServer = sizeof(szServer); TCHAR szIndex[MAX_PATH]; DWORD dwType; wnsprintf(szIndex, ARRAYSIZE(szIndex), TEXT("%d"), nIndex); DWORD dwError = SHGetValue(hkey, NULL, szIndex, &dwType, (void *)szServer, &cbSizeServer); HRESULT hr = ResultFromWin32(dwError); if (SUCCEEDED(hr)) { LPTSTR pszFlags = StrStr(szServer, SZ_DIFFERENT_SYNCFREQUENCY); if (pszFlags) { pszFlags[0] = 0; // Remove the flags. } dwError = ComboBox_AddString(hwndCombo, szServer); if ((dwError == CB_ERR) || (dwError == CB_ERRSPACE)) { hr = E_FAIL; } } return hr; } // This function is called on the forground thread. HRESULT PopulateComboBox(IN HWND hwndCombo, IN BOOL * pfCustomServer) { HRESULT hr; HKEY hkey; *pfCustomServer = FALSE; DWORD dwError = RegOpenKeyEx(HKEY_LOCAL_MACHINE, SZ_REGKEY_DATETIME_SERVERS, 0, KEY_READ, &hkey); hr = ResultFromWin32(dwError); if (SUCCEEDED(hr)) { // Try to add the custom slot. if (SUCCEEDED(AddServerList(hwndCombo, hkey, 0))) { // We do have an existing custom server, so let the caller know so // they know to increase the index by 1. *pfCustomServer = TRUE; } for (int nIndex = 1; SUCCEEDED(hr); nIndex++) { hr = AddServerList(hwndCombo, hkey, nIndex); } RegCloseKey(hkey); } return hr; } // This function is called on the forground thread. HRESULT CInternetTime::_OnUpdateButton(void) { HRESULT hr = S_OK; ENTERCRITICAL; // We don't need to do anything if it's already working on another task. if (eBKAWait == m_eAction) { TCHAR szMessage[2 * MAX_PATH]; TCHAR szTemplate[MAX_PATH]; TCHAR szServer[MAX_PATH]; if (!m_hCurOld) { m_hCurOld = SetCursor(LoadCursor(NULL, IDC_WAIT)); } LoadString(HINST_THISDLL, IDS_IT_WAITFORSYNC, szTemplate, ARRAYSIZE(szTemplate)); GetWindowText(GetDlgItem(m_hwndInternet, DATETIME_INTERNET_SERVER_EDIT), szServer, ARRAYSIZE(szServer)); FormatMessageWedge(szTemplate, szMessage, ARRAYSIZE(szMessage), szServer); SetWindowText(GetDlgItem(m_hwndInternet, DATETIME_INTERNET_ERRORTEXT), szMessage); m_eAction = eBKAUpdate; } LEAVECRITICAL; return hr; } // This function is called on the forground thread. BOOL IsManualPeerListOn(void) { BOOL fIsManualPeerListOn = TRUE; TCHAR szSyncType[MAX_PATH]; DWORD cbSize = sizeof(szSyncType); DWORD dwType; if (ERROR_SUCCESS == SHGetValue(HKEY_LOCAL_MACHINE, SZ_REGKEY_W32TIME_SERVICE_PARAMETERS, SZ_REGVALUE_W32TIME_SYNCFROMFLAGS, &dwType, (LPBYTE)szSyncType, &cbSize)) { if (!StrCmpI(szSyncType, SZ_SYNC_DS) || !StrCmpI(szSyncType, SZ_SYNC_NONE)) { fIsManualPeerListOn = FALSE; } } return fIsManualPeerListOn; } // This function is called on the forground thread. HRESULT CInternetTime::_InitAdvancedPage(HWND hDlg) { DWORD dwType; TCHAR szIndex[MAX_PATH]; HWND hwndServerEdit = GetDlgItem(hDlg, DATETIME_INTERNET_SERVER_EDIT); HWND hwndOnOffCheckbox = GetDlgItem(hDlg, DATETIME_AUTOSETFROMINTERNET); DWORD cbSize = sizeof(szIndex); BOOL fIsFeatureOn = IsManualPeerListOn(); m_hwndInternet = hDlg; HRESULT hr = PopulateComboBox(hwndServerEdit, &g_fCustomServer); // Does the user have access to change the setting in the registry? HKEY hKeyTemp; g_fWriteAccess = FALSE; DWORD dwError = RegOpenKeyEx(HKEY_LOCAL_MACHINE, SZ_REGKEY_W32TIME_SERVICE_PARAMETERS, 0, KEY_WRITE, &hKeyTemp); if (ERROR_SUCCESS == dwError) { // We have access to read & write so we can enable the UI. RegCloseKey(hKeyTemp); dwError = RegCreateKeyEx(HKEY_LOCAL_MACHINE, SZ_REGKEY_DATETIME, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hKeyTemp, NULL); if (ERROR_SUCCESS == dwError) { RegCloseKey(hKeyTemp); dwError = RegCreateKeyEx(HKEY_LOCAL_MACHINE, SZ_REGKEY_DATETIME_SERVERS, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hKeyTemp, NULL); if (ERROR_SUCCESS == dwError) { g_fWriteAccess = TRUE; RegCloseKey(hKeyTemp); } } } dwError = SHGetValue(HKEY_LOCAL_MACHINE, SZ_REGKEY_DATETIME_SERVERS, NULL, &dwType, (void *) szIndex, &cbSize); hr = ResultFromWin32(dwError); if (SUCCEEDED(hr)) { int nIndex = StrToInt(szIndex); if (!g_fCustomServer) { nIndex -= 1; // We don't have slot zero (the custom server), so reduce the index. } ComboBox_SetCurSel(hwndServerEdit, nIndex); } _OnSetFeature(hDlg, fIsFeatureOn); if (fIsFeatureOn) { // The feature is on, so select the text and set focus // to the combobox. ComboBox_SetEditSel(hwndServerEdit, 0, (LPARAM)-1); // Select all the Text SetFocus(hwndServerEdit); } else { // The feature is off so set focus to the checkbox. SetFocus(hwndOnOffCheckbox); } ENTERCRITICAL; if (m_pszStatusString) { SetWindowText(GetDlgItem(m_hwndInternet, DATETIME_INTERNET_ERRORTEXT), m_pszStatusString); } if (m_pszNextSyncTime) { SetWindowText(GetDlgItem(m_hwndInternet, DATETIME_INFOTEXTTOP), m_pszNextSyncTime); } LEAVECRITICAL; return S_OK; } // This function is called on the background thread. HRESULT SetSyncFlags(DWORD dwSyncFromFlags) { LPCTSTR pszFlags = SZ_SYNC_BOTH; switch (dwSyncFromFlags) { case NCSF_DomainHierarchy: pszFlags = SZ_SYNC_DS; break; case NCSF_ManualPeerList: pszFlags = SZ_SYNC_NTP; break; case 0x00000000: pszFlags = SZ_SYNC_NONE; break; }; DWORD cbSize = ((lstrlen(pszFlags) + 1) * sizeof(pszFlags[0])); DWORD dwError = SHSetValue(HKEY_LOCAL_MACHINE, SZ_REGKEY_W32TIME_SERVICE_PARAMETERS, SZ_REGVALUE_W32TIME_SYNCFROMFLAGS, REG_SZ, (LPBYTE)pszFlags, cbSize); HRESULT hr = ResultFromWin32(dwError); return hr; } /*****************************************************************************\ DESCRIPTION: The following reg keys determine if the W32Time service is on or off: HKLM,"System\CurrentControlSet\Services\W32Time\" "Start", 0x00000002 (Manual?) 0x0000003 (Automatic?) "Type", 0x00000020 (Nt5Ds?) 0x0000120 (NTP?) HKLM,"System\CurrentControlSet\Services\W32Time\Parameters" "LocalNTP", 0x00000000 (Off) 0x0000001 (On) "Type", "Nt5Ds" (NTP off) "NTP" (NTP?) HKLM,"System\CurrentControlSet\Services\W32Time\TimeProviders\NTPClient" "SyncFromFlags", 0x00000001 (???) 0x0000002 (???) The following reg keys determine which NTP server to use: HKLM,"System\CurrentControlSet\Services\W32Time\TimeProviders\NTPClient" "ManualPeerList", REG_SZ_EXPAND "time.nist.gov" \*****************************************************************************/ // This function is called on the forground thread. HRESULT CInternetTime::_PersistInternetTimeSettings(HWND hDlg) { BOOL fIsFeatureOn = ((BST_CHECKED == IsDlgButtonChecked(hDlg, DATETIME_AUTOSETFROMINTERNET)) ? TRUE : FALSE); DWORD dwSyncFromFlags = (fIsFeatureOn ? NCSF_ManualPeerList : 0 /*none*/ ); DWORD dwError; DWORD cbSize; HRESULT hr = SetSyncFlags(dwSyncFromFlags); HWND hwndServerEdit = GetDlgItem(hDlg, DATETIME_INTERNET_SERVER_EDIT); // No, so the editbox has a customer server. We need to store the custom server. TCHAR szServer[INTERNET_MAX_HOST_NAME_LENGTH]; TCHAR szServerReg[INTERNET_MAX_HOST_NAME_LENGTH]; szServer[0] = 0; GetWindowText(hwndServerEdit, szServer, ARRAYSIZE(szServer)); // Here, we want to detect the case where someone typed in the same name as // in the drop down list. So if "time.nist.gov" is in the list, we want to // select it instead of saving another "time.nist.gov" in the custom slot. int nDup = ComboBox_FindString(hwndServerEdit, 0, szServer); if (CB_ERR != nDup) { ComboBox_SetCurSel(hwndServerEdit, nDup); } // The ",0x1" denotes that we want to sync less frequently and not use the NTP RFC's // sync frequency. StrCpyN(szServerReg, szServer, ARRAYSIZE(szServerReg)); StrCatBuff(szServerReg, SZ_DIFFERENT_SYNCFREQUENCY, ARRAYSIZE(szServerReg)); // Write the default server name into "ManualPeerList", which is where the // service reads it from. SetW32TimeServer(szServerReg); if (!StrCmpI(szServerReg, L"time.windows.com")) { // We need time.windows.com to scale to a large number of users, so don't let it sync more frequently // than once a week. SetW32TimeSyncFreq(SYNC_ONCE_PER_WEEK); } int nIndex = ComboBox_GetCurSel(hwndServerEdit); if (CB_ERR == nIndex) // Is anything selected? { cbSize = ((lstrlenW(szServer) + 1) * sizeof(szServer[0])); dwError = SHSetValueW(HKEY_LOCAL_MACHINE, SZ_REGKEY_DATETIME_SERVERS, SZ_INDEXTO_CUSTOMHOST, REG_SZ, (void *)szServer, cbSize); nIndex = 0; } else { if (!g_fCustomServer) { nIndex += 1; // Push the index down by one because the listbox doesn't have a custom server } } TCHAR szIndex[MAX_PATH]; wnsprintf(szIndex, ARRAYSIZE(szIndex), TEXT("%d"), nIndex); cbSize = ((lstrlenW(szIndex) + 1) * sizeof(szIndex[0])); dwError = SHSetValueW(HKEY_LOCAL_MACHINE, SZ_REGKEY_DATETIME_SERVERS, NULL, REG_SZ, (void *)szIndex, cbSize); _StartServiceAndRefresh(fIsFeatureOn); // Make sure the service is on and make it update it's settings. return S_OK; } // This function is called on either the forground or background thread. HRESULT CInternetTime::_StartServiceAndRefresh(BOOL fStartIfOff) { HRESULT hr = S_OK; // We need to let the service know that settings may have changed. If the user // wanted this feature on, then we should make sure the service starts. SC_HANDLE hServiceMgr = OpenSCManager(NULL, NULL, (SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE | SC_MANAGER_QUERY_LOCK_STATUS)); if (hServiceMgr) { SC_HANDLE hService = OpenService(hServiceMgr, SZ_SERVICE_W32TIME, (/*SERVICE_START |*/ SERVICE_PAUSE_CONTINUE)); DWORD dwServiceStartMode = 0x00000002; // This will cause the service to start Automatically on reboot. DWORD dwError = SHSetValueW(HKEY_LOCAL_MACHINE, SZ_REGKEY_W32TIME_SERVICE, SZ_REGVALUE_W32TIME_STARTSERVICE, REG_DWORD, &dwServiceStartMode, sizeof(dwServiceStartMode)); if (hService) { SERVICE_STATUS serviceStatus = {0}; BOOL fSucceeded; if (fStartIfOff) { // We should start up the service in case it's not turned on. fSucceeded = StartService(hService, 0, NULL); hr = (fSucceeded ? S_OK : ResultFromLastError()); if (ResultFromWin32(ERROR_SERVICE_ALREADY_RUNNING) == hr) { hr = S_OK; // We can ignore this err value because it's benign. } } // Tell the service to re-read the registry entry settings. fSucceeded = ControlService(hService, SERVICE_CONTROL_PARAMCHANGE, &serviceStatus); CloseServiceHandle(hService); } else { hr = ResultFromLastError(); } CloseServiceHandle(hServiceMgr); } else { hr = ResultFromLastError(); } return hr; } HRESULT CInternetTime::_OnUpdateStatusString(void) { HRESULT hr = S_OK; ENTERCRITICAL; // Be safe while using m_pszStatusString SetWindowText(GetDlgItem(m_hwndInternet, DATETIME_INTERNET_ERRORTEXT), (m_pszStatusString ? m_pszStatusString : TEXT(""))); SetWindowText(GetDlgItem(m_hwndInternet, DATETIME_INFOTEXTTOP), (m_pszNextSyncTime ? m_pszNextSyncTime : TEXT(""))); if (m_hCurOld) { SetCursor(m_hCurOld); m_hCurOld = NULL; } LEAVECRITICAL; return S_OK; } HRESULT HrShellExecute(HWND hwnd, LPCTSTR lpVerb, LPCTSTR lpFile, LPCTSTR lpParameters, LPCTSTR lpDirectory, INT nShowCmd) { HRESULT hr = S_OK; HINSTANCE hReturn = ShellExecute(hwnd, lpVerb, lpFile, lpParameters, lpDirectory, nShowCmd); if ((HINSTANCE)32 > hReturn) { hr = ResultFromLastError(); } return hr; } // This function is called on the forground thread. HRESULT CInternetTime::_GoGetHelp(void) { HRESULT hr = S_OK; TCHAR szCommand[MAX_PATH]; LoadString(HINST_THISDLL, IDS_TROUBLESHOOT_INTERNETIME, szCommand, ARRAYSIZE(szCommand)); HrShellExecute(m_hwndDate, NULL, szCommand, NULL, NULL, SW_NORMAL); return S_OK; } // This function is called on the forground thread. INT_PTR CInternetTime::_OnCommandAdvancedPage(HWND hDlg, WPARAM wParam, LPARAM lParam) { BOOL fHandled = 1; // Not handled (WM_COMMAND seems to be different) WORD wMsg = GET_WM_COMMAND_CMD(wParam, lParam); WORD idCtrl = GET_WM_COMMAND_ID(wParam, lParam); switch (idCtrl) { case DATETIME_AUTOSETFROMINTERNET: switch (wMsg) { case BN_CLICKED: _OnToggleFeature(hDlg); break; } break; case DATETIME_INTERNET_SERVER_EDIT: switch (wMsg) { case EN_CHANGE: case CBN_EDITCHANGE: case CBN_SELCHANGE: PropSheet_Changed(GetParent(hDlg), hDlg); break; } break; case DATETIME_INTERNET_UPDATENOW: switch (wMsg) { case BN_CLICKED: _OnUpdateButton(); break; } break; } return fHandled; } // This function is called on the forground thread. INT_PTR CInternetTime::_OnNotifyAdvancedPage(HWND hDlg, NMHDR * pNMHdr, int idControl) { BOOL fHandled = 1; // Not handled (WM_COMMAND seems to be different) if (pNMHdr) { switch (pNMHdr->idFrom) { case 0: { switch (pNMHdr->code) { case PSN_APPLY: _PersistInternetTimeSettings(hDlg); break; } break; } default: switch (pNMHdr->code) { case NM_RETURN: case NM_CLICK: { PNMLINK pNMLink = (PNMLINK) pNMHdr; if (!StrCmpW(pNMLink->item.szID, L"HelpMe")) { _GoGetHelp(); } break; } } break; } } return fHandled; } // This function is called on the forground thread. EXTERN_C INT_PTR CALLBACK AdvancedDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { if (g_pInternetTime) { return g_pInternetTime->_AdvancedDlgProc(hDlg, uMsg, wParam, lParam); } return (TRUE); } // This function is called on the forground thread. INT_PTR CInternetTime::_AdvancedDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_INITDIALOG: _InitAdvancedPage(hDlg); break; case WM_DESTROY: break; case WM_NOTIFY: _OnNotifyAdvancedPage(hDlg, (NMHDR *)lParam, (int) wParam); break; case WM_COMMAND: _OnCommandAdvancedPage(hDlg, wParam, lParam); break; case WMUSER_UPDATED_STATUS_TEXT: _OnUpdateStatusString(); break; case WM_HELP: WinHelp((HWND) ((LPHELPINFO) lParam)->hItemHandle, SZ_HELPFILE_INTERNETTIME, HELP_WM_HELP, (DWORD_PTR) aInternetTimeHelpIds); break; case WM_CONTEXTMENU: // right mouse click WinHelp((HWND) wParam, SZ_HELPFILE_INTERNETTIME, HELP_CONTEXTMENU, (DWORD_PTR) aInternetTimeHelpIds); break; default: return FALSE; } return (TRUE); } // This function is called on the forground thread. HRESULT CInternetTime::AddInternetPage(void) { HRESULT hr = S_OK; PROPSHEETPAGE pspAdvanced; INITCOMMONCONTROLSEX initComctl32; initComctl32.dwSize = sizeof(initComctl32); initComctl32.dwICC = (ICC_STANDARD_CLASSES | ICC_LINK_CLASS); InitCommonControlsEx(&initComctl32); // Register the comctl32 LinkWindow pspAdvanced.dwSize = sizeof(PROPSHEETPAGE); pspAdvanced.dwFlags = PSP_DEFAULT; pspAdvanced.hInstance = HINST_THISDLL; pspAdvanced.pszTemplate = MAKEINTRESOURCE(DLG_ADVANCED); pspAdvanced.pfnDlgProc = AdvancedDlgProc; pspAdvanced.lParam = (LPARAM) this; if (IsWindow(m_hDlg)) { HPROPSHEETPAGE hPropSheet = CreatePropertySheetPage(&pspAdvanced); if (hPropSheet) { PropSheet_AddPage(m_hDlg, hPropSheet); } } return hr; } ///////////////////////////////////////////////////////////////////// // Background Thread Functions ///////////////////////////////////////////////////////////////////// /////////// // These functions will cause the background thread to sync /////////// #define SECONDS_FROM_100NS 10000000 // ulResolveAttempts -- the number of times the NTP provider has attempted to // resolve this peer unsuccessfully. Setting this // value to 0 indicates that the peer has been successfully // resolved. // u64TimeRemaining -- the number of 100ns intervals until the provider will // poll this peer again // u64LastSuccessfulSync -- the number of 100ns intervals since (0h 1-Jan 1601) // ulLastSyncError -- S_OK if the last sync with this peer was successful, otherwise, // the error that occurred attempting to sync // ulLastSyncErrorMsgId -- the resource identifier of a string representing the last // error that occurred syncing from this peer. 0 if there is no // string associated with this error. // This function is called on the background thread. HRESULT W32TimeGetErrorInfoWrap(UINT * pulResolveAttempts, ULONG * pulValidDataCounter, UINT64 * pu64TimeRemaining, UINT64 * pu64LastSuccessfulSync, HRESULT * phrLastSyncError, UINT * pulLastSyncErrorMsgId, LPTSTR pszServer, DWORD cchSize, LPTSTR pszServerToQuery) { HRESULT hr = S_OK; *pulResolveAttempts = 0; *pulValidDataCounter = 0; *pu64TimeRemaining = 0; *pu64LastSuccessfulSync = 0; *phrLastSyncError = E_FAIL; *pulLastSyncErrorMsgId = 0; pszServer[0] = 0; // NOTE: Server should return ERROR_TIME_SKEW if time is too far out of date to sync. W32TIME_NTP_PROVIDER_DATA * pProviderInfo = NULL; DWORD dwError = W32TimeQueryNTPProviderStatusDDload(SZ_COMPUTER_LOCAL, 0, SZ_NTPCLIENT, &pProviderInfo); if ((ERROR_SUCCESS == dwError) && pProviderInfo) { *phrLastSyncError = pProviderInfo->ulError; *pulLastSyncErrorMsgId = pProviderInfo->ulErrorMsgId; DWORD dwMostRecentlySyncdPeerIndex = 0xFFFFFFFF; UINT64 u64LastSuccessfulSync = 0; for (DWORD dwIndex = 0; dwIndex < pProviderInfo->cPeerInfo; dwIndex++) { // Only want to query those peers which we explictly sync'd from. // If we haven't explicitly requested a sync, we'll just take the most recently sync'd peer. if (NULL == pszServerToQuery || ComparePeers(pszServerToQuery, pProviderInfo->pPeerInfo[dwIndex].wszUniqueName)) { if (u64LastSuccessfulSync <= pProviderInfo->pPeerInfo[dwIndex].u64LastSuccessfulSync) { dwMostRecentlySyncdPeerIndex = dwIndex; u64LastSuccessfulSync = pProviderInfo->pPeerInfo[dwIndex].u64LastSuccessfulSync; } } } if (dwMostRecentlySyncdPeerIndex < pProviderInfo->cPeerInfo && pProviderInfo->pPeerInfo) { *pulResolveAttempts = pProviderInfo->pPeerInfo[dwMostRecentlySyncdPeerIndex].ulResolveAttempts; *pulValidDataCounter = pProviderInfo->pPeerInfo[dwMostRecentlySyncdPeerIndex].ulValidDataCounter; *pu64TimeRemaining = pProviderInfo->pPeerInfo[dwMostRecentlySyncdPeerIndex].u64TimeRemaining; *pu64LastSuccessfulSync = pProviderInfo->pPeerInfo[dwMostRecentlySyncdPeerIndex].u64LastSuccessfulSync; *phrLastSyncError = pProviderInfo->pPeerInfo[dwMostRecentlySyncdPeerIndex].ulLastSyncError; *pulLastSyncErrorMsgId = pProviderInfo->pPeerInfo[dwMostRecentlySyncdPeerIndex].ulLastSyncErrorMsgId; if (pProviderInfo->pPeerInfo[dwMostRecentlySyncdPeerIndex].wszUniqueName) { StrCpyN(pszServer, pProviderInfo->pPeerInfo[dwMostRecentlySyncdPeerIndex].wszUniqueName, cchSize); // Strip off non-user friendly information which may have been tacked on to the peer name: LPTSTR pszJunk = StrStrW(pszServer, L" ("); if (pszJunk) { pszJunk[0] = 0; } pszJunk = StrStrW(pszServer, L","); if (pszJunk) { pszJunk[0] = 0; } } } else { *phrLastSyncError = HRESULT_FROM_WIN32(ERROR_TIMEOUT); if (NULL != pszServerToQuery) { StrCpyN(pszServer, pszServerToQuery, cchSize); } } W32TimeBufferFreeDDload(pProviderInfo); } else { hr = ResultFromWin32(dwError); } return hr; } // pftTimeRemaining will be returned with the time/date of the next sync, not time from now. HRESULT W32TimeGetErrorInfoWrapHelper(UINT * pulResolveAttempts, ULONG * pulValidDataCounter, FILETIME * pftTimeRemaining, FILETIME * pftLastSuccessfulSync, HRESULT * phrLastSyncError, UINT * pulLastSyncErrorMsgId, LPTSTR pszServer, DWORD cchSize, LPTSTR pszServerToQuery) { UINT64 * pu64LastSuccessfulSync = (UINT64 *) pftLastSuccessfulSync; UINT64 u64TimeRemaining; HRESULT hr = W32TimeGetErrorInfoWrap(pulResolveAttempts, pulValidDataCounter, &u64TimeRemaining, pu64LastSuccessfulSync, phrLastSyncError, pulLastSyncErrorMsgId, pszServer, cchSize, pszServerToQuery); if (SUCCEEDED(hr)) { SYSTEMTIME stCurrent; FILETIME ftCurrent; GetSystemTime(&stCurrent); SystemTimeToFileTime(&stCurrent, &ftCurrent); ULONGLONG * pNextSync = (ULONGLONG *) pftTimeRemaining; ULONGLONG * pCurrent = (ULONGLONG *) &ftCurrent; *pNextSync = (*pCurrent + u64TimeRemaining); } return hr; } // This function is called on the background thread. HRESULT CInternetTime::_GetDateTimeString(FILETIME * pftTimeDate, BOOL fDate, LPTSTR pszString, DWORD cchSize) { HRESULT hr = S_OK; TCHAR szFormat[MAX_PATH]; pszString[0] = 0; if (GetLocaleInfo(LOCALE_USER_DEFAULT, (fDate ? LOCALE_SSHORTDATE : LOCALE_STIMEFORMAT), szFormat, ARRAYSIZE(szFormat))) { SYSTEMTIME stTimeDate; if (FileTimeToSystemTime(pftTimeDate, &stTimeDate)) { if (fDate) { if (!GetDateFormat(LOCALE_USER_DEFAULT, 0, &stTimeDate, szFormat, pszString, cchSize)) { hr = ResultFromLastError(); } } else { if (!GetTimeFormat(LOCALE_USER_DEFAULT, TIME_NOSECONDS, &stTimeDate, szFormat, pszString, cchSize)) { hr = ResultFromLastError(); } } } else { hr = ResultFromLastError(); } } else { hr = ResultFromLastError(); } return hr; } HRESULT _CleanUpErrorString(LPWSTR pszTemp4, DWORD cchSize) { PathRemoveBlanks(pszTemp4); while (TRUE) { DWORD cchSizeTemp = lstrlen(pszTemp4); if (cchSizeTemp && ((TEXT('\n') == pszTemp4[cchSizeTemp-1]) || (13 == pszTemp4[cchSizeTemp-1]))) { pszTemp4[cchSizeTemp-1] = 0; } else { break; } } return S_OK; } // This function is called on the background thread. HRESULT CInternetTime::_CreateW32TimeSuccessErrorString(DWORD dwError, LPTSTR pszString, DWORD cchSize, LPTSTR pszNextSync, DWORD cchNextSyncSize, LPTSTR pszServerToQuery) { HRESULT hr = S_OK; UINT ulResolveAttempts = 0; FILETIME ftTimeRemainingUTC = {0}; FILETIME ftLastSuccessfulSyncUTC = {0}; FILETIME ftTimeRemaining = {0}; FILETIME ftLastSuccessfulSync = {0}; UINT ulLastSyncErrorMsgId = 0; TCHAR szServer[MAX_PATH]; HRESULT hrLastSyncError = 0; ULONG ulValidDataCounter = 0; TCHAR szTemplate[MAX_PATH]; TCHAR szTemp1[MAX_PATH]; TCHAR szTemp2[MAX_PATH]; TCHAR szTemp3[MAX_URL_STRING]; TCHAR szTemp4[MAX_URL_STRING]; pszString[0] = 0; pszNextSync[0] = 0; hr = W32TimeGetErrorInfoWrapHelper(&ulResolveAttempts, &ulValidDataCounter, &ftTimeRemainingUTC, &ftLastSuccessfulSyncUTC, &hrLastSyncError, &ulLastSyncErrorMsgId, szServer, ARRAYSIZE(szServer), pszServerToQuery); if (SUCCEEDED(hr)) { // ftTimeRemaining and ftLastSuccessfulSync are stored in UTC, so translate to our time zone. FileTimeToLocalFileTime(&ftTimeRemainingUTC, &ftTimeRemaining); FileTimeToLocalFileTime(&ftLastSuccessfulSyncUTC, &ftLastSuccessfulSync); // Create the string showing the next time we plan on syncing. LoadString(HINST_THISDLL, IDS_IT_NEXTSYNC, szTemplate, ARRAYSIZE(szTemplate)); if (SUCCEEDED(_GetDateTimeString(&ftTimeRemaining, TRUE, szTemp1, ARRAYSIZE(szTemp1))) && // Get the date SUCCEEDED(_GetDateTimeString(&ftTimeRemaining, FALSE, szTemp2, ARRAYSIZE(szTemp2))) && // Get the time FAILED(FormatMessageWedge(szTemplate, pszNextSync, cchNextSyncSize, szTemp1, szTemp2))) { pszNextSync[0] = 0; } if (ResyncResult_ChangeTooBig == dwError) { hrLastSyncError = E_FAIL; } if ((ResyncResult_NoData == dwError || ResyncResult_StaleData == dwError) && SUCCEEDED(hrLastSyncError)) { // We've synchronized from our peer, but it didn't provide good enough samples to update our clock. hrLastSyncError = HRESULT_FROM_WIN32(ERROR_TIMEOUT); // approximately the right error } // We should never hit the following case. But if the operation failed (NOT ResyncResult_Success) // then we need hrLastSyncError to be a failure value. if ((ResyncResult_Success != dwError) && SUCCEEDED(hrLastSyncError)) { hrLastSyncError = E_FAIL; } switch (hrLastSyncError) { case S_OK: if (!ftLastSuccessfulSyncUTC.dwLowDateTime && !ftLastSuccessfulSyncUTC.dwHighDateTime) { // We have never sync from the server. LoadString(HINST_THISDLL, IDS_NEVER_TRIED_TOSYNC, pszString, cchSize); } else { if (szServer[0]) { // Format: "Successfully synchronized the clock on 12/23/2001 at 11:03:32am from time.windows.com." LoadString(HINST_THISDLL, IDS_IT_SUCCESS, szTemplate, ARRAYSIZE(szTemplate)); if (SUCCEEDED(_GetDateTimeString(&ftLastSuccessfulSync, TRUE, szTemp1, ARRAYSIZE(szTemp1))) && // Get the date SUCCEEDED(_GetDateTimeString(&ftLastSuccessfulSync, FALSE, szTemp2, ARRAYSIZE(szTemp2))) && // Get the time FAILED(FormatMessageWedge(szTemplate, pszString, cchSize, szTemp1, szTemp2, szServer))) { pszString[0] = 0; } } else { // Format: "Successfully synchronized the clock on 12/23/2001 at 11:03:32am." LoadString(HINST_THISDLL, IDS_IT_SUCCESS2, szTemplate, ARRAYSIZE(szTemplate)); if (SUCCEEDED(_GetDateTimeString(&ftLastSuccessfulSync, TRUE, szTemp1, ARRAYSIZE(szTemp1))) && // Get the date SUCCEEDED(_GetDateTimeString(&ftLastSuccessfulSync, FALSE, szTemp2, ARRAYSIZE(szTemp2))) && // Get the time FAILED(FormatMessageWedge(szTemplate, pszString, cchSize, szTemp1, szTemp2))) { pszString[0] = 0; } } } break; case S_FALSE: pszString[0] = 0; break; default: if (ulValidDataCounter && ((0 != ftLastSuccessfulSyncUTC.dwLowDateTime) || (0 != ftLastSuccessfulSyncUTC.dwHighDateTime))) { // Getting this far means we may have failed this last sync attempt, but we have succeeded // previously hr = E_FAIL; szTemp4[0] = 0; // This will be the error message. szTemp3[0] = 0; if (ResyncResult_ChangeTooBig == dwError) { // If the date is too far off, we fail the sync for security reasons. That happened // here. LoadString(HINST_THISDLL, IDS_ERR_DATETOOWRONG, szTemp4, ARRAYSIZE(szTemp4)); } else if (ulLastSyncErrorMsgId) // We have a bonus error string. { _LoadW32Time(); if (g_hInstW32Time) { // Load the specific reason the sync failed. if (!FormatMessage(FORMAT_MESSAGE_FROM_HMODULE, (LPCVOID)g_hInstW32Time, ulLastSyncErrorMsgId, 0, szTemp4, ARRAYSIZE(szTemp4), NULL)) { szTemp4[0] = 0; // We will get the value below hr = S_OK; } else { _CleanUpErrorString(szTemp4, ARRAYSIZE(szTemp4)); } } } if (!szTemp4[0]) { if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, HRESULT_CODE(hrLastSyncError), 0, szTemp4, ARRAYSIZE(szTemp4), NULL)) { _CleanUpErrorString(szTemp4, ARRAYSIZE(szTemp4)); } else { szTemp4[0] = 0; } } if (szTemp4[0]) { LoadString(HINST_THISDLL, IDS_IT_FAIL1, szTemplate, ARRAYSIZE(szTemplate)); if (FAILED(FormatMessageWedge(szTemplate, szTemp3, ARRAYSIZE(szTemp3), szServer, szTemp4))) { szTemp3[0] = 0; } } else { // <-------------------------------------------------------------> // Format: // "An error occurred synchronizing the clock from time.windows.com // on 2/3/2001 at 11:03:32am. Unable to connect to the server." LoadString(HINST_THISDLL, IDS_IT_FAIL2, szTemplate, ARRAYSIZE(szTemplate)); if (FAILED(FormatMessageWedge(szTemplate, szTemp3, ARRAYSIZE(szTemp3), szServer))) { szTemp3[0] = 0; } hr = S_OK; } LoadString(HINST_THISDLL, IDS_IT_FAILLAST, szTemplate, ARRAYSIZE(szTemplate)); if (SUCCEEDED(_GetDateTimeString(&ftLastSuccessfulSync, TRUE, szTemp1, ARRAYSIZE(szTemp1))) && // Get the date SUCCEEDED(_GetDateTimeString(&ftLastSuccessfulSync, FALSE, szTemp2, ARRAYSIZE(szTemp2))) && // Get the time FAILED(FormatMessageWedge(szTemplate, szTemp4, ARRAYSIZE(szTemp4), szTemp1, szTemp2))) { szTemp4[0] = 0; } wnsprintf(pszString, cchSize, TEXT("%s\n\n%s"), szTemp3, szTemp4); } else { // Getting this far means we may have failed to sync this time and we have never succeeded before. if (ulLastSyncErrorMsgId) // We have a bonus error string. { _LoadW32Time(); szTemp3[0] = 0; if (g_hInstW32Time) { // <-------------------------------------------------------------> // Format: // "An error occurred synchronizing the clock from time.windows.com // on 2/3/2001 at 11:03:32am. Unable to connect to the server." if (!FormatMessage(FORMAT_MESSAGE_FROM_HMODULE, (LPCVOID)g_hInstW32Time, ulLastSyncErrorMsgId, 0, szTemp4, ARRAYSIZE(szTemp4), NULL)) { szTemp4[0] = 0; } else { _CleanUpErrorString(szTemp4, ARRAYSIZE(szTemp4)); } if (szServer[0]) { LoadString(HINST_THISDLL, IDS_IT_FAIL1, szTemplate, ARRAYSIZE(szTemplate)); if (FAILED(FormatMessageWedge(szTemplate, pszString, cchSize, szServer, szTemp4))) { pszString[0] = 0; } } else { LoadString(HINST_THISDLL, IDS_IT_FAIL3, szTemplate, ARRAYSIZE(szTemplate)); if (FAILED(FormatMessageWedge(szTemplate, pszString, cchSize, szTemp4))) { pszString[0] = 0; } } } } else { // <-------------------------------------------------------------> // Format: // "An error occurred synchronizing the clock from time.windows.com // on 2/3/2001 at 11:03:32am. Unable to connect to the server." LoadString(HINST_THISDLL, IDS_IT_FAIL2, szTemplate, ARRAYSIZE(szTemplate)); if (FAILED(FormatMessageWedge(szTemplate, pszString, cchSize, szServer))) { pszString[0] = 0; } } } break; }; } else { LoadString(HINST_THISDLL, IDS_ERR_GETINFO_FAIL, szTemplate, ARRAYSIZE(szTemplate)); if (!FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, HRESULT_CODE(hr), 0, szTemp1, ARRAYSIZE(szTemp1), NULL)) { szTemp1[0] = 0; } else { _CleanUpErrorString(szTemp1, ARRAYSIZE(szTemp1)); } wnsprintf(pszString, cchSize, szTemplate, szTemp1); } return hr; } /////////// // These functions will cause the background thread to check if we should add the "Internet Time" tab. /////////// // This function is called on the background thread. DWORD CALLBACK _AsyncCheckDCThread(void * pv) { if (g_pInternetTime) { g_pInternetTime->AsyncCheck(); } return 0; } typedef DWORD (* PFN_NETGETJOININFORMATION) (IN LPCWSTR pszComputerName OPTIONAL, OUT LPWSTR * pszDomainName, IN PNETSETUP_JOIN_STATUS pJoinStatus); // This function is called on the background thread. DWORD NetGetJoinInformation_DelayLoad( IN LPCWSTR pszComputerName OPTIONAL, OUT LPWSTR * pszDomainName, IN PNETSETUP_JOIN_STATUS pJoinStatus) { DWORD dwError = ERROR_INVALID_FUNCTION; HMODULE hmod = LoadLibrary(TEXT("NETAPI32.DLL")); if (hmod) { PFN_NETGETJOININFORMATION pfnDelayLoad = (PFN_NETGETJOININFORMATION)GetProcAddress(hmod, "NetGetJoinInformation"); if (pfnDelayLoad) { dwError = pfnDelayLoad(pszComputerName, pszDomainName, pJoinStatus); } FreeLibrary(hmod); } else { dwError = GetLastError(); } return dwError; } typedef DWORD (* PFN_DSGETDCNAME) (IN LPCTSTR ComputerName OPTIONAL, IN LPCTSTR DomainName OPTIONAL, IN GUID *DomainGuid OPTIONAL, IN LPCTSTR SiteName OPTIONAL, IN ULONG Flags, OUT PDOMAIN_CONTROLLER_INFO * DomainControllerInfo); // This function is called on the background thread. DWORD DsGetDcName_DelayLoad( IN LPCWSTR pszComputerName OPTIONAL, IN LPCWSTR pszDomainName OPTIONAL, IN GUID * pDomainGuid OPTIONAL, IN LPCWSTR pszSiteName OPTIONAL, IN ULONG pulFlags, OUT PDOMAIN_CONTROLLER_INFOW * pDomainControllerInfo) { DWORD dwError = ERROR_INVALID_FUNCTION; HMODULE hmod = LoadLibrary(TEXT("NETAPI32.DLL")); if (hmod) { PFN_DSGETDCNAME pfnDelayLoad = (PFN_DSGETDCNAME)GetProcAddress(hmod, "DsGetDcNameW"); if (pfnDelayLoad) { dwError = pfnDelayLoad(pszComputerName, pszDomainName, pDomainGuid, pszSiteName, pulFlags, pDomainControllerInfo); } FreeLibrary(hmod); } else { dwError = GetLastError(); } return dwError; } typedef DWORD (* PFN_NETAPIBUFFERFREE) (IN LPVOID Buffer); // This function is called on the background thread. DWORD NetApiBufferFree_DelayLoad(IN LPVOID Buffer) { DWORD dwError = FALSE; HMODULE hmod = LoadLibrary(TEXT("NETAPI32.DLL")); if (hmod) { PFN_NETAPIBUFFERFREE pfnDelayLoad = (PFN_NETAPIBUFFERFREE)GetProcAddress(hmod, "NetApiBufferFree"); if (pfnDelayLoad) { dwError = pfnDelayLoad(Buffer); } FreeLibrary(hmod); } else { dwError = GetLastError(); } return dwError; } // This function is called on the background thread. BOOL CInternetTime::IsInternetTimeAvailable(void) { return SHRegGetBoolUSValue(SZ_REGKEY_DATETIME, SZ_REGVALUE_INTERNET_FEATURE_AVAILABLE, FALSE, FEATURE_INTERNET_TIME); } // This function is called on the background thread. EXTERN_C BOOL DoesTimeComeFromDC(void) { BOOL fTimeFromDomain = FALSE; LPWSTR pszDomain = NULL; NETSETUP_JOIN_STATUS joinStatus = NetSetupUnknownStatus; DWORD dwError = NetGetJoinInformation_DelayLoad(NULL, &pszDomain, &joinStatus); // We will act like there isn't a DC if we have the test registry set. if (NERR_Success == dwError) { // If we are connected to a domain, we need to do the expensive net search // to see that is where we will get the time from. if (NetSetupDomainName == joinStatus) { PDOMAIN_CONTROLLER_INFO pdomainInfo = {0}; dwError = DsGetDcName_DelayLoad(NULL, NULL, NULL, NULL, DS_TIMESERV_REQUIRED, &pdomainInfo); // We will act like there isn't a DC if we have the test registry set. if (ERROR_SUCCESS == dwError) { if (FALSE == SHRegGetBoolUSValue(SZ_REGKEY_DATETIME, SZ_REGVALUE_TEST_SIMULATENODC, FALSE, FALSE)) { fTimeFromDomain = TRUE; } NetApiBufferFree_DelayLoad(pdomainInfo); } } if (pszDomain) { NetApiBufferFree_DelayLoad(pszDomain); } } return fTimeFromDomain; } // This function is called on the background thread. HRESULT CInternetTime::_SyncNow(BOOL fOnlyUpdateInfo) { HRESULT hr = S_OK; ENTERCRITICAL; BOOL fContinue = ((eBKAUpdate == m_eAction) || (eBKAGetInfo == m_eAction)); if (fContinue) { m_eAction = eBKAUpdating; } LEAVECRITICAL; if (fContinue) { hr = E_OUTOFMEMORY; DWORD cchSize = 4024; LPTSTR pszString = (LPTSTR) LocalAlloc(LPTR, sizeof(pszString[0]) * cchSize); if (pszString) { WCHAR szExistingServer[MAX_URL_STRING]; HRESULT hrServer = E_FAIL; TCHAR szNewServer[MAX_PATH]; TCHAR szNextSync[MAX_PATH]; DWORD dwError = 0; DWORD dwSyncFlags; if (!fOnlyUpdateInfo) { hrServer = GetW32TimeServer(FALSE, szExistingServer, ARRAYSIZE(szExistingServer)); GetWindowText(GetDlgItem(m_hwndInternet, DATETIME_INTERNET_SERVER_EDIT), szNextSync, ARRAYSIZE(szNextSync)); // save the new server away for future processing StrCpyN(szNewServer, szNextSync, ARRAYSIZE(szNewServer)); if (!ContainsServer(szExistingServer, szNextSync)) { // The servers don't match. We want to add the new server to the beginning of the list. This // will work around a problem in W32time. If we don't do this, then: // 1. It will cause a second sync with the original server (which is bad for perf and affects statistics) // 2. The Last Updated sync time will then be from the wrong peer. This is really bad because it's result is basic // on the user's previously bad time // 3. It will do an extra DNS resolution causing slowness on our side, increasing server traffic, and dragging down // the intranet. TCHAR szTemp[MAX_URL_STRING]; StrCpyN(szTemp, szNextSync, ARRAYSIZE(szTemp)); wnsprintf(szNextSync, ARRAYSIZE(szNextSync), TEXT("%s %s"), szTemp, szExistingServer); dwSyncFlags = TimeSyncFlag_ReturnResult | TimeSyncFlag_UpdateAndResync; } else { // We've already got this server in our list of servers. Just cause our peers to resync. dwSyncFlags = TimeSyncFlag_ReturnResult | TimeSyncFlag_HardResync; } SetW32TimeServer(szNextSync); // I will ignore the error value because I will get the error info in _CreateW32TimeSuccessErrorString. dwError = W32TimeSyncNowDDLoad(SZ_COMPUTER_LOCAL, TRUE /* Synchronous */, dwSyncFlags); if ((ResyncResult_StaleData == dwError) && (0 == (TimeSyncFlag_HardResync & dwSyncFlags))) { // We've got stale data preventing us from resyncing. Try again with a full resync. dwSyncFlags = TimeSyncFlag_ReturnResult | TimeSyncFlag_HardResync; dwError = W32TimeSyncNowDDLoad(SZ_COMPUTER_LOCAL, TRUE /* Synchronous */, dwSyncFlags); } } pszString[0] = 0; szNextSync[0] = 0; hr = _CreateW32TimeSuccessErrorString(dwError, pszString, cchSize, szNextSync, ARRAYSIZE(szNextSync), (SUCCEEDED(hrServer) ? szNewServer : NULL)); if (SUCCEEDED(hrServer)) { SetW32TimeServer(szExistingServer); _StartServiceAndRefresh(TRUE); // Make sure the service is on and make it update it's settings. } ENTERCRITICAL; Str_SetPtr(&m_pszNextSyncTime, szNextSync); if (m_pszStatusString) { LocalFree(m_pszStatusString); } m_pszStatusString = pszString; PostMessage(m_hwndInternet, WMUSER_UPDATED_STATUS_TEXT, 0, 0); // Tell the forground thread to pick up the new string. m_eAction = eBKAWait; LEAVECRITICAL; } } return hr; } // This function is called on the background thread. void CInternetTime::AsyncCheck(void) { HRESULT hr = S_OK; if (m_hDlg && !DoesTimeComeFromDC()) { // Tell the forground thread to add us. PostMessage(m_hwndDate, WMUSER_ADDINTERNETTAB, 0, 0); _ProcessBkThreadActions(); } } // This function is called on the background thread. HRESULT CInternetTime::_ProcessBkThreadActions(void) { HRESULT hr = S_OK; while (eBKAQuit != m_eAction) // Okay since we are only reading { switch (m_eAction) { case eBKAGetInfo: _SyncNow(TRUE); break; case eBKAUpdate: _SyncNow(FALSE); break; case eBKAUpdating: case eBKAWait: default: Sleep(300); // We don't care if there is up to 100ms latency between button press and action starting. break; } } return hr; } ///////////////////////////////////////////////////////////////////// // Public Functions ///////////////////////////////////////////////////////////////////// // This function is called on the forground thread. EXTERN_C HRESULT AddInternetPageAsync(HWND hDlg, HWND hwndDate) { HRESULT hr = E_OUTOFMEMORY; if (!g_pInternetTime) { g_pInternetTime = new CInternetTime(hDlg, hwndDate); } if (g_pInternetTime) { // We only want to add the page that allows the user to get the time from the internet if: // 1. The feature is turned on, and // 2. The user doesn't get the time from an intranet Domain Control. if (g_pInternetTime->IsInternetTimeAvailable()) { // Start the thread to find out if we are in a domain and need the advanced page. We need // to do this on a background thread because the DsGetDcName() API may take 10-20 seconds. hr = (SHCreateThread(_AsyncCheckDCThread, hDlg, (CTF_INSIST | CTF_FREELIBANDEXIT), NULL) ? S_OK : E_FAIL); } } return hr; } // This function is called on the forground thread. EXTERN_C HRESULT AddInternetTab(HWND hDlg) { HRESULT hr = E_OUTOFMEMORY; if (g_pInternetTime) { hr = g_pInternetTime->AddInternetPage(); } return hr; }