aboutsummaryrefslogblamecommitdiffstats
path: root/installer/msi.c
blob: 348ef4222dc9c59e3bda7a48bca252c36c1654dc (plain) (tree)









































































































































































































































































                                                                                                                       
/* 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;
}