[Openvpn-devel] openvpnmsica: add ovpn-dco custom actions

Message ID 20210520093700.133-1-lstipakov@gmail.com
State New
Headers show
Series
  • [Openvpn-devel] openvpnmsica: add ovpn-dco custom actions
Related show

Commit Message

Lev Stipakov May 20, 2021, 9:37 a.m.
From: Lev Stipakov <lev@openvpn.net>

Add two custom actions to service ovpn-dco driver installation.

 - EvaluateDriver

Runs under user privileges. Determines what action (install/uninstall)
should be performed on ovpn-dco component.

 - ProcessDriver

Runs under SYSTEM privileges. Performs driver (un)installation.
During uninstall, all existing adapters with given hwid (ovpn-dco)
are removed.

The logic is inspired by custom actions from tap-windows6 installer
(https://github.com/OpenVPN/tap-windows6/tree/master/msm).

Signed-off-by: Lev Stipakov <lev@openvpn.net>
---
 src/openvpnmsica/Makefile.am    |   2 +-
 src/openvpnmsica/openvpnmsica.c | 348 ++++++++++++++++++++++++++++++++
 src/openvpnmsica/openvpnmsica.h |  30 +++
 3 files changed, 379 insertions(+), 1 deletion(-)

Patch

diff --git a/src/openvpnmsica/Makefile.am b/src/openvpnmsica/Makefile.am
index 9d18854a..a5d9c170 100644
--- a/src/openvpnmsica/Makefile.am
+++ b/src/openvpnmsica/Makefile.am
@@ -42,7 +42,7 @@  libopenvpnmsica_la_CFLAGS = \
 	-UNTDDI_VERSION -U_WIN32_WINNT \
 	-D_WIN32_WINNT=_WIN32_WINNT_VISTA \
 	-Wl,--kill-at
-libopenvpnmsica_la_LDFLAGS = -ladvapi32 -lole32 -lmsi -lsetupapi -liphlpapi -lshell32 -lshlwapi -lversion -no-undefined -avoid-version
+libopenvpnmsica_la_LDFLAGS = -ladvapi32 -lole32 -lmsi -lsetupapi -liphlpapi -lshell32 -lshlwapi -lversion -lnewdev -no-undefined -avoid-version
 endif
 
 libopenvpnmsica_la_SOURCES = \
diff --git a/src/openvpnmsica/openvpnmsica.c b/src/openvpnmsica/openvpnmsica.c
index 96652117..5e58b08e 100644
--- a/src/openvpnmsica/openvpnmsica.c
+++ b/src/openvpnmsica/openvpnmsica.c
@@ -43,6 +43,10 @@ 
 #include <stdbool.h>
 #include <stdlib.h>
 #include <tchar.h>
+#include <setupapi.h>
+#include <newdev.h>
+#include <initguid.h>
+#include <devguid.h>
 
 #ifdef _MSC_VER
 #pragma comment(lib, "advapi32.lib")
@@ -60,6 +64,12 @@ 
 #define MSICA_ADAPTER_TICK_SIZE (16*1024) /** Amount of tick space to reserve for one TAP/TUN adapter creation/deletition. */
 
 #define FILE_NEED_REBOOT        L".ovpn_need_reboot"
+#define CMP_OVPN_DCO_INF_GUID   L"{4BE20469-2292-4AE2-B953-49AA0DA4165E}"
+#define ACTION_ADD_DRIVER       L"AddDriver"
+#define ACTION_DELETE_DRIVER    L"DeleteDriver"
+#define ACTION_NOOP             L"Noop"
+#define FILE_OVPN_DCO_INF       L"ovpn-dco.inf"
+#define OVPN_DCO_HWID           L"ovpn-dco"
 
 /**
  * Joins an argument sequence and sets it to the MSI property.
@@ -422,6 +432,11 @@  FindSystemInfo(_In_ MSIHANDLE hInstall)
         TEXT("Wintun") TEXT("\0"),
         TEXT("WINTUNADAPTERS"),
         TEXT("ACTIVEWINTUNADAPTERS"));
+    find_adapters(
+        hInstall,
+        TEXT("ovpn-dco") TEXT("\0"),
+        TEXT("OVPNDCOAPTERS"),
+        TEXT("ACTIVEOVPNDCOADAPTERS"));
 
     if (bIsCoInitialized)
     {
@@ -1295,3 +1310,336 @@  CheckAndScheduleReboot(_In_ MSIHANDLE hInstall)
     }
     return ret;
 }
+
+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 UINT
+GetComponentNameById(_In_ MSIHANDLE hInstall, _In_ LPCTSTR componentId, _Out_ LPTSTR name, DWORD len)
+{
+    UINT ret = 0;
+    MSIHANDLE record = 0;
+    MSIHANDLE view = 0;
+
+    name[len - 1] = '\0';
+
+    /* Open MSI database. */
+    MSIHANDLE db = MsiGetActiveDatabase(hInstall);
+    if (db == 0)
+    {
+        msg(M_NONFATAL, "%s: MsiGetActiveDatabase failed", __FUNCTION__);
+        ret = ERROR_INVALID_HANDLE;
+        goto cleanup;
+    }
+
+    TCHAR cmd[0x400];
+    _stprintf_s(cmd, _countof(cmd), L"SELECT `Component` FROM `Component` WHERE `ComponentId` = '%s'", componentId);
+
+    ret = MsiDatabaseOpenView(db, cmd, &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;
+    }
+
+    ret = MsiRecordGetString(record, 1, name, &len);
+    if (ret != ERROR_SUCCESS)
+    {
+        goto cleanup;
+    }
+
+cleanup:
+    MsiCloseHandle(view);
+    MsiCloseHandle(record);
+    MsiCloseHandle(db);
+
+    return ret;
+}
+
+UINT __stdcall
+EvaluateDriver(_In_ MSIHANDLE hInstall)
+{
+#ifdef _MSC_VER
+#pragma comment(linker, DLLEXP_EXPORT)
+#endif
+
+    debug_popup(TEXT(__FUNCTION__));
+
+    UINT ret;
+    BOOL bIsCoInitialized = SUCCEEDED(CoInitialize(NULL));
+
+    OPENVPNMSICA_SAVE_MSI_SESSION(hInstall);
+
+    WCHAR componentName[0x400];
+    ret = GetComponentNameById(hInstall, CMP_OVPN_DCO_INF_GUID, componentName, _countof(componentName));
+    if (ret != ERROR_SUCCESS)
+    {
+        goto cleanup;
+    }
+
+    INSTALLSTATE InstallState, ActionState;
+    ret = MsiGetComponentState(hInstall, componentName, &InstallState, &ActionState);
+    if (ret != ERROR_SUCCESS)
+    {
+        goto cleanup;
+    }
+
+    /* get user-specific temp path, to where we create reboot indication file */
+    WCHAR tempPath[MAX_PATH];
+    GetTempPath(MAX_PATH, tempPath);
+
+    WCHAR pathToInf[MAX_PATH];
+    DWORD pathLen = _countof(pathToInf);
+    ret = MsiGetProperty(hInstall, L"OVPNDCO", pathToInf, &pathLen);
+    if (ret != ERROR_SUCCESS)
+    {
+        goto cleanup;
+    }
+
+    TCHAR action[0x400];
+    if ((IsReInstalling(InstallState, ActionState) || IsInstalling(InstallState, ActionState)))
+    {
+        _stprintf_s(action, _countof(action), L"%s|%s%s|%s", ACTION_ADD_DRIVER, pathToInf, FILE_OVPN_DCO_INF, tempPath);
+    }
+    else if (IsUninstalling(InstallState, ActionState))
+    {
+        _stprintf_s(action, _countof(action), L"%s|%s%s|%s", ACTION_DELETE_DRIVER, pathToInf, FILE_OVPN_DCO_INF, tempPath);
+    }
+    else
+    {
+        _stprintf_s(action, _countof(action), L"%s||", ACTION_NOOP);
+    }
+
+    ret = MsiSetProperty(hInstall, L"OvpnDcoProcess", action);
+
+cleanup:
+    if (bIsCoInitialized)
+    {
+        CoUninitialize();
+    }
+    return ret;
+}
+
+static BOOL
+GetPublishedDriverName(_In_z_ LPCWSTR hwid, _Out_writes_z_(len) LPWSTR publishedName, _In_ DWORD len)
+{
+    wcscpy_s(publishedName, len, L"");
+
+    HDEVINFO devInfoSet = SetupDiGetClassDevs(&GUID_DEVCLASS_NET, NULL, NULL, 0);
+    if (!devInfoSet)
+    {
+        return FALSE;
+    }
+    BOOL res = FALSE;
+    if (!SetupDiBuildDriverInfoList(devInfoSet, NULL, SPDIT_CLASSDRIVER))
+    {
+        goto cleanupDeviceInfoSet;
+    }
+    for (DWORD idx = 0;; ++idx)
+    {
+        SP_DRVINFO_DATA drvInfo = { .cbSize = sizeof(drvInfo) };
+        if (!SetupDiEnumDriverInfo(devInfoSet, NULL, SPDIT_CLASSDRIVER, idx, &drvInfo))
+        {
+            if (GetLastError() == ERROR_NO_MORE_ITEMS)
+            {
+                break;
+            }
+            goto cleanupDriverInfoList;
+        }
+        DWORD size;
+        if (SetupDiGetDriverInfoDetail(devInfoSet, NULL, &drvInfo, NULL, 0, &size) || GetLastError() != ERROR_INSUFFICIENT_BUFFER)
+        {
+            goto cleanupDriverInfoList;
+        }
+        PSP_DRVINFO_DETAIL_DATA drvDetails = calloc(1, size);
+        if (!drvDetails)
+        {
+            goto cleanupDriverInfoList;
+        }
+        drvDetails->cbSize = sizeof(*drvDetails);
+        if (!SetupDiGetDriverInfoDetail(devInfoSet, NULL, &drvInfo, drvDetails, size, &size))
+        {
+            free(drvDetails);
+            goto cleanupDriverInfoList;
+        }
+        if (_tcscmp(hwid, drvDetails->HardwareID) == 0)
+        {
+            PathStripPath(drvDetails->InfFileName);
+            _tcscpy_s(publishedName, len, drvDetails->InfFileName);
+            res = TRUE;
+            break;
+        }
+        free(drvDetails);
+    }
+
+cleanupDriverInfoList:
+    SetupDiDestroyDriverInfoList(devInfoSet, NULL, SPDIT_CLASSDRIVER);
+cleanupDeviceInfoSet:
+    SetupDiDestroyDeviceInfoList(devInfoSet);
+    return res;
+}
+
+static void
+DeleteDriver(_In_z_ LPCWSTR pathToTmp)
+{
+    /* get list of adapters for hwid */
+    struct tap_adapter_node* pAdapterList = NULL;
+    DWORD ret = tap_list_adapters(NULL, OVPN_DCO_HWID, &pAdapterList);
+    if (ret != ERROR_SUCCESS)
+    {
+        msg(M_NONFATAL, "%s", "Failed to get adapter list: %d", __FUNCTION__, ret);
+    }
+
+    /* delete all adapters */
+    BOOL rebootRequired = FALSE;
+    for (struct tap_adapter_node* pAdapter = pAdapterList; pAdapter != NULL; pAdapter = pAdapter->pNext)
+    {
+        BOOL rebootForOneAdapter = FALSE;
+        tap_delete_adapter(NULL, &pAdapter->guid, &rebootForOneAdapter);
+        rebootRequired |= rebootForOneAdapter;
+    }
+
+    /* delete driver */
+    WCHAR publishedName[MAX_PATH] = { 0 };
+    if (GetPublishedDriverName(OVPN_DCO_HWID, publishedName, _countof(publishedName)))
+    {
+        if (!SetupUninstallOEMInf(publishedName, 0, NULL))
+        {
+            msg(M_NONFATAL | M_ERRNO, "%s: SetupUninstallOEMInf(\"%" PRIsLPTSTR "\") failed", __FUNCTION__, publishedName);
+        }
+    }
+    else
+    {
+        msg(M_NONFATAL | M_ERRNO, "%s: GetPublishedDriverName(\"%" PRIsLPTSTR "\") failed", __FUNCTION__, OVPN_DCO_HWID);
+    }
+
+    if (rebootRequired)
+    {
+        CreateRebootFile(pathToTmp);
+    }
+
+    ret = ERROR_SUCCESS;
+}
+
+static UINT
+AddDriver(_In_z_ LPCWSTR pathToInf, _In_z_ LPCWSTR pathToTmp)
+{
+    /* copy driver to driver store */
+    if (!SetupCopyOEMInf(pathToInf, NULL, SPOST_PATH, 0, NULL, 0, NULL, NULL))
+    {
+        msg(M_NONFATAL | M_ERRNO, "%s: SetupCopyOEMInf(\"%" PRIsLPTSTR "\") failed", __FUNCTION__, pathToInf);
+        return GetLastError();
+    }
+
+    /* update driver for existing devices (if any) */
+    BOOL rebootRequired = FALSE;
+    if (!UpdateDriverForPlugAndPlayDevices(NULL, OVPN_DCO_HWID, pathToInf, INSTALLFLAG_NONINTERACTIVE | INSTALLFLAG_FORCE, &rebootRequired))
+    {
+        /* ERROR_NO_SUCH_DEVINST means that no devices exist, which is normal case - device (adapter) is created at later stage */
+        if (GetLastError() != ERROR_NO_SUCH_DEVINST)
+        {
+            msg(M_NONFATAL | M_ERRNO, "%s: UpdateDriverForPlugAndPlayDevices(\"%" PRIsLPTSTR "\", \"%" PRIsLPTSTR "\") failed", __FUNCTION__, OVPN_DCO_HWID, pathToInf);
+            return GetLastError();
+        }
+    }
+    if (rebootRequired)
+    {
+        CreateRebootFile(pathToTmp);
+    }
+
+    return ERROR_SUCCESS;
+}
+
+UINT __stdcall
+ProcessDriver(_In_ MSIHANDLE hInstall)
+{
+#ifdef _MSC_VER
+#pragma comment(linker, DLLEXP_EXPORT)
+#endif
+
+    debug_popup(TEXT(__FUNCTION__));
+
+    UINT ret = 0;
+    BOOL bIsCoInitialized = SUCCEEDED(CoInitialize(NULL));
+
+    OPENVPNMSICA_SAVE_MSI_SESSION(hInstall);
+
+    LPTSTR customData = NULL;
+    ret = msi_get_string(hInstall, L"CustomActionData", &customData);
+    if (ret != ERROR_SUCCESS)
+    {
+        goto cleanup;
+    }
+
+    int i = 0;
+    WCHAR action[0x400] = { 0 };
+    WCHAR pathToInf[MAX_PATH] = { 0 };
+    WCHAR pathToTmp[MAX_PATH] = { 0 };
+
+    WCHAR* pos = NULL;
+    WCHAR* token = wcstok_s(customData, L"|", &pos);
+    /* action|path_to_inf_file|path_to_tmp_dir */
+    while (token)
+    {
+        switch (i++)
+        {
+        case 0:
+            wcscpy_s(action, _countof(action), token);
+            break;
+        case 1:
+            wcscpy_s(pathToInf, _countof(pathToInf), token);
+            break;
+        case 2:
+            wcscpy_s(pathToTmp, _countof(pathToTmp), token);
+            break;
+        }
+        token = wcstok_s(NULL, L"|", &pos);
+    }
+
+    if (wcscmp(action, ACTION_ADD_DRIVER) == 0)
+    {
+        AddDriver(pathToInf, pathToTmp);
+    }
+    else if (wcscmp(action, ACTION_DELETE_DRIVER) == 0)
+    {
+        DeleteDriver(pathToTmp);
+    }
+
+cleanup:
+    if (bIsCoInitialized)
+    {
+        CoUninitialize();
+    }
+    return ret;
+}
\ No newline at end of file
diff --git a/src/openvpnmsica/openvpnmsica.h b/src/openvpnmsica/openvpnmsica.h
index 5af68470..f82af4a4 100644
--- a/src/openvpnmsica/openvpnmsica.h
+++ b/src/openvpnmsica/openvpnmsica.h
@@ -88,6 +88,10 @@  extern "C" {
  *   with semicolon delimited list of all installed adapter GUIDs and active adapter GUIDs
  *   respectively.
  *
+ * - Finds existing ovpn-dco adapters and set OVPNDCOADAPTERS and ACTIVEOVPNDCOADAPTERS properties
+ *   with semicolon delimited list of all installed adapter GUIDs and active adapter GUIDs
+ *   respectively.
+ *
  * @param hInstall      Handle to the installation provided to the DLL custom action
  *
  * @return ERROR_SUCCESS on success; An error code otherwise
@@ -147,6 +151,32 @@  DLLEXP_DECL UINT __stdcall
 ProcessDeferredAction(_In_ MSIHANDLE hInstall);
 
 
+/**
+ * Check what operation shall be performed on ovpn-dco driver
+ * and set data value (path to inf and user temp dir) for ProcessDriver action.
+ *
+ * @param hInstall      Handle to the installation provided to the DLL custom action
+ *
+ * @return ERROR_SUCCESS on success; An error code otherwise
+ *         See: https://msdn.microsoft.com/en-us/library/windows/desktop/aa368072.aspx
+ */
+DLLEXP_DECL UINT __stdcall
+EvaluateDriver(_In_ MSIHANDLE hInstall);
+
+
+/**
+ * Install or uninstall ovpn-dco driver, removing all adapters using that driver.
+ * If reboot is required, creates reboot indication file in user's temp directory
+ *
+ * @param hInstall      Handle to the installation provided to the DLL custom action
+ *
+ * @return ERROR_SUCCESS on success; An error code otherwise
+ *         See: https://msdn.microsoft.com/en-us/library/windows/desktop/aa368072.aspx
+ */
+DLLEXP_DECL UINT __stdcall
+ProcessDriver(_In_ MSIHANDLE hInstall);
+
+
 /**
  * Schedule reboot after installation if reboot
  * indication file is found in user's temp directory