diff options
Diffstat (limited to 'installer/fetcher/fetcher.c')
-rw-r--r-- | installer/fetcher/fetcher.c | 340 |
1 files changed, 340 insertions, 0 deletions
diff --git a/installer/fetcher/fetcher.c b/installer/fetcher/fetcher.c new file mode 100644 index 00000000..5c688997 --- /dev/null +++ b/installer/fetcher/fetcher.c @@ -0,0 +1,340 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020-2022 Jason A. Donenfeld. All Rights Reserved. + */ + +#include <windows.h> +#include <delayimp.h> +#include <commctrl.h> +#include <shlwapi.h> +#include <ntsecapi.h> +#include <sddl.h> +#include <winhttp.h> +#include <wintrust.h> +#include <softpub.h> +#include <msi.h> +#include <stdio.h> +#include <string.h> +#include <stdbool.h> +#include <wchar.h> +#include "filelist.h" +#include "crypto.h" +#include "systeminfo.h" +#include "constants.h" + +static char msi_filename[MAX_PATH]; +static volatile bool msi_filename_is_set, prompts = true; +static volatile size_t g_current, g_total; +static HWND progress; +static HANDLE filehandle = INVALID_HANDLE_VALUE; + +static wchar_t *L(const char *a) +{ + static wchar_t w[0x2000]; + if (!MultiByteToWideChar(CP_UTF8, 0, a, -1, w, sizeof(w))) + abort(); + return w; +} + +static bool random_string(char hex[static 65]) +{ + uint8_t bytes[32]; + if (!RtlGenRandom(bytes, sizeof(bytes))) + return false; + for (int i = 0; i < 32; ++i) { + hex[i * 2] = 87U + (bytes[i] >> 4) + ((((bytes[i] >> 4) - 10U) >> 8) & ~38U); + hex[i * 2 + 1] = 87U + (bytes[i] & 0xf) + ((((bytes[i] & 0xf) - 10U) >> 8) & ~38U); + } + hex[64] = '\0'; + return true; +} + +static void set_status(HWND progress, const char *status) +{ + LONG_PTR current_style = GetWindowLongPtrA(progress, GWL_STYLE); + char buf[0x1000]; + g_total = 0; + _snprintf_s(buf, sizeof(buf), _TRUNCATE, "WireGuard: %s...", status); + SetWindowTextA(progress, buf); + if (!(current_style & PBS_MARQUEE)) { + SendMessageA(progress, PBM_SETRANGE32, 0, 100); + SendMessageA(progress, PBM_SETPOS, 0, 0); + SetWindowLongPtrA(progress, GWL_STYLE, current_style | PBS_MARQUEE); + SendMessageA(progress, PBM_SETMARQUEE, TRUE, 0); + } +} + +static void set_progress(HWND progress, size_t current, size_t total) +{ + g_current = current; + g_total = total; + PostMessageA(progress, WM_APP, 0, 0); +} + +static DWORD __stdcall download_thread(void *param) +{ + DWORD ret = 1, bytes_read, bytes_written, enable_http2 = WINHTTP_PROTOCOL_FLAG_HTTP2; + HINTERNET session = NULL, connection = NULL, request = NULL; + uint8_t hash[32], computed_hash[32], buf[512 * 1024]; + char download_path[MAX_FILENAME_LEN + sizeof(msi_path)], random_filename[65]; + wchar_t total_bytes_str[22]; + size_t total_bytes, current_bytes; + const char *arch; + struct blake2b256_state hasher; + SECURITY_ATTRIBUTES security_attributes = { .nLength = sizeof(security_attributes) }; + WINTRUST_FILE_INFO wintrust_fileinfo = { .cbStruct = sizeof(wintrust_fileinfo) }; + WINTRUST_DATA wintrust_data = { + .cbStruct = sizeof(wintrust_data), + .dwUIChoice = WTD_UI_NONE, + .fdwRevocationChecks = WTD_REVOKE_WHOLECHAIN, + .dwUnionChoice = WTD_CHOICE_FILE, + .dwStateAction = WTD_STATEACTION_VERIFY, + .pFile = &wintrust_fileinfo + }; + + (void)param; + + set_status(progress, "determining paths"); + if (!ConvertStringSecurityDescriptorToSecurityDescriptorA("O:BAD:PAI(A;;FA;;;BA)", SDDL_REVISION_1, &security_attributes.lpSecurityDescriptor, NULL)) + goto out; + if (!GetWindowsDirectoryA(msi_filename, sizeof(msi_filename)) || !PathAppendA(msi_filename, "Temp")) + goto out; + if (!random_string(random_filename)) + goto out; + if (!PathAppendA(msi_filename, random_filename)) + goto out; + + set_status(progress, "determining architecture"); + arch = architecture(); + if (!arch) + goto out; + + set_status(progress, "connecting to server"); + session = WinHttpOpen(L(useragent()), is_win7() ? WINHTTP_ACCESS_TYPE_DEFAULT_PROXY : WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY, NULL, NULL, 0); + if (!session) + goto out; + WinHttpSetOption(session, WINHTTP_OPTION_ENABLE_HTTP_PROTOCOL, &enable_http2, sizeof(enable_http2)); // Don't check return value, in case of old Windows + if (is_win8dotzero_or_below()) { + DWORD enable_tls12 = WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2; + if (!WinHttpSetOption(session, WINHTTP_OPTION_SECURE_PROTOCOLS, &enable_tls12, sizeof(enable_tls12))) + goto out; + } + + connection = WinHttpConnect(session, L(server), port, 0); + if (!connection) + goto out; + + set_status(progress, "downloading installer list"); + request = WinHttpOpenRequest(connection, L"GET", L(msi_path latest_version_file), NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_REFRESH | WINHTTP_FLAG_SECURE); + if (!request) + goto out; + if (!WinHttpSendRequest(request, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0)) + goto out; + if (!WinHttpReceiveResponse(request, NULL)) + goto out; + if (!WinHttpReadData(request, buf, sizeof(buf), &bytes_read)) + goto out; + WinHttpCloseHandle(request); + request = NULL; + if (bytes_read <= 0 || bytes_read >= sizeof(buf)) + goto out; + + set_status(progress, "verifying installer list"); + memcpy(download_path, msi_path, strlen(msi_path)); + if (!extract_newest_file(download_path + strlen(msi_path), hash, (const char *)buf, bytes_read, arch)) + goto out; + + set_status(progress, "creating temporary file"); + filehandle = CreateFileA(msi_filename, GENERIC_WRITE | DELETE, 0, &security_attributes, CREATE_NEW, FILE_ATTRIBUTE_TEMPORARY, NULL); + if (filehandle == INVALID_HANDLE_VALUE) + goto out; + msi_filename_is_set = true; + + set_status(progress, "downloading installer"); + request = WinHttpOpenRequest(connection, L"GET", L(download_path), NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_SECURE); + if (!request) + goto out; + if (!WinHttpSendRequest(request, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0)) + goto out; + if (!WinHttpReceiveResponse(request, NULL)) + goto out; + bytes_read = sizeof(total_bytes_str); + if (!WinHttpQueryHeaders(request, WINHTTP_QUERY_CONTENT_LENGTH, WINHTTP_HEADER_NAME_BY_INDEX, total_bytes_str, &bytes_read, WINHTTP_NO_HEADER_INDEX)) + goto out; + total_bytes = wcstoul(total_bytes_str, NULL, 10); + if (total_bytes > 100 * 1024 * 1024) + goto out; + blake2b256_init(&hasher); + set_progress(progress, 0, total_bytes); + for (current_bytes = 0;;) { + if (!WinHttpReadData(request, buf, 8192, &bytes_read)) + goto out; + if (!bytes_read) + break; + current_bytes += bytes_read; + if (current_bytes > 100 * 1024 * 1024) + goto out; + blake2b256_update(&hasher, buf, bytes_read); + if (!WriteFile(filehandle, buf, bytes_read, &bytes_written, NULL) || bytes_read != bytes_written) + goto out; + set_progress(progress, current_bytes, total_bytes); + } + + set_status(progress, "verifying installer"); + blake2b256_final(&hasher, computed_hash); + if (memcmp(hash, computed_hash, sizeof(hash))) + goto out; + CloseHandle(filehandle); //TODO: I wish this wasn't required. + filehandle = INVALID_HANDLE_VALUE; + wintrust_fileinfo.pcwszFilePath = L(msi_filename); + ret = WinVerifyTrustEx(INVALID_HANDLE_VALUE, &(GUID)WINTRUST_ACTION_GENERIC_VERIFY_V2, &wintrust_data); + wintrust_data.dwStateAction = WTD_STATEACTION_CLOSE; + WinVerifyTrustEx(INVALID_HANDLE_VALUE, &(GUID)WINTRUST_ACTION_GENERIC_VERIFY_V2, &wintrust_data); + if (ret) + goto out; + + set_status(progress, "launching installer"); + ShowWindow(progress, SW_HIDE); + ret = MsiInstallProductA(msi_filename, NULL); + ret = ret == ERROR_INSTALL_USEREXIT ? ERROR_SUCCESS : ret; + +out: + if (request) + WinHttpCloseHandle(request); + if (connection) + WinHttpCloseHandle(connection); + if (session) + WinHttpCloseHandle(session); + if (security_attributes.lpSecurityDescriptor) + LocalFree(security_attributes.lpSecurityDescriptor); + + if (ret && prompts) { + ShowWindow(progress, SW_SHOWDEFAULT); + if (MessageBoxA(progress, "Something went wrong when downloading the WireGuard installer. Would you like to open your web browser to the MSI download page?", "Download Error", MB_YESNO | MB_ICONWARNING) == IDYES) + ShellExecuteA(progress, NULL, "https://" server msi_path, NULL, NULL, SW_SHOWNORMAL); + } + exit(ret); + return ret; +} + +static int cleanup(void) +{ + BOOL did_delete_via_handle = FALSE; + FILE_DISPOSITION_INFO disposition = { TRUE }; + if (filehandle != INVALID_HANDLE_VALUE) { + did_delete_via_handle = SetFileInformationByHandle(filehandle, FileDispositionInfo, &disposition, sizeof(disposition)); + CloseHandle(filehandle); + filehandle = INVALID_HANDLE_VALUE; + } + if (msi_filename_is_set && !did_delete_via_handle) { + //TODO: how does DeleteFile deal with reparse points? + for (int i = 0; i < 200 && !DeleteFileA(msi_filename) && GetLastError() != ERROR_FILE_NOT_FOUND; ++i) + Sleep(200); + } + return 0; +} + +static FARPROC WINAPI delayed_load_library_hook(unsigned dliNotify, PDelayLoadInfo pdli) +{ + HMODULE library; + if (dliNotify != dliNotePreLoadLibrary) + return NULL; + library = LoadLibraryExA(pdli->szDll, NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); + if (!library) + abort(); + return (FARPROC)library; +} + +PfnDliHook __pfnDliNotifyHook2 = delayed_load_library_hook; + +static LRESULT CALLBACK wndproc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData) +{ + (void)uIdSubclass; (void)dwRefData; + + switch (uMsg) { + case WM_CLOSE: + case WM_DESTROY: { + LRESULT ret = DefSubclassProc(hWnd, uMsg, wParam, lParam); + exit(0); + return ret; + } + case WM_APP: if (g_total) { + char buf[0x1000], *start, *paren; + LONG_PTR current_style; + int chars = GetWindowTextA(progress, buf, sizeof(buf)); + if (chars) { + start = buf + chars; + if (start[-1] == '.' && start[-2] == '.' && start[-3] == '.') + start -= 3; + else if ((paren = memchr(buf, '(', chars)) && paren > buf) + start = paren - 1; + *start = '\0'; + _snprintf_s(start, sizeof(buf) - (start - buf), _TRUNCATE, " (%.2f%%)", g_current * 100.0f / g_total); + SetWindowTextA(progress, buf); + } + current_style = GetWindowLongPtrA(progress, GWL_STYLE); + if (current_style & PBS_MARQUEE) { + SetWindowLongPtrA(progress, GWL_STYLE, current_style & ~PBS_MARQUEE); + SendMessageA(progress, PBM_SETMARQUEE, FALSE, 0); + } + SendMessageA(progress, PBM_SETRANGE32, 0, (LPARAM)g_total); + SendMessageA(progress, PBM_SETPOS, (WPARAM)g_current, 0); + break; + } + } + return DefSubclassProc(hWnd, uMsg, wParam, lParam); +} + +static void parse_command_line(void) +{ + LPWSTR *argv; + int argc; + argv = CommandLineToArgvW(GetCommandLineW(), &argc); + if (!argv) + return; + for (int i = 1; i < argc; ++i) { + if (wcsicmp(argv[i], L"/noprompt") == 0) + prompts = false; + } + LocalFree(argv); +} + +int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR pCmdLine, int nCmdShow) +{ + MSG msg; + HICON icon; + HDC dc; + float scale; + + (void)hPrevInstance; (void)pCmdLine; (void)nCmdShow; + + if (!SetDllDirectoryA("") || !SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_SYSTEM32)) + return 1; + + parse_command_line(); + + InitCommonControlsEx(&(INITCOMMONCONTROLSEX){ .dwSize = sizeof(INITCOMMONCONTROLSEX), .dwICC = ICC_PROGRESS_CLASS }); + + progress = CreateWindowExA(0, PROGRESS_CLASS, "WireGuard Installer", + (WS_OVERLAPPEDWINDOW & ~(WS_BORDER | WS_THICKFRAME | WS_MAXIMIZEBOX)) | PBS_MARQUEE | PBS_SMOOTH, + CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + NULL, NULL, hInstance, NULL); + SetWindowSubclass(progress, wndproc, 0, 0); + dc = GetDC(progress); + scale = GetDeviceCaps(dc, LOGPIXELSY) / 96.0f; + ReleaseDC(progress, dc); + icon = LoadIconA(hInstance, MAKEINTRESOURCE(7)); + SendMessageA(progress, WM_SETICON, ICON_BIG, (LPARAM)icon); + SendMessageA(progress, WM_SETICON, ICON_SMALL, (LPARAM)icon); + SendMessageA(progress, PBM_SETMARQUEE, TRUE, 0); + SetWindowPos(progress, HWND_TOPMOST, -1, -1, 500 * scale, 80 * scale, SWP_NOMOVE | SWP_SHOWWINDOW); + + _onexit(cleanup); + CreateThread(NULL, 0, download_thread, NULL, 0, NULL); + + while (GetMessage(&msg, NULL, 0, 0)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + return 0; +} |