aboutsummaryrefslogtreecommitdiffstats
path: root/installer/msi.c
diff options
context:
space:
mode:
Diffstat (limited to 'installer/msi.c')
-rw-r--r--installer/msi.c266
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;
+}