blob: de1cf65c5014884c1243a45bd6248ac30aa04fec [file] [log] [blame]
/*
* openvpnmsica -- Custom Action DLL to provide OpenVPN-specific support to MSI packages
* https://community.openvpn.net/openvpn/wiki/OpenVPNMSICA
*
* Copyright (C) 2018-2020 Simon Rozman <simon@rozman.si>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#elif defined(_MSC_VER)
#include <config-msvc.h>
#endif
#include <winsock2.h> /* Must be included _before_ <windows.h> */
#include "openvpnmsica.h"
#include "msica_arg.h"
#include "msiex.h"
#include "../tapctl/basic.h"
#include "../tapctl/error.h"
#include "../tapctl/tap.h"
#include <windows.h>
#include <iphlpapi.h>
#include <malloc.h>
#include <memory.h>
#include <msiquery.h>
#include <shellapi.h>
#include <shlwapi.h>
#include <stdbool.h>
#include <stdlib.h>
#include <tchar.h>
#ifdef _MSC_VER
#pragma comment(lib, "advapi32.lib")
#pragma comment(lib, "iphlpapi.lib")
#pragma comment(lib, "shell32.lib")
#pragma comment(lib, "shlwapi.lib")
#pragma comment(lib, "version.lib")
#endif
/**
* Local constants
*/
#define MSICA_ADAPTER_TICK_SIZE (16*1024) /** Amount of tick space to reserve for one TAP/TUN adapter creation/deletition. */
/**
* Joins an argument sequence and sets it to the MSI property.
*
* @param hInstall Handle to the installation provided to the DLL custom action
*
* @param szProperty MSI property name to set to the joined argument sequence.
*
* @param seq The argument sequence.
*
* @return ERROR_SUCCESS on success; An error code otherwise
*/
static UINT
setup_sequence(
_In_ MSIHANDLE hInstall,
_In_z_ LPCTSTR szProperty,
_In_ struct msica_arg_seq *seq)
{
UINT uiResult;
LPTSTR szSequence = msica_arg_seq_join(seq);
uiResult = MsiSetProperty(hInstall, szProperty, szSequence);
free(szSequence);
if (uiResult != ERROR_SUCCESS)
{
SetLastError(uiResult); /* MSDN does not mention MsiSetProperty() to set GetLastError(). But we do have an error code. Set last error manually. */
msg(M_NONFATAL | M_ERRNO, "%s: MsiSetProperty(\"%" PRIsLPTSTR "\") failed", __FUNCTION__, szProperty);
return uiResult;
}
return ERROR_SUCCESS;
}
#ifdef _DEBUG
/**
* Pops up a message box creating a time window to attach a debugger to the installer process in
* order to debug custom actions.
*
* @param szFunctionName Function name that triggered the pop-up. Displayed in message box's
* title.
*/
static void
_debug_popup(_In_z_ LPCTSTR szFunctionName)
{
TCHAR szTitle[0x100], szMessage[0x100+MAX_PATH], szProcessPath[MAX_PATH];
/* Compose pop-up title. The dialog title will contain function name to ease the process
* locating. Mind that Visual Studio displays window titles on the process list. */
_stprintf_s(szTitle, _countof(szTitle), TEXT("%s v%s"), szFunctionName, TEXT(PACKAGE_VERSION));
/* Get process name. */
GetModuleFileName(NULL, szProcessPath, _countof(szProcessPath));
LPCTSTR szProcessName = _tcsrchr(szProcessPath, TEXT('\\'));
szProcessName = szProcessName ? szProcessName + 1 : szProcessPath;
/* Compose the pop-up message. */
_stprintf_s(
szMessage, _countof(szMessage),
TEXT("The %s process (PID: %u) has started to execute the %s custom action.\r\n")
TEXT("\r\n")
TEXT("If you would like to debug the custom action, attach a debugger to this process and set breakpoints before dismissing this dialog.\r\n")
TEXT("\r\n")
TEXT("If you are not debugging this custom action, you can safely ignore this message."),
szProcessName,
GetCurrentProcessId(),
szFunctionName);
MessageBox(NULL, szMessage, szTitle, MB_OK);
}
#define debug_popup(f) _debug_popup(f)
#else /* ifdef _DEBUG */
#define debug_popup(f)
#endif /* ifdef _DEBUG */
/**
* Detects if the OpenVPNService service is in use (running or paused) and sets
* OPENVPNSERVICE to the service process PID, or its path if it is set to
* auto-start, but not running.
*
* @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
*/
static UINT
set_openvpnserv_state(_In_ MSIHANDLE hInstall)
{
UINT uiResult;
/* Get Service Control Manager handle. */
SC_HANDLE hSCManager = OpenSCManager(NULL, SERVICES_ACTIVE_DATABASE, SC_MANAGER_CONNECT);
if (hSCManager == NULL)
{
uiResult = GetLastError();
msg(M_NONFATAL | M_ERRNO, "%s: OpenSCManager() failed", __FUNCTION__);
return uiResult;
}
/* Get OpenVPNService service handle. */
SC_HANDLE hService = OpenService(hSCManager, TEXT("OpenVPNService"), SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG);
if (hService == NULL)
{
uiResult = GetLastError();
if (uiResult == ERROR_SERVICE_DOES_NOT_EXIST)
{
/* This is not actually an error. */
goto cleanup_OpenSCManager;
}
msg(M_NONFATAL | M_ERRNO, "%s: OpenService(\"OpenVPNService\") failed", __FUNCTION__);
goto cleanup_OpenSCManager;
}
/* Query service status. */
SERVICE_STATUS_PROCESS ssp;
DWORD dwBufSize;
if (QueryServiceStatusEx(hService, SC_STATUS_PROCESS_INFO, (LPBYTE)&ssp, sizeof(ssp), &dwBufSize))
{
switch (ssp.dwCurrentState)
{
case SERVICE_START_PENDING:
case SERVICE_RUNNING:
case SERVICE_STOP_PENDING:
case SERVICE_PAUSE_PENDING:
case SERVICE_PAUSED:
case SERVICE_CONTINUE_PENDING:
{
/* Service is started (kind of). Set OPENVPNSERVICE property to service PID. */
TCHAR szPID[10 /*MAXDWORD in decimal*/ + 1 /*terminator*/];
_stprintf_s(
szPID, _countof(szPID),
TEXT("%u"),
ssp.dwProcessId);
uiResult = MsiSetProperty(hInstall, TEXT("OPENVPNSERVICE"), szPID);
if (uiResult != ERROR_SUCCESS)
{
SetLastError(uiResult); /* MSDN does not mention MsiSetProperty() to set GetLastError(). But we do have an error code. Set last error manually. */
msg(M_NONFATAL | M_ERRNO, "%s: MsiSetProperty(\"OPENVPNSERVICE\") failed", __FUNCTION__);
}
/* We know user is using the service. Skip auto-start setting check. */
goto cleanup_OpenService;
}
break;
}
}
else
{
uiResult = GetLastError();
msg(M_NONFATAL | M_ERRNO, "%s: QueryServiceStatusEx(\"OpenVPNService\") failed", __FUNCTION__);
}
/* Service is not started. Is it set to auto-start? */
/* MSDN describes the maximum buffer size for QueryServiceConfig() to be 8kB. */
/* This is small enough to fit on stack. */
BYTE _buffer_8k[8192];
LPQUERY_SERVICE_CONFIG pQsc = (LPQUERY_SERVICE_CONFIG)_buffer_8k;
dwBufSize = sizeof(_buffer_8k);
if (!QueryServiceConfig(hService, pQsc, dwBufSize, &dwBufSize))
{
uiResult = GetLastError();
msg(M_NONFATAL | M_ERRNO, "%s: QueryServiceStatusEx(\"QueryServiceConfig\") failed", __FUNCTION__);
goto cleanup_OpenService;
}
if (pQsc->dwStartType <= SERVICE_AUTO_START)
{
/* Service is set to auto-start. Set OPENVPNSERVICE property to its path. */
uiResult = MsiSetProperty(hInstall, TEXT("OPENVPNSERVICE"), pQsc->lpBinaryPathName);
if (uiResult != ERROR_SUCCESS)
{
SetLastError(uiResult); /* MSDN does not mention MsiSetProperty() to set GetLastError(). But we do have an error code. Set last error manually. */
msg(M_NONFATAL | M_ERRNO, "%s: MsiSetProperty(\"OPENVPNSERVICE\") failed", __FUNCTION__);
goto cleanup_OpenService;
}
}
uiResult = ERROR_SUCCESS;
cleanup_OpenService:
CloseServiceHandle(hService);
cleanup_OpenSCManager:
CloseServiceHandle(hSCManager);
return uiResult;
}
static void
find_adapters(
_In_ MSIHANDLE hInstall,
_In_z_ LPCTSTR szzHardwareIDs,
_In_z_ LPCTSTR szAdaptersPropertyName,
_In_z_ LPCTSTR szActiveAdaptersPropertyName)
{
UINT uiResult;
/* Get network adapters with given hardware ID. */
struct tap_adapter_node *pAdapterList = NULL;
uiResult = tap_list_adapters(NULL, szzHardwareIDs, &pAdapterList);
if (uiResult != ERROR_SUCCESS)
{
return;
}
else if (pAdapterList == NULL)
{
/* No adapters - no fun. */
return;
}
/* Get IPv4/v6 info for all network adapters. Actually, we're interested in link status only: up/down? */
PIP_ADAPTER_ADDRESSES pAdapterAdresses = NULL;
ULONG ulAdapterAdressesSize = 16*1024;
for (size_t iteration = 0; iteration < 2; iteration++)
{
pAdapterAdresses = (PIP_ADAPTER_ADDRESSES)malloc(ulAdapterAdressesSize);
if (pAdapterAdresses == NULL)
{
msg(M_NONFATAL, "%s: malloc(%u) failed", __FUNCTION__, ulAdapterAdressesSize);
uiResult = ERROR_OUTOFMEMORY; goto cleanup_pAdapterList;
}
ULONG ulResult = GetAdaptersAddresses(
AF_UNSPEC,
GAA_FLAG_SKIP_UNICAST | GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_SKIP_FRIENDLY_NAME | GAA_FLAG_INCLUDE_ALL_INTERFACES,
NULL,
pAdapterAdresses,
&ulAdapterAdressesSize);
if (ulResult == ERROR_SUCCESS)
{
break;
}
free(pAdapterAdresses);
if (ulResult != ERROR_BUFFER_OVERFLOW)
{
SetLastError(ulResult); /* MSDN does not mention GetAdaptersAddresses() to set GetLastError(). But we do have an error code. Set last error manually. */
msg(M_NONFATAL | M_ERRNO, "%s: GetAdaptersAddresses() failed", __FUNCTION__);
uiResult = ulResult; goto cleanup_pAdapterList;
}
}
/* Count adapters. */
size_t adapter_count = 0;
for (struct tap_adapter_node *pAdapter = pAdapterList; pAdapter; pAdapter = pAdapter->pNext)
{
adapter_count++;
}
/* Prepare semicolon delimited list of TAP adapter ID(s) and active TAP adapter ID(s). */
LPTSTR
szAdapters = (LPTSTR)malloc(adapter_count * (38 /*GUID*/ + 1 /*separator/terminator*/) * sizeof(TCHAR)),
szAdaptersTail = szAdapters;
if (szAdapters == NULL)
{
msg(M_FATAL, "%s: malloc(%u) failed", __FUNCTION__, adapter_count * (38 /*GUID*/ + 1 /*separator/terminator*/) * sizeof(TCHAR));
uiResult = ERROR_OUTOFMEMORY; goto cleanup_pAdapterAdresses;
}
LPTSTR
szAdaptersActive = (LPTSTR)malloc(adapter_count * (38 /*GUID*/ + 1 /*separator/terminator*/) * sizeof(TCHAR)),
szAdaptersActiveTail = szAdaptersActive;
if (szAdaptersActive == NULL)
{
msg(M_FATAL, "%s: malloc(%u) failed", __FUNCTION__, adapter_count * (38 /*GUID*/ + 1 /*separator/terminator*/) * sizeof(TCHAR));
uiResult = ERROR_OUTOFMEMORY; goto cleanup_szAdapters;
}
for (struct tap_adapter_node *pAdapter = pAdapterList; pAdapter; pAdapter = pAdapter->pNext)
{
/* Convert adapter GUID to UTF-16 string. (LPOLESTR defaults to LPWSTR) */
LPOLESTR szAdapterId = NULL;
StringFromIID((REFIID)&pAdapter->guid, &szAdapterId);
/* Append to the list of TAP adapter ID(s). */
if (szAdapters < szAdaptersTail)
{
*(szAdaptersTail++) = TEXT(';');
}
memcpy(szAdaptersTail, szAdapterId, 38 * sizeof(TCHAR));
szAdaptersTail += 38;
/* If this adapter is active (connected), add it to the list of active TAP adapter ID(s). */
for (PIP_ADAPTER_ADDRESSES p = pAdapterAdresses; p; p = p->Next)
{
OLECHAR szId[38 /*GUID*/ + 1 /*terminator*/];
GUID guid;
if (MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, p->AdapterName, -1, szId, _countof(szId)) > 0
&& SUCCEEDED(IIDFromString(szId, &guid))
&& memcmp(&guid, &pAdapter->guid, sizeof(GUID)) == 0)
{
if (p->OperStatus == IfOperStatusUp)
{
/* This TAP adapter is active (connected). */
if (szAdaptersActive < szAdaptersActiveTail)
{
*(szAdaptersActiveTail++) = TEXT(';');
}
memcpy(szAdaptersActiveTail, szAdapterId, 38 * sizeof(TCHAR));
szAdaptersActiveTail += 38;
}
break;
}
}
CoTaskMemFree(szAdapterId);
}
szAdaptersTail [0] = 0;
szAdaptersActiveTail[0] = 0;
/* Set Installer properties. */
uiResult = MsiSetProperty(hInstall, szAdaptersPropertyName, szAdapters);
if (uiResult != ERROR_SUCCESS)
{
SetLastError(uiResult); /* MSDN does not mention MsiSetProperty() to set GetLastError(). But we do have an error code. Set last error manually. */
msg(M_NONFATAL | M_ERRNO, "%s: MsiSetProperty(\"%s\") failed", __FUNCTION__, szAdaptersPropertyName);
goto cleanup_szAdaptersActive;
}
uiResult = MsiSetProperty(hInstall, szActiveAdaptersPropertyName, szAdaptersActive);
if (uiResult != ERROR_SUCCESS)
{
SetLastError(uiResult); /* MSDN does not mention MsiSetProperty() to set GetLastError(). But we do have an error code. Set last error manually. */
msg(M_NONFATAL | M_ERRNO, "%s: MsiSetProperty(\"%s\") failed", __FUNCTION__, szActiveAdaptersPropertyName);
goto cleanup_szAdaptersActive;
}
cleanup_szAdaptersActive:
free(szAdaptersActive);
cleanup_szAdapters:
free(szAdapters);
cleanup_pAdapterAdresses:
free(pAdapterAdresses);
cleanup_pAdapterList:
tap_free_adapter_list(pAdapterList);
}
UINT __stdcall
FindSystemInfo(_In_ MSIHANDLE hInstall)
{
#ifdef _MSC_VER
#pragma comment(linker, DLLEXP_EXPORT)
#endif
debug_popup(TEXT(__FUNCTION__));
BOOL bIsCoInitialized = SUCCEEDED(CoInitialize(NULL));
OPENVPNMSICA_SAVE_MSI_SESSION(hInstall);
set_openvpnserv_state(hInstall);
find_adapters(
hInstall,
TEXT("root\\") TEXT(TAP_WIN_COMPONENT_ID) TEXT("\0") TEXT(TAP_WIN_COMPONENT_ID) TEXT("\0"),
TEXT("TAPWINDOWS6ADAPTERS"),
TEXT("ACTIVETAPWINDOWS6ADAPTERS"));
find_adapters(
hInstall,
TEXT("Wintun") TEXT("\0"),
TEXT("WINTUNADAPTERS"),
TEXT("ACTIVEWINTUNADAPTERS"));
if (bIsCoInitialized)
{
CoUninitialize();
}
return ERROR_SUCCESS;
}
UINT __stdcall
CloseOpenVPNGUI(_In_ MSIHANDLE hInstall)
{
#ifdef _MSC_VER
#pragma comment(linker, DLLEXP_EXPORT)
#endif
UNREFERENCED_PARAMETER(hInstall); /* This CA is does not interact with MSI session (report errors, access properties, tables, etc.). */
debug_popup(TEXT(__FUNCTION__));
/* Find OpenVPN GUI window. */
HWND hWnd = FindWindow(TEXT("OpenVPN-GUI"), NULL);
if (hWnd)
{
/* Ask it to close and wait for 100ms. Unfortunately, this will succeed only for recent OpenVPN GUI that do not run elevated. */
SendMessage(hWnd, WM_CLOSE, 0, 0);
Sleep(100);
}
return ERROR_SUCCESS;
}
UINT __stdcall
StartOpenVPNGUI(_In_ MSIHANDLE hInstall)
{
#ifdef _MSC_VER
#pragma comment(linker, DLLEXP_EXPORT)
#endif
debug_popup(TEXT(__FUNCTION__));
UINT uiResult;
BOOL bIsCoInitialized = SUCCEEDED(CoInitialize(NULL));
OPENVPNMSICA_SAVE_MSI_SESSION(hInstall);
/* Create and populate a MSI record. */
MSIHANDLE hRecord = MsiCreateRecord(1);
if (!hRecord)
{
uiResult = ERROR_INVALID_HANDLE;
msg(M_NONFATAL, "%s: MsiCreateRecord failed", __FUNCTION__);
goto cleanup_CoInitialize;
}
uiResult = MsiRecordSetString(hRecord, 0, TEXT("\"[#bin.openvpn_gui.exe]\""));
if (uiResult != ERROR_SUCCESS)
{
SetLastError(uiResult); /* MSDN does not mention MsiRecordSetString() to set GetLastError(). But we do have an error code. Set last error manually. */
msg(M_NONFATAL | M_ERRNO, "%s: MsiRecordSetString failed", __FUNCTION__);
goto cleanup_MsiCreateRecord;
}
/* Format string. */
TCHAR szStackBuf[MAX_PATH];
DWORD dwPathSize = _countof(szStackBuf);
LPTSTR szPath = szStackBuf;
uiResult = MsiFormatRecord(hInstall, hRecord, szPath, &dwPathSize);
if (uiResult == ERROR_MORE_DATA)
{
/* Allocate buffer on heap (+1 for terminator), and retry. */
szPath = (LPTSTR)malloc((++dwPathSize) * sizeof(TCHAR));
if (szPath == NULL)
{
msg(M_FATAL, "%s: malloc(%u) failed", __FUNCTION__, dwPathSize * sizeof(TCHAR));
uiResult = ERROR_OUTOFMEMORY; goto cleanup_MsiCreateRecord;
}
uiResult = MsiFormatRecord(hInstall, hRecord, szPath, &dwPathSize);
}
if (uiResult != ERROR_SUCCESS)
{
SetLastError(uiResult); /* MSDN does not mention MsiFormatRecord() to set GetLastError(). But we do have an error code. Set last error manually. */
msg(M_NONFATAL | M_ERRNO, "%s: MsiFormatRecord failed", __FUNCTION__);
goto cleanup_malloc_szPath;
}
/* Launch the OpenVPN GUI. */
SHELLEXECUTEINFO sei = {
.cbSize = sizeof(SHELLEXECUTEINFO),
.fMask = SEE_MASK_FLAG_NO_UI, /* Don't show error UI, we'll display it. */
.lpFile = szPath,
.nShow = SW_SHOWNORMAL
};
if (!ShellExecuteEx(&sei))
{
uiResult = GetLastError();
msg(M_NONFATAL | M_ERRNO, "%s: ShellExecuteEx(%s) failed", __FUNCTION__, szPath);
goto cleanup_malloc_szPath;
}
uiResult = ERROR_SUCCESS;
cleanup_malloc_szPath:
if (szPath != szStackBuf)
{
free(szPath);
}
cleanup_MsiCreateRecord:
MsiCloseHandle(hRecord);
cleanup_CoInitialize:
if (bIsCoInitialized)
{
CoUninitialize();
}
return uiResult;
}
/**
* Schedules adapter creation.
*
* When the rollback is enabled, the adapter deletition is scheduled on rollback.
*
* @param seq The argument sequence to pass to InstallTUNTAPAdapters custom action
*
* @param seqRollback The argument sequence to pass to InstallTUNTAPAdaptersRollback custom
* action. NULL when rollback is disabled.
*
* @param szDisplayName Adapter display name
*
* @param szHardwareId Adapter hardware ID
*
* @param iTicks Pointer to an integer that represents amount of work (on progress
* indicator) the InstallTUNTAPAdapters will take. This function increments it
* by MSICA_ADAPTER_TICK_SIZE for each adapter to create.
*
* @return ERROR_SUCCESS on success; An error code otherwise
*/
static DWORD
schedule_adapter_create(
_Inout_ struct msica_arg_seq *seq,
_Inout_opt_ struct msica_arg_seq *seqRollback,
_In_z_ LPCTSTR szDisplayName,
_In_z_ LPCTSTR szHardwareId,
_Inout_ int *iTicks)
{
/* Get existing network adapters. */
struct tap_adapter_node *pAdapterList = NULL;
DWORD dwResult = tap_list_adapters(NULL, NULL, &pAdapterList);
if (dwResult != ERROR_SUCCESS)
{
return dwResult;
}
/* Does adapter exist? */
for (struct tap_adapter_node *pAdapterOther = pAdapterList;; pAdapterOther = pAdapterOther->pNext)
{
if (pAdapterOther == NULL)
{
/* No adapter with a same name found. */
TCHAR szArgument[10 /*create=""|deleteN=""*/ + MAX_PATH /*szDisplayName*/ + 1 /*|*/ + MAX_PATH /*szHardwareId*/ + 1 /*terminator*/];
/* InstallTUNTAPAdapters will create the adapter. */
_stprintf_s(
szArgument, _countof(szArgument),
TEXT("create=\"%.*s|%.*s\""),
MAX_PATH, szDisplayName,
MAX_PATH, szHardwareId);
msica_arg_seq_add_tail(seq, szArgument);
if (seqRollback)
{
/* InstallTUNTAPAdaptersRollback will delete the adapter. */
_stprintf_s(
szArgument, _countof(szArgument),
TEXT("deleteN=\"%.*s\""),
MAX_PATH, szDisplayName);
msica_arg_seq_add_head(seqRollback, szArgument);
}
*iTicks += MSICA_ADAPTER_TICK_SIZE;
break;
}
else if (_tcsicmp(szDisplayName, pAdapterOther->szName) == 0)
{
/* Adapter with a same name found. */
for (LPCTSTR hwid = pAdapterOther->szzHardwareIDs;; hwid += _tcslen(hwid) + 1)
{
if (hwid[0] == 0)
{
/* This adapter has a different hardware ID. */
msg(M_NONFATAL, "%s: Adapter with name \"%" PRIsLPTSTR "\" already exists", __FUNCTION__, pAdapterOther->szName);
dwResult = ERROR_ALREADY_EXISTS;
goto cleanup_pAdapterList;
}
else if (_tcsicmp(hwid, szHardwareId) == 0)
{
/* This is an adapter with the requested hardware ID. We already have what we want! */
break;
}
}
break; /* Adapter names are unique. There should be no other adapter with this name. */
}
}
cleanup_pAdapterList:
tap_free_adapter_list(pAdapterList);
return dwResult;
}
/**
* Schedules adapter deletion.
*
* When the rollback is enabled, the adapter deletition is scheduled as: disable in
* UninstallTUNTAPAdapters, enable on rollback, delete on commit.
*
* When rollback is disabled, the adapter deletition is scheduled as delete in
* UninstallTUNTAPAdapters.
*
* @param seq The argument sequence to pass to UninstallTUNTAPAdapters custom action
*
* @param seqCommit The argument sequence to pass to UninstallTUNTAPAdaptersCommit custom
* action. NULL when rollback is disabled.
*
* @param seqRollback The argument sequence to pass to UninstallTUNTAPAdaptersRollback custom
* action. NULL when rollback is disabled.
*
* @param szDisplayName Adapter display name
*
* @param szzHardwareIDs String of strings with acceptable adapter hardware IDs
*
* @param iTicks Pointer to an integer that represents amount of work (on progress
* indicator) the UninstallTUNTAPAdapters will take. This function increments
* it by MSICA_ADAPTER_TICK_SIZE for each adapter to delete.
*
* @return ERROR_SUCCESS on success; An error code otherwise
*/
static DWORD
schedule_adapter_delete(
_Inout_ struct msica_arg_seq *seq,
_Inout_opt_ struct msica_arg_seq *seqCommit,
_Inout_opt_ struct msica_arg_seq *seqRollback,
_In_z_ LPCTSTR szDisplayName,
_In_z_ LPCTSTR szzHardwareIDs,
_Inout_ int *iTicks)
{
/* Get adapters with given hardware ID. */
struct tap_adapter_node *pAdapterList = NULL;
DWORD dwResult = tap_list_adapters(NULL, szzHardwareIDs, &pAdapterList);
if (dwResult != ERROR_SUCCESS)
{
return dwResult;
}
/* Does adapter exist? */
for (struct tap_adapter_node *pAdapter = pAdapterList; pAdapter != NULL; pAdapter = pAdapter->pNext)
{
if (_tcsicmp(szDisplayName, pAdapter->szName) == 0)
{
/* Adapter found. */
TCHAR szArgument[8 /*disable=|enable=|delete=*/ + 38 /*{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}*/ + 1 /*terminator*/];
if (seqCommit && seqRollback)
{
/* UninstallTUNTAPAdapters will disable the adapter. */
_stprintf_s(
szArgument, _countof(szArgument),
TEXT("disable=") TEXT(PRIXGUID),
PRIGUID_PARAM(pAdapter->guid));
msica_arg_seq_add_tail(seq, szArgument);
/* UninstallTUNTAPAdaptersRollback will re-enable the adapter. */
_stprintf_s(
szArgument, _countof(szArgument),
TEXT("enable=") TEXT(PRIXGUID),
PRIGUID_PARAM(pAdapter->guid));
msica_arg_seq_add_head(seqRollback, szArgument);
/* UninstallTUNTAPAdaptersCommit will delete the adapter. */
_stprintf_s(
szArgument, _countof(szArgument),
TEXT("delete=") TEXT(PRIXGUID),
PRIGUID_PARAM(pAdapter->guid));
msica_arg_seq_add_tail(seqCommit, szArgument);
}
else
{
/* UninstallTUNTAPAdapters will delete the adapter. */
_stprintf_s(
szArgument, _countof(szArgument),
TEXT("delete=") TEXT(PRIXGUID),
PRIGUID_PARAM(pAdapter->guid));
msica_arg_seq_add_tail(seq, szArgument);
}
iTicks += MSICA_ADAPTER_TICK_SIZE;
break; /* Adapter names are unique. There should be no other adapter with this name. */
}
}
tap_free_adapter_list(pAdapterList);
return dwResult;
}
UINT __stdcall
EvaluateTUNTAPAdapters(_In_ MSIHANDLE hInstall)
{
#ifdef _MSC_VER
#pragma comment(linker, DLLEXP_EXPORT)
#endif
debug_popup(TEXT(__FUNCTION__));
UINT uiResult;
BOOL bIsCoInitialized = SUCCEEDED(CoInitialize(NULL));
OPENVPNMSICA_SAVE_MSI_SESSION(hInstall);
struct msica_arg_seq
seqInstall,
seqInstallCommit,
seqInstallRollback,
seqUninstall,
seqUninstallCommit,
seqUninstallRollback;
msica_arg_seq_init(&seqInstall);
msica_arg_seq_init(&seqInstallCommit);
msica_arg_seq_init(&seqInstallRollback);
msica_arg_seq_init(&seqUninstall);
msica_arg_seq_init(&seqUninstallCommit);
msica_arg_seq_init(&seqUninstallRollback);
/* Check rollback state. */
bool bRollbackEnabled = MsiEvaluateCondition(hInstall, TEXT("RollbackDisabled")) != MSICONDITION_TRUE;
/* Open MSI database. */
MSIHANDLE hDatabase = MsiGetActiveDatabase(hInstall);
if (hDatabase == 0)
{
msg(M_NONFATAL, "%s: MsiGetActiveDatabase failed", __FUNCTION__);
uiResult = ERROR_INVALID_HANDLE;
goto cleanup_exec_seq;
}
/* Check if TUNTAPAdapter table exists. If it doesn't exist, there's nothing to do. */
switch (MsiDatabaseIsTablePersistent(hDatabase, TEXT("TUNTAPAdapter")))
{
case MSICONDITION_FALSE:
case MSICONDITION_TRUE: break;
default:
uiResult = ERROR_SUCCESS;
goto cleanup_hDatabase;
}
/* Prepare a query to get a list/view of adapters. */
MSIHANDLE hViewST = 0;
LPCTSTR szQuery = TEXT("SELECT `Adapter`,`DisplayName`,`Condition`,`Component_`,`HardwareId` FROM `TUNTAPAdapter`");
uiResult = MsiDatabaseOpenView(hDatabase, szQuery, &hViewST);
if (uiResult != ERROR_SUCCESS)
{
SetLastError(uiResult); /* MSDN does not mention MsiDatabaseOpenView() to set GetLastError(). But we do have an error code. Set last error manually. */
msg(M_NONFATAL | M_ERRNO, "%s: MsiDatabaseOpenView(\"%" PRIsLPTSTR "\") failed", __FUNCTION__, szQuery);
goto cleanup_hDatabase;
}
/* Execute query! */
uiResult = MsiViewExecute(hViewST, 0);
if (uiResult != ERROR_SUCCESS)
{
SetLastError(uiResult); /* MSDN does not mention MsiViewExecute() to set GetLastError(). But we do have an error code. Set last error manually. */
msg(M_NONFATAL | M_ERRNO, "%s: MsiViewExecute(\"%" PRIsLPTSTR "\") failed", __FUNCTION__, szQuery);
goto cleanup_hViewST;
}
/* Create a record to report progress with. */
MSIHANDLE hRecordProg = MsiCreateRecord(2);
if (!hRecordProg)
{
uiResult = ERROR_INVALID_HANDLE;
msg(M_NONFATAL, "%s: MsiCreateRecord failed", __FUNCTION__);
goto cleanup_hViewST_close;
}
for (;; )
{
/* Fetch one record from the view. */
MSIHANDLE hRecord = 0;
uiResult = MsiViewFetch(hViewST, &hRecord);
if (uiResult == ERROR_NO_MORE_ITEMS)
{
uiResult = ERROR_SUCCESS;
break;
}
else if (uiResult != ERROR_SUCCESS)
{
SetLastError(uiResult); /* MSDN does not mention MsiViewFetch() to set GetLastError(). But we do have an error code. Set last error manually. */
msg(M_NONFATAL | M_ERRNO, "%s: MsiViewFetch failed", __FUNCTION__);
goto cleanup_hRecordProg;
}
INSTALLSTATE iInstalled, iAction;
{
/* Read adapter component ID (`Component_` is field #4). */
LPTSTR szValue = NULL;
uiResult = msi_get_record_string(hRecord, 4, &szValue);
if (uiResult != ERROR_SUCCESS)
{
goto cleanup_hRecord;
}
/* Get the component state. */
uiResult = MsiGetComponentState(hInstall, szValue, &iInstalled, &iAction);
if (uiResult != ERROR_SUCCESS)
{
SetLastError(uiResult); /* MSDN does not mention MsiGetComponentState() to set GetLastError(). But we do have an error code. Set last error manually. */
msg(M_NONFATAL | M_ERRNO, "%s: MsiGetComponentState(\"%" PRIsLPTSTR "\") failed", __FUNCTION__, szValue);
free(szValue);
goto cleanup_hRecord;
}
free(szValue);
}
/* Get adapter display name (`DisplayName` is field #2). */
LPTSTR szDisplayName = NULL;
uiResult = msi_format_field(hInstall, hRecord, 2, &szDisplayName);
if (uiResult != ERROR_SUCCESS)
{
goto cleanup_hRecord;
}
/* `DisplayName` field type is [Filename](https://docs.microsoft.com/en-us/windows/win32/msi/filename), which is either "8.3|long name" or "8.3". */
LPTSTR szDisplayNameEx = _tcschr(szDisplayName, TEXT('|'));
szDisplayNameEx = szDisplayNameEx != NULL ? szDisplayNameEx + 1 : szDisplayName;
/* Get adapter hardware ID (`HardwareId` is field #5). */
TCHAR szzHardwareIDs[0x100] = { 0 };
{
LPTSTR szHwId = NULL;
uiResult = msi_get_record_string(hRecord, 5, &szHwId);
if (uiResult != ERROR_SUCCESS)
{
goto cleanup_szDisplayName;
}
memcpy_s(szzHardwareIDs, sizeof(szzHardwareIDs) - 2*sizeof(TCHAR) /*requires double zero termination*/, szHwId, _tcslen(szHwId)*sizeof(TCHAR));
free(szHwId);
}
if (iAction > INSTALLSTATE_BROKEN)
{
int iTicks = 0;
if (iAction >= INSTALLSTATE_LOCAL)
{
/* Read and evaluate adapter condition (`Condition` is field #3). */
LPTSTR szValue = NULL;
uiResult = msi_get_record_string(hRecord, 3, &szValue);
if (uiResult != ERROR_SUCCESS)
{
goto cleanup_szDisplayName;
}
#ifdef __GNUC__
/*
* warning: enumeration value ‘MSICONDITION_TRUE’ not handled in switch
* warning: enumeration value ‘MSICONDITION_NONE’ not handled in switch
*/
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wswitch"
#endif
switch (MsiEvaluateCondition(hInstall, szValue))
{
case MSICONDITION_FALSE:
free(szValue);
goto cleanup_szDisplayName;
case MSICONDITION_ERROR:
uiResult = ERROR_INVALID_FIELD;
msg(M_NONFATAL | M_ERRNO, "%s: MsiEvaluateCondition(\"%" PRIsLPTSTR "\") failed", __FUNCTION__, szValue);
free(szValue);
goto cleanup_szDisplayName;
}
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
free(szValue);
/* Component is or should be installed. Schedule adapter creation. */
if (schedule_adapter_create(
&seqInstall,
bRollbackEnabled ? &seqInstallRollback : NULL,
szDisplayNameEx,
szzHardwareIDs,
&iTicks) != ERROR_SUCCESS)
{
uiResult = ERROR_INSTALL_FAILED;
goto cleanup_szDisplayName;
}
}
else
{
/* Component is installed, but should be degraded to advertised/removed. Schedule adapter deletition.
*
* Note: On adapter removal (product is being uninstalled), we tolerate dwResult error.
* Better a partial uninstallation than no uninstallation at all.
*/
schedule_adapter_delete(
&seqUninstall,
bRollbackEnabled ? &seqUninstallCommit : NULL,
bRollbackEnabled ? &seqUninstallRollback : NULL,
szDisplayNameEx,
szzHardwareIDs,
&iTicks);
}
/* Arrange the amount of tick space to add to the progress indicator.
* Do this within the loop to poll for user cancellation. */
MsiRecordSetInteger(hRecordProg, 1, 3 /* OP3 = Add ticks to the expected total number of progress of the progress bar */);
MsiRecordSetInteger(hRecordProg, 2, iTicks);
if (MsiProcessMessage(hInstall, INSTALLMESSAGE_PROGRESS, hRecordProg) == IDCANCEL)
{
uiResult = ERROR_INSTALL_USEREXIT;
goto cleanup_szDisplayName;
}
}
cleanup_szDisplayName:
free(szDisplayName);
cleanup_hRecord:
MsiCloseHandle(hRecord);
if (uiResult != ERROR_SUCCESS)
{
goto cleanup_hRecordProg;
}
}
/* Store deferred custom action parameters. */
if ((uiResult = setup_sequence(hInstall, TEXT("InstallTUNTAPAdapters" ), &seqInstall )) != ERROR_SUCCESS
|| (uiResult = setup_sequence(hInstall, TEXT("InstallTUNTAPAdaptersCommit" ), &seqInstallCommit )) != ERROR_SUCCESS
|| (uiResult = setup_sequence(hInstall, TEXT("InstallTUNTAPAdaptersRollback" ), &seqInstallRollback )) != ERROR_SUCCESS
|| (uiResult = setup_sequence(hInstall, TEXT("UninstallTUNTAPAdapters" ), &seqUninstall )) != ERROR_SUCCESS
|| (uiResult = setup_sequence(hInstall, TEXT("UninstallTUNTAPAdaptersCommit" ), &seqUninstallCommit )) != ERROR_SUCCESS
|| (uiResult = setup_sequence(hInstall, TEXT("UninstallTUNTAPAdaptersRollback"), &seqUninstallRollback)) != ERROR_SUCCESS)
{
goto cleanup_hRecordProg;
}
uiResult = ERROR_SUCCESS;
cleanup_hRecordProg:
MsiCloseHandle(hRecordProg);
cleanup_hViewST_close:
MsiViewClose(hViewST);
cleanup_hViewST:
MsiCloseHandle(hViewST);
cleanup_hDatabase:
MsiCloseHandle(hDatabase);
cleanup_exec_seq:
msica_arg_seq_free(&seqInstall);
msica_arg_seq_free(&seqInstallCommit);
msica_arg_seq_free(&seqInstallRollback);
msica_arg_seq_free(&seqUninstall);
msica_arg_seq_free(&seqUninstallCommit);
msica_arg_seq_free(&seqUninstallRollback);
if (bIsCoInitialized)
{
CoUninitialize();
}
return uiResult;
}
/**
* Parses string encoded GUID.
*
* @param szArg Zero terminated string where the GUID string starts
*
* @param guid Pointer to GUID that receives parsed value
*
* @return TRUE on success; FALSE otherwise
*/
static BOOL
parse_guid(
_In_z_ LPCWSTR szArg,
_Out_ GUID *guid)
{
if (swscanf_s(szArg, _L(PRIXGUID), PRIGUID_PARAM_REF(*guid)) != 11)
{
msg(M_NONFATAL | M_ERRNO, "%s: swscanf_s(\"%ls\") failed", __FUNCTION__, szArg);
return FALSE;
}
return TRUE;
}
UINT __stdcall
ProcessDeferredAction(_In_ MSIHANDLE hInstall)
{
#ifdef _MSC_VER
#pragma comment(linker, DLLEXP_EXPORT)
#endif
debug_popup(TEXT(__FUNCTION__));
UINT uiResult;
BOOL bIsCoInitialized = SUCCEEDED(CoInitialize(NULL));
OPENVPNMSICA_SAVE_MSI_SESSION(hInstall);
BOOL bIsCleanup = MsiGetMode(hInstall, MSIRUNMODE_COMMIT) || MsiGetMode(hInstall, MSIRUNMODE_ROLLBACK);
/* Get sequence arguments. Always Unicode as CommandLineToArgvW() is available as Unicode-only. */
LPWSTR szSequence = NULL;
uiResult = msi_get_string(hInstall, L"CustomActionData", &szSequence);
if (uiResult != ERROR_SUCCESS)
{
goto cleanup_CoInitialize;
}
int nArgs;
LPWSTR *szArg = CommandLineToArgvW(szSequence, &nArgs);
if (szArg == NULL)
{
uiResult = GetLastError();
msg(M_NONFATAL | M_ERRNO, "%s: CommandLineToArgvW(\"%ls\") failed", __FUNCTION__, szSequence);
goto cleanup_szSequence;
}
/* Tell the installer to use explicit progress messages. */
MSIHANDLE hRecordProg = MsiCreateRecord(3);
MsiRecordSetInteger(hRecordProg, 1, 1);
MsiRecordSetInteger(hRecordProg, 2, 1);
MsiRecordSetInteger(hRecordProg, 3, 0);
MsiProcessMessage(hInstall, INSTALLMESSAGE_PROGRESS, hRecordProg);
/* Prepare hRecordProg for progress messages. */
MsiRecordSetInteger(hRecordProg, 1, 2);
MsiRecordSetInteger(hRecordProg, 3, 0);
BOOL bRebootRequired = FALSE;
for (int i = 1 /*CommandLineToArgvW injects msiexec.exe as szArg[0]*/; i < nArgs; ++i)
{
DWORD dwResult = ERROR_SUCCESS;
if (wcsncmp(szArg[i], L"create=", 7) == 0)
{
/* Create an adapter with a given name and hardware ID. */
LPWSTR szName = szArg[i] + 7;
LPWSTR szHardwareId = wcschr(szName, L'|');
if (szHardwareId == NULL)
{
goto invalid_argument;
}
szHardwareId[0] = 0;
++szHardwareId;
{
/* Report the name of the adapter to installer. */
MSIHANDLE hRecord = MsiCreateRecord(4);
MsiRecordSetString(hRecord, 1, TEXT("Creating adapter"));
MsiRecordSetString(hRecord, 2, szName);
MsiRecordSetString(hRecord, 3, szHardwareId);
int iResult = MsiProcessMessage(hInstall, INSTALLMESSAGE_ACTIONDATA, hRecord);
MsiCloseHandle(hRecord);
if (iResult == IDCANCEL)
{
uiResult = ERROR_INSTALL_USEREXIT;
goto cleanup;
}
}
GUID guidAdapter;
dwResult = tap_create_adapter(NULL, NULL, szHardwareId, &bRebootRequired, &guidAdapter);
if (dwResult == ERROR_SUCCESS)
{
/* Set adapter name. May fail on some machines, but that is not critical - use silent
flag to mute messagebox and print error only to log */
tap_set_adapter_name(&guidAdapter, szName, TRUE);
}
}
else if (wcsncmp(szArg[i], L"deleteN=", 8) == 0)
{
/* Delete the adapter by name. */
LPCWSTR szName = szArg[i] + 8;
{
/* Report the name of the adapter to installer. */
MSIHANDLE hRecord = MsiCreateRecord(3);
MsiRecordSetString(hRecord, 1, TEXT("Deleting adapter"));
MsiRecordSetString(hRecord, 2, szName);
int iResult = MsiProcessMessage(hInstall, INSTALLMESSAGE_ACTIONDATA, hRecord);
MsiCloseHandle(hRecord);
if (iResult == IDCANCEL)
{
uiResult = ERROR_INSTALL_USEREXIT;
goto cleanup;
}
}
/* Get existing adapters. */
struct tap_adapter_node *pAdapterList = NULL;
dwResult = tap_list_adapters(NULL, NULL, &pAdapterList);
if (dwResult == ERROR_SUCCESS)
{
/* Does the adapter exist? */
for (struct tap_adapter_node *pAdapter = pAdapterList; pAdapter != NULL; pAdapter = pAdapter->pNext)
{
if (_tcsicmp(szName, pAdapter->szName) == 0)
{
/* Adapter found. */
dwResult = tap_delete_adapter(NULL, &pAdapter->guid, &bRebootRequired);
break;
}
}
tap_free_adapter_list(pAdapterList);
}
}
else if (wcsncmp(szArg[i], L"delete=", 7) == 0)
{
/* Delete the adapter by GUID. */
GUID guid;
if (!parse_guid(szArg[i] + 7, &guid))
{
goto invalid_argument;
}
dwResult = tap_delete_adapter(NULL, &guid, &bRebootRequired);
}
else if (wcsncmp(szArg[i], L"enable=", 7) == 0)
{
/* Enable the adapter. */
GUID guid;
if (!parse_guid(szArg[i] + 7, &guid))
{
goto invalid_argument;
}
dwResult = tap_enable_adapter(NULL, &guid, TRUE, &bRebootRequired);
}
else if (wcsncmp(szArg[i], L"disable=", 8) == 0)
{
/* Disable the adapter. */
GUID guid;
if (!parse_guid(szArg[i] + 8, &guid))
{
goto invalid_argument;
}
dwResult = tap_enable_adapter(NULL, &guid, FALSE, &bRebootRequired);
}
else
{
goto invalid_argument;
}
if (dwResult != ERROR_SUCCESS && !bIsCleanup /* Ignore errors in case of commit/rollback to do as much work as possible. */)
{
uiResult = ERROR_INSTALL_FAILURE;
goto cleanup;
}
/* Report progress and check for user cancellation. */
MsiRecordSetInteger(hRecordProg, 2, MSICA_ADAPTER_TICK_SIZE);
if (MsiProcessMessage(hInstall, INSTALLMESSAGE_PROGRESS, hRecordProg) == IDCANCEL)
{
dwResult = ERROR_INSTALL_USEREXIT;
goto cleanup;
}
continue;
invalid_argument:
msg(M_NONFATAL, "%s: Ignoring invalid argument: %ls", __FUNCTION__, szArg[i]);
}
cleanup:
if (bRebootRequired)
{
MsiSetMode(hInstall, MSIRUNMODE_REBOOTATEND, TRUE);
}
MsiCloseHandle(hRecordProg);
LocalFree(szArg);
cleanup_szSequence:
free(szSequence);
cleanup_CoInitialize:
if (bIsCoInitialized)
{
CoUninitialize();
}
return uiResult;
}