/* * Process Hacker Plugins - * Update Checker Plugin * * Copyright (C) 2011-2016 dmex * * This file is part of Process Hacker. * * Process Hacker is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Process Hacker is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Process Hacker. If not, see . */ #include "updater.h" #include #include static HANDLE UpdateDialogThreadHandle = NULL; static HWND UpdateDialogHandle = NULL; static PH_EVENT InitializedEvent = PH_EVENT_INIT; mxml_type_t QueryXmlDataCallback( _In_ mxml_node_t *node ) { return MXML_OPAQUE; } BOOLEAN LastUpdateCheckExpired( VOID ) { ULONG64 lastUpdateTimeTicks = 0; LARGE_INTEGER currentUpdateTimeTicks; PPH_STRING lastUpdateTimeString; // Get the last update check time lastUpdateTimeString = PhGetStringSetting(SETTING_NAME_LAST_CHECK); PhStringToInteger64(&lastUpdateTimeString->sr, 0, &lastUpdateTimeTicks); // Query the current time PhQuerySystemTime(¤tUpdateTimeTicks); // Check if the last update check was more than 7 days ago if (currentUpdateTimeTicks.QuadPart - lastUpdateTimeTicks >= 7 * PH_TICKS_PER_DAY) { PPH_STRING currentUpdateTimeString = PhFormatUInt64(currentUpdateTimeTicks.QuadPart, FALSE); // Save the current time PhSetStringSetting2(SETTING_NAME_LAST_CHECK, ¤tUpdateTimeString->sr); // Cleanup PhDereferenceObject(currentUpdateTimeString); PhDereferenceObject(lastUpdateTimeString); return TRUE; } // Cleanup PhDereferenceObject(lastUpdateTimeString); return FALSE; } PPH_STRING UpdateVersionString( VOID ) { ULONG majorVersion; ULONG minorVersion; ULONG revisionVersion; PPH_STRING currentVersion = NULL; PPH_STRING versionHeader = NULL; PhGetPhVersionNumbers( &majorVersion, &minorVersion, NULL, &revisionVersion ); currentVersion = PhFormatString( L"%lu.%lu.%lu", majorVersion, minorVersion, revisionVersion ); if (currentVersion) { versionHeader = PhConcatStrings2(L"ProcessHacker-Build: ", currentVersion->Buffer); PhDereferenceObject(currentVersion); } return versionHeader; } PPH_STRING UpdateWindowsString( VOID ) { static PH_STRINGREF keyName = PH_STRINGREF_INIT(L"Software\\Microsoft\\Windows NT\\CurrentVersion"); HANDLE keyHandle = NULL; PPH_STRING buildLabHeader = NULL; if (NT_SUCCESS(PhOpenKey( &keyHandle, KEY_READ, PH_KEY_LOCAL_MACHINE, &keyName, 0 ))) { PPH_STRING buildLabString; if (buildLabString = PhQueryRegistryString(keyHandle, L"BuildLabEx")) { buildLabHeader = PhConcatStrings2(L"ProcessHacker-OsBuild: ", buildLabString->Buffer); PhDereferenceObject(buildLabString); } else if (buildLabString = PhQueryRegistryString(keyHandle, L"BuildLab")) { buildLabHeader = PhConcatStrings2(L"ProcessHacker-OsBuild: ", buildLabString->Buffer); PhDereferenceObject(buildLabString); } NtClose(keyHandle); } return buildLabHeader; } BOOLEAN ParseVersionString( _Inout_ PPH_UPDATER_CONTEXT Context ) { PH_STRINGREF sr, majorPart, minorPart, revisionPart; ULONG64 majorInteger = 0, minorInteger = 0, revisionInteger = 0; PhInitializeStringRef(&sr, Context->Version->Buffer); PhInitializeStringRef(&revisionPart, Context->RevVersion->Buffer); if (PhSplitStringRefAtChar(&sr, '.', &majorPart, &minorPart)) { PhStringToInteger64(&majorPart, 10, &majorInteger); PhStringToInteger64(&minorPart, 10, &minorInteger); PhStringToInteger64(&revisionPart, 10, &revisionInteger); Context->MajorVersion = (ULONG)majorInteger; Context->MinorVersion = (ULONG)minorInteger; Context->RevisionVersion = (ULONG)revisionInteger; return TRUE; } return FALSE; } BOOLEAN ReadRequestString( _In_ HINTERNET Handle, _Out_ _Deref_post_z_cap_(*DataLength) PSTR *Data, _Out_ ULONG *DataLength ) { PSTR data; ULONG allocatedLength; ULONG dataLength; ULONG returnLength; BYTE buffer[PAGE_SIZE]; allocatedLength = sizeof(buffer); data = (PSTR)PhAllocate(allocatedLength); dataLength = 0; // Zero the buffer memset(buffer, 0, PAGE_SIZE); while (WinHttpReadData(Handle, buffer, PAGE_SIZE, &returnLength)) { if (returnLength == 0) break; if (allocatedLength < dataLength + returnLength) { allocatedLength *= 2; data = (PSTR)PhReAllocate(data, allocatedLength); } // Copy the returned buffer into our pointer memcpy(data + dataLength, buffer, returnLength); // Zero the returned buffer for the next loop //memset(buffer, 0, returnLength); dataLength += returnLength; } if (allocatedLength < dataLength + 1) { allocatedLength++; data = (PSTR)PhReAllocate(data, allocatedLength); } // Ensure that the buffer is null-terminated. data[dataLength] = 0; *DataLength = dataLength; *Data = data; return TRUE; } PPH_UPDATER_CONTEXT CreateUpdateContext( VOID ) { PPH_UPDATER_CONTEXT context; context = (PPH_UPDATER_CONTEXT)PhAllocate(sizeof(PH_UPDATER_CONTEXT)); memset(context, 0, sizeof(PH_UPDATER_CONTEXT)); return context; } VOID FreeUpdateContext( _In_ _Post_invalid_ PPH_UPDATER_CONTEXT Context ) { if (!Context) return; Context->HaveData = FALSE; Context->UpdaterState = PhUpdateMaximum; Context->MinorVersion = 0; Context->MajorVersion = 0; Context->RevisionVersion = 0; Context->CurrentMinorVersion = 0; Context->CurrentMajorVersion = 0; Context->CurrentRevisionVersion = 0; PhClearReference(&Context->Version); PhClearReference(&Context->RevVersion); PhClearReference(&Context->RelDate); PhClearReference(&Context->Size); PhClearReference(&Context->Hash); PhClearReference(&Context->ReleaseNotesUrl); PhClearReference(&Context->SetupFilePath); PhClearReference(&Context->SetupFileDownloadUrl); if (Context->FontHandle) { DeleteObject(Context->FontHandle); Context->FontHandle = NULL; } if (Context->IconBitmap) { DeleteObject(Context->IconBitmap); Context->IconBitmap = NULL; } if (Context->IconHandle) { DestroyIcon(Context->IconHandle); Context->IconHandle = NULL; } PhFree(Context); } BOOLEAN QueryUpdateData( _Inout_ PPH_UPDATER_CONTEXT Context, _In_ BOOLEAN UseFailServer ) { BOOLEAN isSuccess = FALSE; HINTERNET httpSessionHandle = NULL; HINTERNET httpConnectionHandle = NULL; HINTERNET httpRequestHandle = NULL; WINHTTP_CURRENT_USER_IE_PROXY_CONFIG proxyConfig = { 0 }; mxml_node_t* xmlNode = NULL; ULONG xmlStringBufferLength = 0; PSTR xmlStringBuffer = NULL; PPH_STRING versionHeader = UpdateVersionString(); PPH_STRING windowsHeader = UpdateWindowsString(); // Get the current Process Hacker version PhGetPhVersionNumbers( &Context->CurrentMajorVersion, &Context->CurrentMinorVersion, NULL, &Context->CurrentRevisionVersion ); __try { // Query the current system proxy WinHttpGetIEProxyConfigForCurrentUser(&proxyConfig); // Open the HTTP session with the system proxy configuration if available if (!(httpSessionHandle = WinHttpOpen( NULL, proxyConfig.lpszProxy != NULL ? WINHTTP_ACCESS_TYPE_NAMED_PROXY : WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, proxyConfig.lpszProxy, proxyConfig.lpszProxyBypass, 0 ))) { __leave; } if (WindowsVersion >= WINDOWS_8_1) { // Enable GZIP and DEFLATE support on Windows 8.1 and above using undocumented flags. ULONG httpFlags = WINHTTP_DECOMPRESSION_FLAG_GZIP | WINHTTP_DECOMPRESSION_FLAG_DEFLATE; WinHttpSetOption( httpSessionHandle, WINHTTP_OPTION_DECOMPRESSION, &httpFlags, sizeof(ULONG) ); } if (UseFailServer) { if (!(httpConnectionHandle = WinHttpConnect( httpSessionHandle, L"processhacker.sourceforge.net", INTERNET_DEFAULT_HTTP_PORT, 0 ))) { __leave; } if (!(httpRequestHandle = WinHttpOpenRequest( httpConnectionHandle, NULL, L"/update.php", NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_REFRESH ))) { __leave; } } else { if (!(httpConnectionHandle = WinHttpConnect( httpSessionHandle, L"wj32.org", INTERNET_DEFAULT_HTTPS_PORT, 0 ))) { __leave; } if (!(httpRequestHandle = WinHttpOpenRequest( httpConnectionHandle, NULL, L"/processhacker/update.php", NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_REFRESH | WINHTTP_FLAG_SECURE ))) { __leave; } } if (versionHeader) { WinHttpAddRequestHeaders( httpRequestHandle, versionHeader->Buffer, (ULONG)versionHeader->Length / sizeof(WCHAR), WINHTTP_ADDREQ_FLAG_ADD ); } if (windowsHeader) { WinHttpAddRequestHeaders( httpRequestHandle, windowsHeader->Buffer, (ULONG)windowsHeader->Length / sizeof(WCHAR), WINHTTP_ADDREQ_FLAG_ADD ); } if (!WinHttpSendRequest( httpRequestHandle, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH, 0 )) { __leave; } if (!WinHttpReceiveResponse(httpRequestHandle, NULL)) __leave; // Read the resulting xml into our buffer. if (!ReadRequestString(httpRequestHandle, &xmlStringBuffer, &xmlStringBufferLength)) __leave; // Check the buffer for valid data. if (xmlStringBuffer == NULL || xmlStringBuffer[0] == '\0') __leave; // Load our XML xmlNode = mxmlLoadString(NULL, xmlStringBuffer, QueryXmlDataCallback); if (xmlNode == NULL || xmlNode->type != MXML_ELEMENT) __leave; // Find the version node Context->Version = PhGetOpaqueXmlNodeText( mxmlFindElement(xmlNode->child, xmlNode, "ver", NULL, NULL, MXML_DESCEND) ); if (PhIsNullOrEmptyString(Context->Version)) __leave; // Find the revision node Context->RevVersion = PhGetOpaqueXmlNodeText( mxmlFindElement(xmlNode->child, xmlNode, "rev", NULL, NULL, MXML_DESCEND) ); if (PhIsNullOrEmptyString(Context->RevVersion)) __leave; // Find the release date node Context->RelDate = PhGetOpaqueXmlNodeText( mxmlFindElement(xmlNode->child, xmlNode, "reldate", NULL, NULL, MXML_DESCEND) ); if (PhIsNullOrEmptyString(Context->RelDate)) __leave; // Find the size node Context->Size = PhGetOpaqueXmlNodeText( mxmlFindElement(xmlNode->child, xmlNode, "size", NULL, NULL, MXML_DESCEND) ); if (PhIsNullOrEmptyString(Context->Size)) __leave; //Find the hash node Context->Hash = PhGetOpaqueXmlNodeText( mxmlFindElement(xmlNode->child, xmlNode, "sha1", NULL, NULL, MXML_DESCEND) ); if (PhIsNullOrEmptyString(Context->Hash)) __leave; // Find the release notes URL Context->ReleaseNotesUrl = PhGetOpaqueXmlNodeText( mxmlFindElement(xmlNode->child, xmlNode, "relnotes", NULL, NULL, MXML_DESCEND) ); if (PhIsNullOrEmptyString(Context->ReleaseNotesUrl)) __leave; // Find the installer download URL Context->SetupFileDownloadUrl = PhGetOpaqueXmlNodeText( mxmlFindElement(xmlNode->child, xmlNode, "setupurl", NULL, NULL, MXML_DESCEND) ); if (PhIsNullOrEmptyString(Context->SetupFileDownloadUrl)) __leave; if (!ParseVersionString(Context)) __leave; isSuccess = TRUE; } __finally { if (httpRequestHandle) WinHttpCloseHandle(httpRequestHandle); if (httpConnectionHandle) WinHttpCloseHandle(httpConnectionHandle); if (httpSessionHandle) WinHttpCloseHandle(httpSessionHandle); if (xmlNode) mxmlDelete(xmlNode); if (xmlStringBuffer) PhFree(xmlStringBuffer); PhClearReference(&versionHeader); PhClearReference(&windowsHeader); } return isSuccess; } NTSTATUS UpdateCheckSilentThread( _In_ PVOID Parameter ) { PPH_UPDATER_CONTEXT context = NULL; ULONGLONG currentVersion = 0; ULONGLONG latestVersion = 0; context = CreateUpdateContext(); __try { if (!LastUpdateCheckExpired()) { __leave; } if (!QueryUpdateData(context, FALSE)) { if (!QueryUpdateData(context, TRUE)) { __leave; } } currentVersion = MAKEDLLVERULL( context->CurrentMajorVersion, context->CurrentMinorVersion, 0, context->CurrentRevisionVersion ); #ifdef DEBUG_UPDATE latestVersion = MAKEDLLVERULL( 9999, 9999, 0, 9999 ); #else latestVersion = MAKEDLLVERULL( context->MajorVersion, context->MinorVersion, 0, context->RevisionVersion ); #endif if (WindowsVersion < WINDOWS_7 && context->MajorVersion != 2) { // If we're running on XP or Vista, we only support 2.x builds and below. latestVersion = currentVersion; } // Compare the current version against the latest available version if (currentVersion < latestVersion) { // Don't spam the user the second they open PH, delay dialog creation for 3 seconds. Sleep(3000); if (!UpdateDialogHandle) { // We have data we're going to cache and pass into the dialog context->HaveData = TRUE; // Show the dialog asynchronously on a new thread. ShowUpdateDialog(context); } } } __finally { // Check the dialog doesn't own the window context... if (!context->HaveData) { FreeUpdateContext(context); } } return STATUS_SUCCESS; } NTSTATUS UpdateCheckThread( _In_ PVOID Parameter ) { PPH_UPDATER_CONTEXT context = NULL; ULONGLONG currentVersion = 0; ULONGLONG latestVersion = 0; context = (PPH_UPDATER_CONTEXT)Parameter; // Check if we have cached update data if (!context->HaveData) { context->HaveData = QueryUpdateData(context, FALSE); if (!context->HaveData) { context->HaveData = QueryUpdateData(context, TRUE); } } // sanity check if (!context->HaveData) { PostMessage(context->DialogHandle, PH_UPDATEISERRORED, 0, 0); return STATUS_SUCCESS; } currentVersion = MAKEDLLVERULL( context->CurrentMajorVersion, context->CurrentMinorVersion, 0, context->CurrentRevisionVersion ); #ifdef DEBUG_UPDATE latestVersion = MAKEDLLVERULL( 9999, 9999, 0, 9999 ); #else latestVersion = MAKEDLLVERULL( context->MajorVersion, context->MinorVersion, 0, context->RevisionVersion ); #endif if (WindowsVersion < WINDOWS_7 && context->MajorVersion != 2) { // If we're running on XP or Vista, we only support 2.x builds and below. PostMessage(context->DialogHandle, PH_UPDATENOTSUPPORTED, 0, 0); } else if (currentVersion == latestVersion) { // User is running the latest version PostMessage(context->DialogHandle, PH_UPDATEISCURRENT, 0, 0); } else if (currentVersion > latestVersion) { // User is running a newer version PostMessage(context->DialogHandle, PH_UPDATENEWER, 0, 0); } else { // User is running an older version PostMessage(context->DialogHandle, PH_UPDATEAVAILABLE, 0, 0); } return STATUS_SUCCESS; } NTSTATUS UpdateDownloadThread( _In_ PVOID Parameter ) { BOOLEAN downloadSuccess = FALSE; BOOLEAN hashSuccess = FALSE; BOOLEAN verifySuccess = FALSE; HANDLE tempFileHandle = NULL; HINTERNET httpSessionHandle = NULL; HINTERNET httpConnectionHandle = NULL; HINTERNET httpRequestHandle = NULL; PPH_STRING setupTempPath = NULL; PPH_STRING downloadHostPath = NULL; PPH_STRING downloadUrlPath = NULL; PPH_STRING userAgentString = NULL; PPH_STRING fullSetupPath = NULL; PPH_STRING randomGuidString = NULL; ULONG indexOfFileName = -1; GUID randomGuid; URL_COMPONENTS httpUrlComponents = { sizeof(URL_COMPONENTS) }; WINHTTP_CURRENT_USER_IE_PROXY_CONFIG proxyConfig = { 0 }; PPH_UPDATER_CONTEXT context = (PPH_UPDATER_CONTEXT)Parameter; __try { // Create a user agent string. userAgentString = PhFormatString( L"PH_%lu.%lu_%lu", context->CurrentMajorVersion, context->CurrentMinorVersion, context->CurrentRevisionVersion ); if (PhIsNullOrEmptyString(userAgentString)) __leave; // Allocate the GetTempPath buffer setupTempPath = PhCreateStringEx(NULL, GetTempPath(0, NULL) * sizeof(WCHAR)); if (PhIsNullOrEmptyString(setupTempPath)) __leave; // Get the temp path if (GetTempPath((ULONG)setupTempPath->Length / sizeof(WCHAR), setupTempPath->Buffer) == 0) __leave; if (PhIsNullOrEmptyString(setupTempPath)) __leave; // Generate random guid for our directory path. PhGenerateGuid(&randomGuid); if (randomGuidString = PhFormatGuid(&randomGuid)) { PPH_STRING guidSubString; // Strip the left and right curly brackets. guidSubString = PhSubstring(randomGuidString, 1, randomGuidString->Length / sizeof(WCHAR) - 2); PhSwapReference(&randomGuidString, guidSubString); } // Append the tempath to our string: %TEMP%RandomString\\processhacker-%lu.%lu-setup.exe // Example: C:\\Users\\dmex\\AppData\\Temp\\ABCD\\processhacker-2.90-setup.exe context->SetupFilePath = PhFormatString( L"%s%s\\processhacker-%lu.%lu-setup.exe", setupTempPath->Buffer, randomGuidString->Buffer, context->MajorVersion, context->MinorVersion ); if (PhIsNullOrEmptyString(context->SetupFilePath)) __leave; // Create the directory if it does not exist. if (fullSetupPath = PhGetFullPath(context->SetupFilePath->Buffer, &indexOfFileName)) { PPH_STRING directoryPath; if (indexOfFileName == -1) __leave; if (directoryPath = PhSubstring(fullSetupPath, 0, indexOfFileName)) { SHCreateDirectoryEx(NULL, directoryPath->Buffer, NULL); PhDereferenceObject(directoryPath); } } // Create output file if (!NT_SUCCESS(PhCreateFileWin32( &tempFileHandle, context->SetupFilePath->Buffer, FILE_GENERIC_READ | FILE_GENERIC_WRITE, FILE_ATTRIBUTE_NOT_CONTENT_INDEXED | FILE_ATTRIBUTE_TEMPORARY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OVERWRITE_IF, FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT ))) { __leave; } // Set lengths to non-zero enabling these params to be cracked. httpUrlComponents.dwSchemeLength = (ULONG)-1; httpUrlComponents.dwHostNameLength = (ULONG)-1; httpUrlComponents.dwUrlPathLength = (ULONG)-1; if (!WinHttpCrackUrl( context->SetupFileDownloadUrl->Buffer, (ULONG)context->SetupFileDownloadUrl->Length, 0, &httpUrlComponents )) { __leave; } // Create the Host string. downloadHostPath = PhCreateStringEx( httpUrlComponents.lpszHostName, httpUrlComponents.dwHostNameLength * sizeof(WCHAR) ); if (PhIsNullOrEmptyString(downloadHostPath)) __leave; // Create the Path string. downloadUrlPath = PhCreateStringEx( httpUrlComponents.lpszUrlPath, httpUrlComponents.dwUrlPathLength * sizeof(WCHAR) ); if (PhIsNullOrEmptyString(downloadUrlPath)) __leave; SetDlgItemText(context->DialogHandle, IDC_STATUS, L"Connecting..."); // Query the current system proxy WinHttpGetIEProxyConfigForCurrentUser(&proxyConfig); // Open the HTTP session with the system proxy configuration if available if (!(httpSessionHandle = WinHttpOpen( userAgentString->Buffer, proxyConfig.lpszProxy != NULL ? WINHTTP_ACCESS_TYPE_NAMED_PROXY : WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, proxyConfig.lpszProxy, proxyConfig.lpszProxyBypass, 0 ))) { __leave; } if (WindowsVersion >= WINDOWS_8_1) { // Enable GZIP and DEFLATE support on Windows 8.1 and above using undocumented flags. ULONG httpFlags = WINHTTP_DECOMPRESSION_FLAG_GZIP | WINHTTP_DECOMPRESSION_FLAG_DEFLATE; WinHttpSetOption( httpSessionHandle, WINHTTP_OPTION_DECOMPRESSION, &httpFlags, sizeof(ULONG) ); } if (!(httpConnectionHandle = WinHttpConnect( httpSessionHandle, downloadHostPath->Buffer, httpUrlComponents.nScheme == INTERNET_SCHEME_HTTP ? INTERNET_DEFAULT_HTTP_PORT : INTERNET_DEFAULT_HTTPS_PORT, 0 ))) { __leave; } if (!(httpRequestHandle = WinHttpOpenRequest( httpConnectionHandle, NULL, downloadUrlPath->Buffer, NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_REFRESH | (httpUrlComponents.nScheme == INTERNET_SCHEME_HTTPS ? WINHTTP_FLAG_SECURE : 0) ))) { __leave; } SetDlgItemText(context->DialogHandle, IDC_STATUS, L"Sending request..."); if (!WinHttpSendRequest( httpRequestHandle, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH, 0 )) { __leave; } SetDlgItemText(context->DialogHandle, IDC_STATUS, L"Waiting for response..."); if (WinHttpReceiveResponse(httpRequestHandle, NULL)) { ULONG bytesDownloaded = 0; ULONG downloadedBytes = 0; ULONG contentLengthSize = sizeof(ULONG); ULONG contentLength = 0; BYTE buffer[PAGE_SIZE]; BYTE hashBuffer[20]; PH_HASH_CONTEXT hashContext; IO_STATUS_BLOCK isb; if (!WinHttpQueryHeaders( httpRequestHandle, WINHTTP_QUERY_CONTENT_LENGTH | WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX, &contentLength, &contentLengthSize, 0 )) { __leave; } // Initialize hash algorithm. PhInitializeHash(&hashContext, Sha1HashAlgorithm); // Zero the buffer. memset(buffer, 0, PAGE_SIZE); // Download the data. while (WinHttpReadData(httpRequestHandle, buffer, PAGE_SIZE, &bytesDownloaded)) { // If we get zero bytes, the file was uploaded or there was an error if (bytesDownloaded == 0) break; // If the dialog was closed, just cleanup and exit //if (context->UpdaterState == PhUpdateMaximum) if (!UpdateDialogThreadHandle) __leave; // Update the hash of bytes we downloaded. PhUpdateHash(&hashContext, buffer, bytesDownloaded); // Write the downloaded bytes to disk. if (!NT_SUCCESS(NtWriteFile( tempFileHandle, NULL, NULL, NULL, &isb, buffer, bytesDownloaded, NULL, NULL ))) { __leave; } downloadedBytes += (DWORD)isb.Information; // Check the number of bytes written are the same we downloaded. if (bytesDownloaded != isb.Information) __leave; // Update the GUI progress. // TODO: Update on GUI thread. { //int percent = MulDiv(100, downloadedBytes, contentLength); FLOAT percent = ((FLOAT)downloadedBytes / contentLength * 100); PPH_STRING totalDownloaded = PhFormatSize(downloadedBytes, -1); PPH_STRING totalLength = PhFormatSize(contentLength, -1); PPH_STRING dlLengthString = PhFormatString( L"%s of %s (%.0f%%)", totalDownloaded->Buffer, totalLength->Buffer, percent ); // Update the progress bar position SendMessage(context->ProgressHandle, PBM_SETPOS, (ULONG)percent, 0); Static_SetText(context->StatusHandle, dlLengthString->Buffer); PhDereferenceObject(dlLengthString); PhDereferenceObject(totalDownloaded); PhDereferenceObject(totalLength); } } // Compute hash result (will fail if file not downloaded correctly). if (PhFinalHash(&hashContext, &hashBuffer, 20, NULL)) { // Allocate our hash string, hex the final hash result in our hashBuffer. PPH_STRING hexString = PhBufferToHexString(hashBuffer, 20); if (PhEqualString(hexString, context->Hash, TRUE)) { hashSuccess = TRUE; } PhDereferenceObject(hexString); } } downloadSuccess = TRUE; } __finally { if (tempFileHandle) NtClose(tempFileHandle); if (httpRequestHandle) WinHttpCloseHandle(httpRequestHandle); if (httpConnectionHandle) WinHttpCloseHandle(httpConnectionHandle); if (httpSessionHandle) WinHttpCloseHandle(httpSessionHandle); PhClearReference(&randomGuidString); PhClearReference(&fullSetupPath); PhClearReference(&setupTempPath); PhClearReference(&downloadHostPath); PhClearReference(&downloadUrlPath); PhClearReference(&userAgentString); } if (WindowsVersion < WINDOWS_8) { // Disable signature checking on XP, Vista and Win7 due to SHA2 certificate issues. verifySuccess = TRUE; } else { // Check the digital signature of the installer... if (context->SetupFilePath && PhVerifyFile(context->SetupFilePath->Buffer, NULL) == VrTrusted) { verifySuccess = TRUE; } } if (downloadSuccess && hashSuccess && verifySuccess) { PostMessage(context->DialogHandle, PH_UPDATESUCCESS, 0, 0); } else if (downloadSuccess) { PostMessage(context->DialogHandle, PH_UPDATEFAILURE, verifySuccess, hashSuccess); } else { PostMessage(context->DialogHandle, PH_UPDATEISERRORED, 0, 0); } return STATUS_SUCCESS; } INT_PTR CALLBACK UpdaterWndProc( _In_ HWND hwndDlg, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam ) { PPH_UPDATER_CONTEXT context = NULL; if (uMsg == WM_INITDIALOG) { context = (PPH_UPDATER_CONTEXT)lParam; SetProp(hwndDlg, L"Context", (HANDLE)context); } else { context = (PPH_UPDATER_CONTEXT)GetProp(hwndDlg, L"Context"); if (uMsg == WM_NCDESTROY) { RemoveProp(hwndDlg, L"Context"); FreeUpdateContext(context); PostQuitMessage(0); } } if (context == NULL) return FALSE; switch (uMsg) { case WM_INITDIALOG: { LOGFONT logFont; HWND parentWindow = GetParent(hwndDlg); context->DialogHandle = hwndDlg; context->StatusHandle = GetDlgItem(hwndDlg, IDC_STATUS); context->ProgressHandle = GetDlgItem(hwndDlg, IDC_PROGRESS); if (SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(LOGFONT), &logFont, 0)) { HDC hdc; if (hdc = GetDC(hwndDlg)) { // Create the font handle context->FontHandle = CreateFont( -MulDiv(-14, GetDeviceCaps(hdc, LOGPIXELSY), 72), 0, 0, 0, FW_MEDIUM, FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY | ANTIALIASED_QUALITY, DEFAULT_PITCH, logFont.lfFaceName ); ReleaseDC(hwndDlg, hdc); } } // Load the Process Hacker icon. context->IconHandle = (HICON)LoadImage( NtCurrentPeb()->ImageBaseAddress, MAKEINTRESOURCE(PHAPP_IDI_PROCESSHACKER), IMAGE_ICON, GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON), 0 ); context->IconBitmap = PhIconToBitmap(context->IconHandle, GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON)); // Set the window icons if (context->IconHandle) SendMessage(hwndDlg, WM_SETICON, ICON_SMALL, (LPARAM)context->IconHandle); // Set the text font if (context->FontHandle) SetWindowFont(GetDlgItem(hwndDlg, IDC_MESSAGE), context->FontHandle, FALSE); // Set the window image if (context->IconBitmap) SendMessage(GetDlgItem(hwndDlg, IDC_UPDATEICON), STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)context->IconBitmap); // Center the update window on PH if it's visible else we center on the desktop. PhCenterWindow(hwndDlg, (IsWindowVisible(parentWindow) && !IsMinimized(parentWindow)) ? parentWindow : NULL); // Show new version info (from the background update check) if (context->HaveData) { HANDLE updateCheckThread = NULL; // Create the update check thread. if (updateCheckThread = PhCreateThread(0, UpdateCheckThread, context)) NtClose(updateCheckThread); } } break; case WM_SHOWDIALOG: { if (IsMinimized(hwndDlg)) ShowWindow(hwndDlg, SW_RESTORE); else ShowWindow(hwndDlg, SW_SHOW); SetForegroundWindow(hwndDlg); } break; case WM_CTLCOLORBTN: case WM_CTLCOLORDLG: case WM_CTLCOLORSTATIC: { HDC hDC = (HDC)wParam; HWND hwndChild = (HWND)lParam; // Check for our static label and change the color. if (GetWindowID(hwndChild) == IDC_MESSAGE) { SetTextColor(hDC, RGB(19, 112, 171)); } // Set a transparent background for the control backcolor. SetBkMode(hDC, TRANSPARENT); // set window background color. return (INT_PTR)GetSysColorBrush(COLOR_WINDOW); } break; case WM_COMMAND: { switch (GET_WM_COMMAND_ID(wParam, lParam)) { case IDCANCEL: case IDOK: DestroyWindow(hwndDlg); break; case IDC_DOWNLOAD: { switch (context->UpdaterState) { case PhUpdateDefault: { HANDLE updateCheckThread = NULL; SetDlgItemText(hwndDlg, IDC_MESSAGE, L"Checking for new releases..."); SetDlgItemText(hwndDlg, IDC_RELDATE, L""); Button_Enable(GetDlgItem(hwndDlg, IDC_DOWNLOAD), FALSE); if (updateCheckThread = PhCreateThread(0, UpdateCheckThread, context)) NtClose(updateCheckThread); } break; case PhUpdateDownload: { // We can't download updates below Vista due to SHA2 and the // WINHTTP_OPTION_SECURITY_FLAGS option doesn't work on XP for some reason. // Just show the downloads page when there is a new release. if (WindowsVersion >= WINDOWS_VISTA && PhInstalledUsingSetup()) { HANDLE downloadThreadHandle = NULL; // Disable the download button Button_Enable(GetDlgItem(hwndDlg, IDC_DOWNLOAD), FALSE); // Reset the progress bar (might be a download retry) SendDlgItemMessage(hwndDlg, IDC_PROGRESS, PBM_SETPOS, 0, 0); if (WindowsVersion >= WINDOWS_VISTA) SendDlgItemMessage(hwndDlg, IDC_PROGRESS, PBM_SETSTATE, PBST_NORMAL, 0); // Start file download thread if (downloadThreadHandle = PhCreateThread(0, (PUSER_THREAD_START_ROUTINE)UpdateDownloadThread, context)) NtClose(downloadThreadHandle); } else { // Let the user handle non-setup installation, show the homepage and close this dialog. PhShellExecute(hwndDlg, L"http://processhacker.sourceforge.net/downloads.php", NULL); DestroyWindow(hwndDlg); } } break; case PhUpdateInstall: { SHELLEXECUTEINFO info = { sizeof(SHELLEXECUTEINFO) }; if (PhIsNullOrEmptyString(context->SetupFilePath)) break; info.lpFile = context->SetupFilePath->Buffer; info.lpVerb = PhGetOwnTokenAttributes().Elevated ? NULL : L"runas"; info.nShow = SW_SHOW; info.hwnd = hwndDlg; ProcessHacker_PrepareForEarlyShutdown(PhMainWndHandle); if (!ShellExecuteEx(&info)) { // Install failed, cancel the shutdown. ProcessHacker_CancelEarlyShutdown(PhMainWndHandle); // Set button text for next action Button_SetText(GetDlgItem(hwndDlg, IDC_DOWNLOAD), L"Retry"); } else { ProcessHacker_Destroy(PhMainWndHandle); } } break; } } break; } break; } break; case PH_UPDATEAVAILABLE: { // Set updater state context->UpdaterState = PhUpdateDownload; // Set the UI text SetDlgItemText(hwndDlg, IDC_MESSAGE, PhaFormatString( L"Process Hacker %lu.%lu.%lu", context->MajorVersion, context->MinorVersion, context->RevisionVersion )->Buffer); SetDlgItemText(hwndDlg, IDC_RELDATE, PhaFormatString( L"Released: %s", context->RelDate->Buffer )->Buffer); SetDlgItemText(hwndDlg, IDC_STATUS, PhaFormatString( L"Size: %s", context->Size->Buffer )->Buffer); Button_SetText(GetDlgItem(hwndDlg, IDC_DOWNLOAD), L"Download"); // Enable the controls Button_Enable(GetDlgItem(hwndDlg, IDC_DOWNLOAD), TRUE); Control_Visible(GetDlgItem(hwndDlg, IDC_PROGRESS), TRUE); Control_Visible(GetDlgItem(hwndDlg, IDC_INFOSYSLINK), TRUE); } break; case PH_UPDATEISCURRENT: { // Set updater state context->UpdaterState = PhUpdateMaximum; // Set the UI text SetDlgItemText(hwndDlg, IDC_MESSAGE, L"You're running the latest version."); SetDlgItemText(hwndDlg, IDC_RELDATE, PhaFormatString( L"Stable release build: v%lu.%lu.%lu", context->CurrentMajorVersion, context->CurrentMinorVersion, context->CurrentRevisionVersion )->Buffer); // Disable the download button Button_Enable(GetDlgItem(hwndDlg, IDC_DOWNLOAD), FALSE); // Enable the changelog link Control_Visible(GetDlgItem(hwndDlg, IDC_INFOSYSLINK), TRUE); } break; case PH_UPDATENEWER: { context->UpdaterState = PhUpdateMaximum; // Set the UI text SetDlgItemText(hwndDlg, IDC_MESSAGE, L"You're running a newer version!"); SetDlgItemText(hwndDlg, IDC_RELDATE, PhaFormatString( L"Pre-release build: v%lu.%lu.%lu", context->CurrentMajorVersion, context->CurrentMinorVersion, context->CurrentRevisionVersion )->Buffer); // Disable the download button Button_Enable(GetDlgItem(hwndDlg, IDC_DOWNLOAD), FALSE); // Disable the changelog link Control_Visible(GetDlgItem(hwndDlg, IDC_INFOSYSLINK), FALSE); } break; case PH_UPDATESUCCESS: { context->UpdaterState = PhUpdateInstall; // If PH is not elevated, set the UAC shield for the install button as the setup requires elevation. if (!PhGetOwnTokenAttributes().Elevated) SendMessage(GetDlgItem(hwndDlg, IDC_DOWNLOAD), BCM_SETSHIELD, 0, TRUE); // Set the download result, don't include hash status since it succeeded. SetDlgItemText(hwndDlg, IDC_STATUS, L"Click Install to continue update..."); // Set button text for next action Button_SetText(GetDlgItem(hwndDlg, IDC_DOWNLOAD), L"Install"); // Enable the Download/Install button so the user can install the update Button_Enable(GetDlgItem(hwndDlg, IDC_DOWNLOAD), TRUE); } break; case PH_UPDATEFAILURE: { context->UpdaterState = PhUpdateDefault; if (WindowsVersion >= WINDOWS_VISTA) SendDlgItemMessage(hwndDlg, IDC_PROGRESS, PBM_SETSTATE, PBST_ERROR, 0); SetDlgItemText(hwndDlg, IDC_MESSAGE, L"Please check for updates again..."); SetDlgItemText(hwndDlg, IDC_RELDATE, L"An error was encountered while checking for updates."); if ((BOOLEAN)wParam) SetDlgItemText(hwndDlg, IDC_STATUS, L"Hash check failed."); else if ((BOOLEAN)lParam) SetDlgItemText(hwndDlg, IDC_STATUS, L"Signature check failed."); // Set button text for next action Button_SetText(GetDlgItem(hwndDlg, IDC_DOWNLOAD), L"Retry"); // Enable the Install button Button_Enable(GetDlgItem(hwndDlg, IDC_DOWNLOAD), TRUE); // Hash failed, reset state to downloading so user can redownload the file. } break; case PH_UPDATEISERRORED: { context->UpdaterState = PhUpdateDefault; SetDlgItemText(hwndDlg, IDC_MESSAGE, L"Please check for updates again..."); SetDlgItemText(hwndDlg, IDC_RELDATE, L"An error was encountered while checking for updates."); Button_SetText(GetDlgItem(hwndDlg, IDC_DOWNLOAD), L"Retry"); Button_Enable(GetDlgItem(hwndDlg, IDC_DOWNLOAD), TRUE); } break; case PH_UPDATENOTSUPPORTED: { // Set updater state context->UpdaterState = PhUpdateMaximum; // Set the UI text SetDlgItemText(hwndDlg, IDC_MESSAGE, PhaFormatString( L"You're running the latest version: v%lu.%lu.%lu", context->CurrentMajorVersion, context->CurrentMinorVersion, context->CurrentRevisionVersion )->Buffer); SetDlgItemText(hwndDlg, IDC_RELDATE, PhaFormatString( L"v%lu.%lu.%lu is available for Windows 7 and above.", context->MajorVersion, context->MinorVersion, context->RevisionVersion )->Buffer); // Disable the download button Button_Enable(GetDlgItem(hwndDlg, IDC_DOWNLOAD), FALSE); // Enable the changelog link Control_Visible(GetDlgItem(hwndDlg, IDC_INFOSYSLINK), TRUE); } break; case WM_NOTIFY: { switch (((LPNMHDR)lParam)->code) { case NM_CLICK: // Mouse case NM_RETURN: // Keyboard { // Launch the ReleaseNotes URL (if it exists) with the default browser if (!PhIsNullOrEmptyString(context->ReleaseNotesUrl)) PhShellExecute(hwndDlg, context->ReleaseNotesUrl->Buffer, NULL); } break; } } break; } return FALSE; } NTSTATUS ShowUpdateDialogThread( _In_ PVOID Parameter ) { BOOL result; MSG message; PH_AUTO_POOL autoPool; PPH_UPDATER_CONTEXT context = NULL; if (Parameter) context = (PPH_UPDATER_CONTEXT)Parameter; else context = CreateUpdateContext(); PhInitializeAutoPool(&autoPool); UpdateDialogHandle = CreateDialogParam( PluginInstance->DllBase, MAKEINTRESOURCE(IDD_UPDATE), PhMainWndHandle, UpdaterWndProc, (LPARAM)context ); PhSetEvent(&InitializedEvent); while (result = GetMessage(&message, NULL, 0, 0)) { if (result == -1) break; if (!IsDialogMessage(UpdateDialogHandle, &message)) { TranslateMessage(&message); DispatchMessage(&message); } PhDrainAutoPool(&autoPool); } PhDeleteAutoPool(&autoPool); PhResetEvent(&InitializedEvent); if (UpdateDialogThreadHandle) { NtClose(UpdateDialogThreadHandle); UpdateDialogThreadHandle = NULL; } return STATUS_SUCCESS; } VOID ShowUpdateDialog( _In_opt_ PPH_UPDATER_CONTEXT Context ) { if (!UpdateDialogThreadHandle) { if (!(UpdateDialogThreadHandle = PhCreateThread(0, ShowUpdateDialogThread, Context))) { PhShowStatus(PhMainWndHandle, L"Unable to create the updater window.", 0, GetLastError()); return; } PhWaitForEvent(&InitializedEvent, NULL); } PostMessage(UpdateDialogHandle, WM_SHOWDIALOG, 0, 0); } VOID StartInitialCheck( VOID ) { HANDLE silentCheckThread = NULL; if (silentCheckThread = PhCreateThread(0, UpdateCheckSilentThread, NULL)) NtClose(silentCheckThread); }