diff options
Diffstat (limited to 'installer/msi.c')
-rw-r--r-- | installer/msi.c | 266 |
1 files changed, 266 insertions, 0 deletions
diff --git a/installer/msi.c b/installer/msi.c new file mode 100644 index 0000000..348ef42 --- /dev/null +++ b/installer/msi.c @@ -0,0 +1,266 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (C) 2018-2019 WireGuard LLC. All Rights Reserved. + */ + +#include "installation.h" +#include <Windows.h> +#include <Msi.h> +#include <MsiQuery.h> +#include <tchar.h> + +#pragma warning(disable : 4100) /* unreferenced formal parameter */ + +static MSIHANDLE MsiHandle; + +#define ANCHOR_COMPONENT TEXT("{B668D4C7-ABB3-485A-B8DF-D34200489A43}") +#define PROCESS_ACTION TEXT("ProcessWintun") +#define ACTION_INSTALL TEXT("/WintunAction=Install") +#define ACTION_INSTALL_SEPERATOR TEXT('-') +#define ACTION_INSTALL_SEPERATORS TEXT("-%s-%s-%s") +#define ACTION_UNINSTALL TEXT("/WintunAction=Uninstall") +#define PROPERTY_INSTALLER_HASH TEXT("WintunInstallerHash") +#define PROPERTY_INSTALLER_BUILDTIME TEXT("WintunInstallerBuildtime") +#define PROPERTY_VERSION TEXT("WintunVersion") +#define REGKEY_WINTUN TEXT("Software\\Wintun") +#define REGKEY_INSTALLER_HASH TEXT("InstallerHash") +#define REGKEY_INSTALLER_BUILDTIME TEXT("InstallerBuildtime") +#define REGKEY_VERSION TEXT("Version") + +static VOID +MsiLogger(_In_ LOGGER_LEVEL Level, _In_ const TCHAR *LogLine) +{ + MSIHANDLE Record = MsiCreateRecord(2); + if (!Record) + return; + TCHAR *Template; + INSTALLMESSAGE Type; + switch (Level) + { + case LOG_INFO: + Template = TEXT("Wintun: [1]"); + Type = INSTALLMESSAGE_INFO; + break; + case LOG_WARN: + Template = TEXT("Wintun warning: [1]"); + Type = INSTALLMESSAGE_INFO; + break; + case LOG_ERR: + Template = TEXT("Wintun error: [1]"); + Type = INSTALLMESSAGE_ERROR; + break; + default: + goto cleanup; + } + MsiRecordSetString(Record, 0, Template); + MsiRecordSetString(Record, 1, LogLine); + MsiProcessMessage(MsiHandle, Type, Record); +cleanup: + MsiCloseHandle(Record); +} + +static BOOL +IsInstalling(_In_ INSTALLSTATE InstallState, _In_ INSTALLSTATE ActionState) +{ + return INSTALLSTATE_LOCAL == ActionState || INSTALLSTATE_SOURCE == ActionState || + (INSTALLSTATE_DEFAULT == ActionState && + (INSTALLSTATE_LOCAL == InstallState || INSTALLSTATE_SOURCE == InstallState)); +} + +static BOOL +IsReInstalling(_In_ INSTALLSTATE InstallState, _In_ INSTALLSTATE ActionState) +{ + return (INSTALLSTATE_LOCAL == ActionState || INSTALLSTATE_SOURCE == ActionState || + INSTALLSTATE_DEFAULT == ActionState) && + (INSTALLSTATE_LOCAL == InstallState || INSTALLSTATE_SOURCE == InstallState); +} + +static BOOL +IsUninstalling(_In_ INSTALLSTATE InstallState, _In_ INSTALLSTATE ActionState) +{ + return (INSTALLSTATE_ABSENT == ActionState || INSTALLSTATE_REMOVED == ActionState) && + (INSTALLSTATE_LOCAL == InstallState || INSTALLSTATE_SOURCE == InstallState); +} + +static UINT64 +ParseVersion(_In_ const TCHAR *Version) +{ + ULONG Major = 0, Minor = 0, Revision = 0, Build = 0; + _stscanf_s(Version, TEXT("%u.%u.%u.%u"), &Major, &Minor, &Revision, &Build); + return ((UINT64)Major << 48) | ((UINT64)Minor << 32) | ((UINT64)Revision << 16) | ((UINT64)Build << 0); +} + +_Success_(return ) +static BOOL +Newer(_In_ MSIHANDLE Handle, _In_ BOOL SkipHashComparison, _Out_ TCHAR *InstallAction, _In_ SIZE_T InstallActionSize) +{ + INT64 NewTime, OldTime; + UINT64 NewVersion, OldVersion; + TCHAR NewHash[0x100], OldHash[0x100], NewTimeString[0x100], OldTimeString[0x100], NewVersionString[0x100], + OldVersionString[0x100]; + DWORD Size, Type; + HKEY Key; + BOOL Ret = TRUE; + + Size = _countof(NewHash); + if (MsiGetProperty(Handle, PROPERTY_INSTALLER_HASH, NewHash, &Size) != ERROR_SUCCESS) + return FALSE; + Size = _countof(NewTimeString); + if (MsiGetProperty(Handle, PROPERTY_INSTALLER_BUILDTIME, NewTimeString, &Size) != ERROR_SUCCESS) + return FALSE; + NewTime = _tstoll(NewTimeString); + Size = _countof(NewVersionString); + if (MsiGetProperty(Handle, PROPERTY_VERSION, NewVersionString, &Size) != ERROR_SUCCESS) + return FALSE; + NewVersion = ParseVersion(NewVersionString); + + _stprintf_s( + InstallAction, + InstallActionSize, + ACTION_INSTALL ACTION_INSTALL_SEPERATORS, + NewHash, + NewTimeString, + NewVersionString); + + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, REGKEY_WINTUN, 0, KEY_READ, &Key) != ERROR_SUCCESS) + return TRUE; + Size = sizeof(OldHash); + if (RegQueryValueEx(Key, REGKEY_INSTALLER_HASH, NULL, &Type, (LPBYTE)OldHash, &Size) != ERROR_SUCCESS || + Type != REG_SZ) + goto cleanup; + Size = sizeof(OldTimeString); + if (RegQueryValueEx(Key, REGKEY_INSTALLER_BUILDTIME, NULL, &Type, (LPBYTE)OldTimeString, &Size) != ERROR_SUCCESS || + Type != REG_SZ) + goto cleanup; + OldTime = _tstoll(OldTimeString); + Size = sizeof(OldVersionString); + if (RegQueryValueEx(Key, REGKEY_VERSION, NULL, &Type, (LPBYTE)OldVersionString, &Size) != ERROR_SUCCESS || + Type != REG_SZ) + goto cleanup; + OldVersion = ParseVersion(OldVersionString); + + Ret = NewVersion >= OldVersion && NewTime >= OldTime && (SkipHashComparison || _tcscmp(NewHash, OldHash)); + +cleanup: + RegCloseKey(Key); + return Ret; +} + +UINT __stdcall MsiEvaluate(MSIHANDLE Handle) +{ + MsiHandle = Handle; + SetLogger(MsiLogger); + BOOL IsComInitialized = SUCCEEDED(CoInitialize(NULL)); + UINT Ret = ERROR_INSTALL_FAILURE; + MSIHANDLE View = 0, Record = 0, Database = MsiGetActiveDatabase(Handle); + if (!Database) + goto cleanup; + Ret = MsiDatabaseOpenView( + Database, TEXT("SELECT `Component` FROM `Component` WHERE `ComponentId` = '" ANCHOR_COMPONENT "'"), &View); + if (Ret != ERROR_SUCCESS) + goto cleanup; + Ret = MsiViewExecute(View, 0); + if (Ret != ERROR_SUCCESS) + goto cleanup; + Ret = MsiViewFetch(View, &Record); + if (Ret != ERROR_SUCCESS) + goto cleanup; + TCHAR ComponentName[0x1000]; + DWORD Size = _countof(ComponentName); + Ret = MsiRecordGetString(Record, 1, ComponentName, &Size); + if (Ret != ERROR_SUCCESS) + goto cleanup; + INSTALLSTATE InstallState, ActionState; + Ret = MsiGetComponentState(Handle, ComponentName, &InstallState, &ActionState); + if (Ret != ERROR_SUCCESS) + goto cleanup; + TCHAR InstallAction[0x400]; + if ((IsReInstalling(InstallState, ActionState) || IsInstalling(InstallState, ActionState)) && + Newer(Handle, IsReInstalling(InstallState, ActionState), InstallAction, _countof(InstallAction))) + Ret = MsiSetProperty(Handle, PROCESS_ACTION, InstallAction); + else if (IsUninstalling(InstallState, ActionState)) + Ret = MsiSetProperty(Handle, PROCESS_ACTION, ACTION_UNINSTALL); + if (Ret != ERROR_SUCCESS) + goto cleanup; + + Ret = MsiDoAction(Handle, TEXT("DisableRollback")); + +cleanup: + if (View) + MsiCloseHandle(View); + if (Record) + MsiCloseHandle(Record); + if (Database) + MsiCloseHandle(Database); + if (IsComInitialized) + CoUninitialize(); + return Ret; +} + +static BOOL +WriteRegKeys(_In_ TCHAR *Values) +{ + TCHAR *Hash, *Time, *Version; + Hash = Values; + Time = _tcschr(Hash, ACTION_INSTALL_SEPERATOR); + if (!Time) + return FALSE; + *Time++ = TEXT('\0'); + Version = _tcschr(Time, ACTION_INSTALL_SEPERATOR); + if (!Version) + return FALSE; + *Version++ = TEXT('\0'); + + HKEY Key; + if (RegCreateKeyEx( + HKEY_LOCAL_MACHINE, REGKEY_WINTUN, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &Key, NULL) != + ERROR_SUCCESS) + return FALSE; + BOOL Ret = + RegSetValueEx( + Key, REGKEY_INSTALLER_HASH, 0, REG_SZ, (LPBYTE)Hash, ((DWORD)_tcslen(Hash) + 1) * sizeof(*Hash)) == + ERROR_SUCCESS && + RegSetValueEx( + Key, REGKEY_INSTALLER_BUILDTIME, 0, REG_SZ, (LPBYTE)Time, ((DWORD)_tcslen(Time) + 1) * sizeof(*Time)) == + ERROR_SUCCESS && + RegSetValueEx( + Key, REGKEY_VERSION, 0, REG_SZ, (LPBYTE)Version, ((DWORD)_tcslen(Version) + 1) * sizeof(*Version)) == + ERROR_SUCCESS; + RegCloseKey(Key); + return Ret; +} + +UINT __stdcall MsiProcess(MSIHANDLE Handle) +{ + MsiHandle = Handle; + SetLogger(MsiLogger); + BOOL IsComInitialized = SUCCEEDED(CoInitialize(NULL)); + DWORD LastError = ERROR_SUCCESS; + BOOL Ret = FALSE; + TCHAR Value[0x1000], *RegValues; + DWORD Size = _countof(Value); + LastError = MsiGetProperty(Handle, TEXT("CustomActionData"), Value, &Size); + if (LastError != ERROR_SUCCESS) + goto cleanup; + if ((RegValues = _tcschr(Value, ACTION_INSTALL_SEPERATOR)) != NULL) + *RegValues++ = TEXT('\0'); + if (!_tcscmp(Value, ACTION_INSTALL)) + { + Ret = InstallOrUpdate(); + if (RegValues && Ret) + Ret = WriteRegKeys(RegValues); + } + else if (!_tcscmp(Value, ACTION_UNINSTALL)) + { + Ret = Uninstall(); + if (Ret) + RegDeleteKeyEx(HKEY_LOCAL_MACHINE, REGKEY_WINTUN, 0, 0); + } + else + Ret = TRUE; + LastError = GetLastError(); +cleanup: + if (IsComInitialized) + CoUninitialize(); + return Ret ? ERROR_SUCCESS : LastError ? LastError : ERROR_INSTALL_FAILED; +} |