diff options
Diffstat (limited to 'installer/customactions.c')
-rw-r--r-- | installer/customactions.c | 403 |
1 files changed, 403 insertions, 0 deletions
diff --git a/installer/customactions.c b/installer/customactions.c new file mode 100644 index 00000000..7217fca8 --- /dev/null +++ b/installer/customactions.c @@ -0,0 +1,403 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2019 WireGuard LLC. All Rights Reserved. + */ + +#include <windows.h> +#include <msi.h> +#include <msidefs.h> +#include <msiquery.h> +#include <shlwapi.h> +#include <stdbool.h> +#include <tchar.h> + +#define MANAGER_SERVICE_NAME TEXT("WireGuardManager") +#define TUNNEL_SERVICE_PREFIX TEXT("WireGuardTunnel$") +#define ENUM_SERVICE_STATUS_PROCESS_SIZE 0x10000 + +typedef enum +{ + LOG_LEVEL_INFO = 0, + LOG_LEVEL_WARN, + LOG_LEVEL_ERR +} log_level_t; + +static TCHAR *format_message(const TCHAR *template, const DWORD_PTR argv[]) +{ + TCHAR *formatted_message = NULL; + if (!FormatMessage( + FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_ARGUMENT_ARRAY | FORMAT_MESSAGE_MAX_WIDTH_MASK, + template, + 0, + 0, + (VOID *)&formatted_message, + 0, + (va_list *)argv)) + return NULL; + return formatted_message; +} + +static void log_message(MSIHANDLE installer, log_level_t level, const TCHAR *log_line) +{ + MSIHANDLE record = MsiCreateRecord(2); + if (!record) + return; + TCHAR *template; + INSTALLMESSAGE type; + switch (level) { + case LOG_LEVEL_INFO: + template = TEXT("Custom action: [1]"); + type = INSTALLMESSAGE_INFO; + break; + case LOG_LEVEL_WARN: + template = TEXT("Custom action warning: [1]"); + type = INSTALLMESSAGE_INFO; + break; + case LOG_LEVEL_ERR: + template = TEXT("Custom action error: [1]"); + type = INSTALLMESSAGE_ERROR; + break; + default: + goto cleanup; + } + MsiRecordSetString(record, 0, template); + MsiRecordSetString(record, 1, log_line); + MsiProcessMessage(installer, type, record); +cleanup: + MsiCloseHandle(record); +} + +static void log_messagef(MSIHANDLE installer, log_level_t level, const TCHAR *template, const DWORD_PTR argv[]) +{ + TCHAR *formatted_message = format_message(template, argv); + if (formatted_message) { + log_message(installer, level, formatted_message); + LocalFree(formatted_message); + } +} + +static void log_error(MSIHANDLE installer, DWORD error_code, const TCHAR *prefix) +{ + TCHAR *system_message = NULL; + FormatMessage( + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_MAX_WIDTH_MASK, + NULL, + HRESULT_FROM_SETUPAPI(error_code), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (VOID *)&system_message, + 0, + NULL); + log_messagef( + installer, + LOG_LEVEL_ERR, + system_message ? TEXT("%1: %3(Code 0x%2!08X!)") : TEXT("%1: Code 0x%2!08X!"), + (DWORD_PTR[]){ (DWORD_PTR)prefix, error_code, (DWORD_PTR)system_message }); + LocalFree(system_message); +} + +static void log_errorf(MSIHANDLE installer, DWORD error_code, const TCHAR *template, const DWORD_PTR argv[]) +{ + TCHAR *formatted_message = format_message(template, argv); + if (formatted_message) { + log_error(installer, error_code, formatted_message); + LocalFree(formatted_message); + } +} + +static bool is_valid_tunnel_name(const TCHAR *tunnel_name) +{ + for (size_t i = 0; ; i++) { + TCHAR c = tunnel_name[i]; + if (!c) + return i > 0; + if (i >= 32) + return false; + if ((c < TEXT('a') || c > TEXT('z')) && + (c < TEXT('A') || c > TEXT('Z')) && + (c < TEXT('0') || c > TEXT('9')) && + c != TEXT('_') && + c != TEXT('=') && + c != TEXT('+') && + c != TEXT('.') && + c != TEXT('-')) + return false; + } +} + +static void replace_msi_operators(TCHAR *dest, const TCHAR *src, size_t count) +{ + for (size_t i = 0; i < count; i++) { + TCHAR c = src[i]; + switch (c) { + case 0: + dest[i] = 0; + return; + case TEXT('='): + case TEXT('+'): + case TEXT('-'): + dest[i] = TEXT('_'); + break; + default: + dest[i] = c; + } + } +} + +static bool is_service_started(MSIHANDLE installer, SC_HANDLE scm, const TCHAR *service_name) +{ + bool ret = false; + SC_HANDLE service = NULL; + SERVICE_STATUS_PROCESS service_status; + DWORD service_status_size = 0; + const DWORD_PTR log_argv[] = { (DWORD_PTR)__FUNCTION__, (DWORD_PTR)service_name }; + + service = OpenService(scm, MANAGER_SERVICE_NAME, SERVICE_QUERY_STATUS); + if (!service) { + log_errorf(installer, GetLastError(), TEXT("%1!hs!: OpenService failed for service %2"), log_argv); + goto cleanup; + } + if (!QueryServiceStatusEx(service, SC_STATUS_PROCESS_INFO, (LPBYTE)&service_status, sizeof(service_status), &service_status_size)) { + log_errorf(installer, GetLastError(), TEXT("%1!hs!: QueryServiceStatusEx failed for service %2"), log_argv); + goto cleanup; + } + if (service_status.dwCurrentState != SERVICE_STOPPED && service_status.dwCurrentState != SERVICE_STOP_PENDING) + ret = true; + +cleanup: + if (service) + CloseServiceHandle(service); + return ret; +} + +static UINT insert_service_control(MSIHANDLE installer, MSIHANDLE view, const TCHAR *service_name, bool start) +{ + UINT ret = ERROR_INSTALL_FAILURE; + MSIHANDLE record = 0; + const DWORD_PTR log_argv[] = { (DWORD_PTR)__FUNCTION__, (DWORD_PTR)service_name }; + size_t service_name_len; + TCHAR *sanitized_service_name = NULL, *service_control_stop = NULL, *service_control_start = NULL; + static unsigned int index = 0; + + record = MsiCreateRecord(5); + if (!record) + goto cleanup; + + service_name_len = _tcslen(service_name) + 1; + sanitized_service_name = LocalAlloc(LMEM_FIXED, sizeof(TCHAR) * service_name_len); + if (!sanitized_service_name) { + ret = GetLastError(); + log_errorf(installer, ret, TEXT("%1!hs!: LocalAlloc failed for service %2"), log_argv); + goto cleanup; + } + replace_msi_operators(sanitized_service_name, service_name, service_name_len); + + log_messagef(installer, LOG_LEVEL_INFO, TEXT("%1!hs!: Scheduling stop on upgrade or removal on uninstall of service %2"), log_argv); + service_control_stop = format_message(TEXT("stop_%1%2!u!"), (DWORD_PTR[]){ (DWORD_PTR)sanitized_service_name, index++ }); + if (!service_control_stop) { + ret = GetLastError(); + log_errorf(installer, ret, TEXT("%1!hs!: FormatMessage failed for service %2"), log_argv); + goto cleanup; + } + MsiRecordSetString (record, 1/*ServiceControl*/, service_control_stop); + MsiRecordSetString (record, 2/*Name */, service_name); + MsiRecordSetInteger(record, 3/*Event */, msidbServiceControlEventStop | msidbServiceControlEventUninstallStop | msidbServiceControlEventUninstallDelete); + MsiRecordSetString (record, 4/*Component_ */, TEXT("WireGuardExecutable")); + MsiRecordSetInteger(record, 5/*Wait */, 1); /* Waits 30 seconds. */ + ret = MsiViewExecute(view, record); + if (ret != ERROR_SUCCESS) { + log_errorf(installer, ret, TEXT("%1!hs!: MsiViewExecute failed for service %2"), log_argv); + goto cleanup; + } + if (!start) + goto cleanup; + + log_messagef(installer, LOG_LEVEL_INFO, TEXT("%1!hs!: Scheduling start on upgrade of service %2"), log_argv); + service_control_start = format_message(TEXT("start_%1%2!u!"), (DWORD_PTR[]){ (DWORD_PTR)sanitized_service_name, index++ }); + if (!service_control_start) { + ret = GetLastError(); + log_errorf(installer, ret, TEXT("%1!hs!: FormatMessage failed for service %2"), log_argv); + goto cleanup; + } + MsiRecordSetString (record, 1/*ServiceControl*/, service_control_start); + MsiRecordSetString (record, 2/*Name */, service_name); + MsiRecordSetInteger(record, 3/*Event */, msidbServiceControlEventStart); + MsiRecordSetString (record, 4/*Component_ */, TEXT("WireGuardExecutable")); + MsiRecordSetInteger(record, 5/*Wait */, 0); /* No wait, so that failure to restart again isn't fatal. */ + ret = MsiViewExecute(view, record); + if (ret != ERROR_SUCCESS) { + log_errorf(installer, ret, TEXT("%1!hs!: MsiViewExecute failed for service %2"), log_argv); + goto cleanup; + } + +cleanup: + if (service_control_start) + LocalFree(service_control_start); + if (service_control_stop) + LocalFree(service_control_stop); + if (sanitized_service_name) + LocalFree(sanitized_service_name); + if (record) + MsiCloseHandle(record); + return ret; +} + +static bool remove_folder(MSIHANDLE installer, TCHAR path[MAX_PATH]) +{ + HANDLE find_handle; + WIN32_FIND_DATA find_data; + TCHAR *path_end; + const DWORD_PTR log_argv[] = { (DWORD_PTR)__FUNCTION__, (DWORD_PTR)path }; + + path_end = path + _tcsnlen(path, MAX_PATH); + if (!PathAppend(path, TEXT("*.*"))) { + log_messagef(installer, LOG_LEVEL_ERR, TEXT("%1!hs!: PathAppend(%2) failed"), log_argv); + return false; + } + find_handle = FindFirstFileEx(path, FindExInfoBasic, &find_data, FindExSearchNameMatch, NULL, 0); + if (find_handle == INVALID_HANDLE_VALUE) { + log_errorf(installer, GetLastError(), TEXT("%1!hs!: FindFirstFileEx(%2) failed"), log_argv); + return false; + } + do { + if (find_data.cFileName[0] == TEXT('.') && (!find_data.cFileName[1] || (find_data.cFileName[1] == TEXT('.') && !find_data.cFileName[2]))) + continue; + + path_end[0] = 0; + if (!PathAppend(path, find_data.cFileName)) { + log_messagef(installer, LOG_LEVEL_ERR, TEXT("%1!hs!: PathAppend(%2) failed"), log_argv); + continue; + } + + if (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + remove_folder(installer, path); + continue; + } + + if ((find_data.dwFileAttributes & FILE_ATTRIBUTE_READONLY) && !SetFileAttributes(path, find_data.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY)) + log_errorf(installer, GetLastError(), TEXT("%1!hs!: SetFileAttributes(%2) failed"), log_argv); + + if (DeleteFile(path)) + log_messagef(installer, LOG_LEVEL_INFO, TEXT("%1!hs!: %2 removed"), log_argv); + else + log_errorf(installer, GetLastError(), TEXT("%1!hs!: DeleteFile(%2) failed"), log_argv); + } while (FindNextFile(find_handle, &find_data)); + FindClose(find_handle); + + path_end[0] = 0; + if (RemoveDirectory(path)) { + log_messagef(installer, LOG_LEVEL_INFO, TEXT("%1!hs!: %2 removed"), log_argv); + return true; + } else { + log_errorf(installer, GetLastError(), TEXT("%1!hs!: RemoveDirectory(%2) failed"), log_argv); + return false; + } +} + +__declspec(dllexport) UINT __stdcall EvaluateWireGuardServices(MSIHANDLE installer) +{ + UINT ret = ERROR_INSTALL_FAILURE; + BOOL is_com_initialized = SUCCEEDED(CoInitialize(NULL)); + MSIHANDLE db = 0, view = 0; + SC_HANDLE scm = NULL; + ENUM_SERVICE_STATUS_PROCESS *service_status = NULL; + DWORD service_status_resume = 0; + const DWORD_PTR log_argv[] = { (DWORD_PTR)__FUNCTION__ }; + + db = MsiGetActiveDatabase(installer); + if (!db) { + log_messagef(installer, LOG_LEVEL_ERR, TEXT("%1!hs!: MsiGetActiveDatabase failed"), log_argv); + goto cleanup; + } + ret = MsiDatabaseOpenView( + db, + TEXT("INSERT INTO `ServiceControl` (`ServiceControl`, `Name`, `Event`, `Component_`, `Wait`) VALUES(?, ?, ?, ?, ?) TEMPORARY"), + &view); + if (ret != ERROR_SUCCESS) { + log_errorf(installer, ret, TEXT("%1!hs!: MsiDatabaseOpenView failed"), log_argv); + goto cleanup; + } + scm = OpenSCManager(NULL, SERVICES_ACTIVE_DATABASE, SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE); + if (!scm) { + ret = GetLastError(); + log_errorf(installer, ret, TEXT("%1!hs!: OpenSCManager failed"), log_argv); + goto cleanup; + } + + insert_service_control(installer, view, MANAGER_SERVICE_NAME, is_service_started(installer, scm, MANAGER_SERVICE_NAME)); + + service_status = LocalAlloc(LMEM_FIXED, ENUM_SERVICE_STATUS_PROCESS_SIZE); + if (!service_status) { + ret = GetLastError(); + log_errorf(installer, ret, TEXT("%1!hs!: LocalAlloc failed"), log_argv); + goto cleanup; + } + for (bool more_services = true; more_services;) { + DWORD service_status_size = 0, service_status_count = 0; + if (EnumServicesStatusEx( + scm, + SC_ENUM_PROCESS_INFO, + SERVICE_WIN32, + SERVICE_STATE_ALL, + (LPBYTE)service_status, + ENUM_SERVICE_STATUS_PROCESS_SIZE, + &service_status_size, + &service_status_count, + &service_status_resume, + NULL)) + more_services = false; + else { + ret = GetLastError(); + if (ret != ERROR_MORE_DATA) { + log_errorf(installer, ret, TEXT("%1!hs!: EnumServicesStatusEx failed"), log_argv); + break; + } + } + + for (DWORD i = 0; i < service_status_count; i++) { + if (_tcsnicmp(service_status[i].lpServiceName, TUNNEL_SERVICE_PREFIX, _countof(TUNNEL_SERVICE_PREFIX) - 1) == 0) { + const TCHAR *tunnel_name = service_status[i].lpServiceName + _countof(TUNNEL_SERVICE_PREFIX) - 1; + if (is_valid_tunnel_name(tunnel_name)) + insert_service_control( + installer, + view, + service_status[i].lpServiceName, + service_status[i].ServiceStatusProcess.dwCurrentState != SERVICE_STOPPED && service_status[i].ServiceStatusProcess.dwCurrentState != SERVICE_STOP_PENDING); + } + } + } + + ret = ERROR_SUCCESS; + +cleanup: + if (service_status) + LocalFree(service_status); + if (scm) + CloseServiceHandle(scm); + if (view) + MsiCloseHandle(view); + if (db) + MsiCloseHandle(db); + if (is_com_initialized) + CoUninitialize(); + return ret == ERROR_SUCCESS ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; +} + +__declspec(dllexport) UINT __stdcall RemoveConfigFolder(MSIHANDLE installer) +{ + LSTATUS result; + TCHAR path[MAX_PATH]; + + result = SHRegGetPath( + HKEY_LOCAL_MACHINE, + TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList\\S-1-5-18"), + TEXT("ProfileImagePath"), + path, + 0); + if (result != ERROR_SUCCESS) { + log_errorf(installer, result, TEXT("%1!hs!: SHRegGetPath failed"), (DWORD_PTR[]){ (DWORD_PTR)__FUNCTION__ }); + return ERROR_SUCCESS; + } + PathAppend(path, TEXT("AppData\\Local\\WireGuard")); + remove_folder(installer, path); + return ERROR_SUCCESS; +} |