Browse code

Introduce tapctl.exe utility and openvpnmsica.dll MSI CA

The tapctl.exe utility is a future replacement for the devcon.exe/
tapinstall.exe utility. While this utility does not offer TAP driver
installation or upgrading, its purpose is to manipulate TAP virtual
network interfaces on Windows. In the long term, its code could be
integrated into openvpn.exe with `--mktun` and `--rmtun`.

The openvpnmsica.dll provides additional MSI custom actions for TUN/TAP
interface creation on install. The interface creation is customizable
using the `TAPInterface` MSI table and is fully compliant with MSI's
deffered processing, commit and rollback. Detailed instruction and
documentation is to be published when MSI packaging completed.

Those utilities were placed into openvpn repository to join the
established compile-sign-package OpenVPN workflow.

Acked-by: Gert Doering <gert@greenie.muc.de>
Message-Id: <20181010192337.6984-1-simon@rozman.si>
URL: https://www.mail-archive.com/search?l=mid&q=20181010192337.6984-1-simon@rozman.si

Signed-off-by: Gert Doering <gert@greenie.muc.de>

Simon Rozman authored on 2018/10/11 04:23:37
Showing 30 changed files
... ...
@@ -1399,10 +1399,12 @@ AC_CONFIG_FILES([
1399 1399
 	src/Makefile
1400 1400
 	src/compat/Makefile
1401 1401
 	src/openvpn/Makefile
1402
+	src/openvpnmsica/Makefile
1402 1403
 	src/openvpnserv/Makefile
1403 1404
 	src/plugins/Makefile
1404 1405
 	src/plugins/auth-pam/Makefile
1405 1406
 	src/plugins/down-root/Makefile
1407
+	src/tapctl/Makefile
1406 1408
 	tests/Makefile
1407 1409
         tests/unit_tests/Makefile
1408 1410
         tests/unit_tests/example_test/Makefile
... ...
@@ -11,6 +11,10 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "msvc-generate", "build\msvc
11 11
 EndProject
12 12
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "compat", "src\compat\compat.vcxproj", "{4B2E2719-E661-45D7-9203-F6F456B22F19}"
13 13
 EndProject
14
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tapctl", "src\tapctl\tapctl.vcxproj", "{A06436E7-D576-490D-8BA0-0751D920334A}"
15
+EndProject
16
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "openvpnmsica", "src\openvpnmsica\openvpnmsica.vcxproj", "{D41AA9D6-B818-476E-992E-0E16EB86BEE2}"
17
+EndProject
14 18
 Global
15 19
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
16 20
 		Debug|Win32 = Debug|Win32
... ...
@@ -51,6 +55,22 @@ Global
51 51
 		{4B2E2719-E661-45D7-9203-F6F456B22F19}.Release|Win32.Build.0 = Release|Win32
52 52
 		{4B2E2719-E661-45D7-9203-F6F456B22F19}.Release|x64.ActiveCfg = Release|x64
53 53
 		{4B2E2719-E661-45D7-9203-F6F456B22F19}.Release|x64.Build.0 = Release|x64
54
+		{A06436E7-D576-490D-8BA0-0751D920334A}.Debug|Win32.ActiveCfg = Debug|Win32
55
+		{A06436E7-D576-490D-8BA0-0751D920334A}.Debug|Win32.Build.0 = Debug|Win32
56
+		{A06436E7-D576-490D-8BA0-0751D920334A}.Debug|x64.ActiveCfg = Debug|x64
57
+		{A06436E7-D576-490D-8BA0-0751D920334A}.Debug|x64.Build.0 = Debug|x64
58
+		{A06436E7-D576-490D-8BA0-0751D920334A}.Release|Win32.ActiveCfg = Release|Win32
59
+		{A06436E7-D576-490D-8BA0-0751D920334A}.Release|Win32.Build.0 = Release|Win32
60
+		{A06436E7-D576-490D-8BA0-0751D920334A}.Release|x64.ActiveCfg = Release|x64
61
+		{A06436E7-D576-490D-8BA0-0751D920334A}.Release|x64.Build.0 = Release|x64
62
+		{D41AA9D6-B818-476E-992E-0E16EB86BEE2}.Debug|Win32.ActiveCfg = Debug|Win32
63
+		{D41AA9D6-B818-476E-992E-0E16EB86BEE2}.Debug|Win32.Build.0 = Debug|Win32
64
+		{D41AA9D6-B818-476E-992E-0E16EB86BEE2}.Debug|x64.ActiveCfg = Debug|x64
65
+		{D41AA9D6-B818-476E-992E-0E16EB86BEE2}.Debug|x64.Build.0 = Debug|x64
66
+		{D41AA9D6-B818-476E-992E-0E16EB86BEE2}.Release|Win32.ActiveCfg = Release|Win32
67
+		{D41AA9D6-B818-476E-992E-0E16EB86BEE2}.Release|Win32.Build.0 = Release|Win32
68
+		{D41AA9D6-B818-476E-992E-0E16EB86BEE2}.Release|x64.ActiveCfg = Release|x64
69
+		{D41AA9D6-B818-476E-992E-0E16EB86BEE2}.Release|x64.Build.0 = Release|x64
54 70
 	EndGlobalSection
55 71
 	GlobalSection(SolutionProperties) = preSolution
56 72
 		HideSolutionNode = FALSE
... ...
@@ -12,4 +12,4 @@
12 12
 MAINTAINERCLEANFILES = \
13 13
 	$(srcdir)/Makefile.in
14 14
 
15
-SUBDIRS = compat openvpn openvpnserv plugins
15
+SUBDIRS = compat openvpn openvpnmsica openvpnserv plugins tapctl
... ...
@@ -14,7 +14,10 @@ MAINTAINERCLEANFILES = \
14 14
 
15 15
 EXTRA_DIST = \
16 16
 	compat.vcxproj \
17
-	compat.vcxproj.filters
17
+	compat.vcxproj.filters \
18
+	PropertySheet.props \
19
+	Debug.props \
20
+	Release.props
18 21
 
19 22
 noinst_LTLIBRARIES = libcompat.la
20 23
 
21 24
new file mode 100644
... ...
@@ -0,0 +1,55 @@
0
+#
1
+#  openvpnmsica -- Custom Action DLL to provide OpenVPN-specific support to MSI packages
2
+#
3
+#  Copyright (C) 2002-2018 OpenVPN Inc <sales@openvpn.net>
4
+#  Copyright (C) 2018 Simon Rozman <simon@rozman.si>
5
+#
6
+#  This program is free software; you can redistribute it and/or modify
7
+#  it under the terms of the GNU General Public License version 2
8
+#  as published by the Free Software Foundation.
9
+#
10
+#  This program is distributed in the hope that it will be useful,
11
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
+#  GNU General Public License for more details.
14
+#
15
+#  You should have received a copy of the GNU General Public License along
16
+#  with this program; if not, write to the Free Software Foundation, Inc.,
17
+#  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18
+#
19
+
20
+include $(top_srcdir)/build/ltrc.inc
21
+
22
+MAINTAINERCLEANFILES = $(srcdir)/Makefile.in
23
+
24
+EXTRA_DIST = \
25
+	openvpnmsica.vcxproj \
26
+	openvpnmsica.vcxproj.filters \
27
+	openvpnmsica.props \
28
+	openvpnmsica-Debug.props \
29
+	openvpnmsica-Release.props
30
+
31
+AM_CPPFLAGS = \
32
+	-I$(top_srcdir)/include -I$(top_srcdir)/src/compat
33
+
34
+AM_CFLAGS = \
35
+	$(TAP_CFLAGS)
36
+
37
+if WIN32
38
+lib_LTLIBRARIES = libopenvpnmsica.la
39
+libopenvpnmsica_la_CFLAGS = \
40
+	-municode -D_UNICODE \
41
+	-UNTDDI_VERSION -U_WIN32_WINNT \
42
+	-D_WIN32_WINNT=_WIN32_WINNT_VISTA
43
+libopenvpnmsica_la_LDFLAGS = -ladvapi32 -lole32 -lmsi -lsetupapi -lshlwapi -no-undefined -avoid-version
44
+endif
45
+
46
+libopenvpnmsica_la_SOURCES = \
47
+	dllmain.c \
48
+	msiex.c msiex.h \
49
+	msica_op.c msica_op.h \
50
+	openvpnmsica.c openvpnmsica.h \
51
+	$(top_srcdir)/src/tapctl/basic.h \
52
+	$(top_srcdir)/src/tapctl/error.c $(top_srcdir)/src/tapctl/error.h \
53
+	$(top_srcdir)/src/tapctl/tap.c $(top_srcdir)/src/tapctl/tap.h \
54
+	openvpnmsica_resources.rc
0 55
new file mode 100644
... ...
@@ -0,0 +1,179 @@
0
+/*
1
+ *  openvpnmsica -- Custom Action DLL to provide OpenVPN-specific support to MSI packages
2
+ *
3
+ *  Copyright (C) 2018 Simon Rozman <simon@rozman.si>
4
+ *
5
+ *  This program is free software; you can redistribute it and/or modify
6
+ *  it under the terms of the GNU General Public License version 2
7
+ *  as published by the Free Software Foundation.
8
+ *
9
+ *  This program is distributed in the hope that it will be useful,
10
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
+ *  GNU General Public License for more details.
13
+ *
14
+ *  You should have received a copy of the GNU General Public License along
15
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
16
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
+ */
18
+
19
+#ifdef HAVE_CONFIG_H
20
+#include <config.h>
21
+#elif defined(_MSC_VER)
22
+#include <config-msvc.h>
23
+#endif
24
+
25
+#include "openvpnmsica.h"
26
+#include "../tapctl/error.h"
27
+
28
+#include <windows.h>
29
+#include <msi.h>
30
+#include <msiquery.h>
31
+#ifdef _MSC_VER
32
+#pragma comment(lib, "msi.lib")
33
+#endif
34
+#include <stdio.h>
35
+#include <tchar.h>
36
+
37
+
38
+DWORD openvpnmsica_tlsidx_session = TLS_OUT_OF_INDEXES;
39
+
40
+
41
+/**
42
+ * DLL entry point
43
+ */
44
+BOOL WINAPI DllMain(
45
+    _In_ HINSTANCE hinstDLL,
46
+    _In_ DWORD     dwReason,
47
+    _In_ LPVOID    lpReserved)
48
+{
49
+    UNREFERENCED_PARAMETER(hinstDLL);
50
+    UNREFERENCED_PARAMETER(lpReserved);
51
+
52
+    switch (dwReason)
53
+    {
54
+        case DLL_PROCESS_ATTACH:
55
+            /* Allocate TLS index. */
56
+            openvpnmsica_tlsidx_session = TlsAlloc();
57
+            if (openvpnmsica_tlsidx_session == TLS_OUT_OF_INDEXES)
58
+                return FALSE;
59
+            /* Fall through. */
60
+
61
+        case DLL_THREAD_ATTACH:
62
+        {
63
+            /* Create TLS data. */
64
+            struct openvpnmsica_tls_data *s = (struct openvpnmsica_tls_data*)malloc(sizeof(struct openvpnmsica_tls_data));
65
+            memset(s, 0, sizeof(struct openvpnmsica_tls_data));
66
+            TlsSetValue(openvpnmsica_tlsidx_session, s);
67
+            break;
68
+        }
69
+
70
+        case DLL_PROCESS_DETACH:
71
+            if (openvpnmsica_tlsidx_session != TLS_OUT_OF_INDEXES)
72
+            {
73
+                /* Free TLS data and TLS index. */
74
+                free(TlsGetValue(openvpnmsica_tlsidx_session));
75
+                TlsFree(openvpnmsica_tlsidx_session);
76
+            }
77
+            break;
78
+
79
+        case DLL_THREAD_DETACH:
80
+            /* Free TLS data. */
81
+            free(TlsGetValue(openvpnmsica_tlsidx_session));
82
+            break;
83
+    }
84
+
85
+    return TRUE;
86
+}
87
+
88
+
89
+bool
90
+dont_mute(unsigned int flags)
91
+{
92
+    UNREFERENCED_PARAMETER(flags);
93
+
94
+    return true;
95
+}
96
+
97
+
98
+void
99
+x_msg_va(const unsigned int flags, const char *format, va_list arglist)
100
+{
101
+    /* Secure last error before it is overridden. */
102
+    DWORD dwResult = (flags & M_ERRNO) != 0 ? GetLastError() : ERROR_SUCCESS;
103
+
104
+    struct openvpnmsica_tls_data *s = (struct openvpnmsica_tls_data *)TlsGetValue(openvpnmsica_tlsidx_session);
105
+    if (s->hInstall == 0)
106
+    {
107
+        /* No MSI session, no fun. */
108
+        return;
109
+    }
110
+
111
+    /* Prepare the message record. The record will contain up to four fields. */
112
+    MSIHANDLE hRecordProg = MsiCreateRecord(4);
113
+
114
+    {
115
+        /* Field 2: The message string. */
116
+        char szBufStack[128];
117
+        int iResultLen = vsnprintf(szBufStack, _countof(szBufStack), format, arglist);
118
+        if (iResultLen < _countof(szBufStack))
119
+        {
120
+            /* Use from stack. */
121
+            MsiRecordSetStringA(hRecordProg, 2, szBufStack);
122
+        }
123
+        else
124
+        {
125
+            /* Allocate on heap and retry. */
126
+            char *szMessage = (char*)malloc(++iResultLen * sizeof(char));
127
+            vsnprintf(szMessage, iResultLen, format, arglist);
128
+            MsiRecordSetStringA(hRecordProg, 2, szMessage);
129
+            free(szMessage);
130
+        }
131
+    }
132
+
133
+    if ((flags & M_ERRNO) == 0)
134
+    {
135
+        /* Field 1: MSI Error Code */
136
+        MsiRecordSetInteger(hRecordProg, 1, ERROR_MSICA);
137
+    }
138
+    else
139
+    {
140
+        /* Field 1: MSI Error Code */
141
+        MsiRecordSetInteger(hRecordProg, 1, ERROR_MSICA_ERRNO);
142
+
143
+        /* Field 3: The Windows error number. */
144
+        MsiRecordSetInteger(hRecordProg, 3, dwResult);
145
+
146
+        /* Field 4: The Windows error description. */
147
+        LPTSTR szErrMessage = NULL;
148
+        if (FormatMessage(
149
+            FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS,
150
+            0,
151
+            dwResult,
152
+            0,
153
+            (LPTSTR)&szErrMessage,
154
+            0,
155
+            NULL) && szErrMessage)
156
+        {
157
+            /* Trim trailing whitespace. Set terminator after the last non-whitespace character. This prevents excessive trailing line breaks. */
158
+            for (size_t i = 0, i_last = 0; ; i++)
159
+            {
160
+                if (szErrMessage[i])
161
+                {
162
+                    if (!_istspace(szErrMessage[i]))
163
+                        i_last = i + 1;
164
+                }
165
+                else
166
+                {
167
+                    szErrMessage[i_last] = 0;
168
+                    break;
169
+                }
170
+            }
171
+            MsiRecordSetString(hRecordProg, 4, szErrMessage);
172
+            LocalFree(szErrMessage);
173
+        }
174
+    }
175
+
176
+    MsiProcessMessage(s->hInstall, INSTALLMESSAGE_ERROR, hRecordProg);
177
+    MsiCloseHandle(hRecordProg);
178
+}
0 179
new file mode 100644
... ...
@@ -0,0 +1,935 @@
0
+/*
1
+ *  openvpnmsica -- Custom Action DLL to provide OpenVPN-specific support to MSI packages
2
+ *
3
+ *  Copyright (C) 2018 Simon Rozman <simon@rozman.si>
4
+ *
5
+ *  This program is free software; you can redistribute it and/or modify
6
+ *  it under the terms of the GNU General Public License version 2
7
+ *  as published by the Free Software Foundation.
8
+ *
9
+ *  This program is distributed in the hope that it will be useful,
10
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
+ *  GNU General Public License for more details.
13
+ *
14
+ *  You should have received a copy of the GNU General Public License along
15
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
16
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
+ */
18
+
19
+#ifdef HAVE_CONFIG_H
20
+#include <config.h>
21
+#elif defined(_MSC_VER)
22
+#include <config-msvc.h>
23
+#endif
24
+
25
+#include "msica_op.h"
26
+#include "../tapctl/error.h"
27
+#include "../tapctl/tap.h"
28
+
29
+#include <windows.h>
30
+#include <malloc.h>
31
+#include <msiquery.h>
32
+#include <objbase.h>
33
+
34
+#ifdef _MSC_VER
35
+#pragma comment(lib, "msi.lib")
36
+#pragma comment(lib, "ole32.lib")
37
+#endif
38
+
39
+
40
+/**
41
+ * Operation data persist header
42
+ */
43
+struct msica_op_hdr
44
+{
45
+    enum msica_op_type type;  /** Action type */
46
+    int ticks;                /** Number of ticks on the progress indicator this operation represents */
47
+    DWORD size_data;          /** Size of the operation data (DWORD to better align with Win32 API) */
48
+};
49
+
50
+
51
+void
52
+msica_op_seq_init(_Inout_ struct msica_op_seq *seq)
53
+{
54
+    seq->head = NULL;
55
+    seq->tail = NULL;
56
+}
57
+
58
+
59
+void
60
+msica_op_seq_free(_Inout_ struct msica_op_seq *seq)
61
+{
62
+    while (seq->head)
63
+    {
64
+        struct msica_op *op = seq->head;
65
+        seq->head = seq->head->next;
66
+        free(op);
67
+    }
68
+    seq->tail = NULL;
69
+}
70
+
71
+
72
+struct msica_op*
73
+msica_op_create_bool(
74
+    _In_     enum msica_op_type  type,
75
+    _In_     int                 ticks,
76
+    _In_opt_ struct msica_op    *next,
77
+    _In_     bool                value)
78
+{
79
+    if (MSICA_OP_TYPE_DATA(type) != 0x1)
80
+    {
81
+        msg(M_NONFATAL, "%s: Operation data type not bool (%x)", __FUNCTION__, MSICA_OP_TYPE_DATA(type));
82
+        return NULL;
83
+    }
84
+
85
+    /* Create and fill operation struct. */
86
+    struct msica_op_bool *op = (struct msica_op_bool*)malloc(sizeof(struct msica_op_bool));
87
+    op->base.type  = type;
88
+    op->base.ticks = ticks;
89
+    op->base.next  = next;
90
+    op->value      = value;
91
+
92
+    return &op->base;
93
+}
94
+
95
+
96
+struct msica_op*
97
+msica_op_create_string(
98
+    _In_     enum msica_op_type  type,
99
+    _In_     int                 ticks,
100
+    _In_opt_ struct msica_op    *next,
101
+    _In_z_   LPCTSTR             value)
102
+{
103
+    if (MSICA_OP_TYPE_DATA(type) != 0x2)
104
+    {
105
+        msg(M_NONFATAL, "%s: Operation data type not string (%x)", __FUNCTION__, MSICA_OP_TYPE_DATA(type));
106
+        return NULL;
107
+    }
108
+
109
+    /* Create and fill operation struct. */
110
+    size_t value_size = (_tcslen(value) + 1) * sizeof(TCHAR);
111
+    struct msica_op_string *op = (struct msica_op_string*)malloc(sizeof(struct msica_op_string) + value_size);
112
+    op->base.type  = type;
113
+    op->base.ticks = ticks;
114
+    op->base.next  = next;
115
+    memcpy(op->value, value, value_size);
116
+
117
+    return &op->base;
118
+}
119
+
120
+
121
+struct msica_op*
122
+msica_op_create_multistring_va(
123
+    _In_     enum msica_op_type  type,
124
+    _In_     int                 ticks,
125
+    _In_opt_ struct msica_op    *next,
126
+    _In_     va_list             arglist)
127
+{
128
+    if (MSICA_OP_TYPE_DATA(type) != 0x3)
129
+    {
130
+        msg(M_NONFATAL, "%s: Operation data type not multi-string (%x)", __FUNCTION__, MSICA_OP_TYPE_DATA(type));
131
+        return NULL;
132
+    }
133
+
134
+    /* Calculate required space first. */
135
+    LPCTSTR str;
136
+    size_t value_size = 1;
137
+    for (va_list a = arglist; (str = va_arg(a, LPCTSTR)) != NULL; value_size += _tcslen(str) + 1);
138
+    value_size *= sizeof(TCHAR);
139
+
140
+    /* Create and fill operation struct. */
141
+    struct msica_op_multistring *op = (struct msica_op_multistring*)malloc(sizeof(struct msica_op_multistring) + value_size);
142
+    op->base.type  = type;
143
+    op->base.ticks = ticks;
144
+    op->base.next  = next;
145
+    LPTSTR value = op->value;
146
+    for (va_list a = arglist; (str = va_arg(a, LPCTSTR)) != NULL;)
147
+    {
148
+        size_t size = _tcslen(str) + 1;
149
+        memcpy(value, str, size*sizeof(TCHAR));
150
+        value += size;
151
+    }
152
+    value[0] = 0;
153
+
154
+    return &op->base;
155
+}
156
+
157
+
158
+struct msica_op*
159
+msica_op_create_guid(
160
+    _In_     enum msica_op_type  type,
161
+    _In_     int                 ticks,
162
+    _In_opt_ struct msica_op    *next,
163
+    _In_     const GUID         *value)
164
+{
165
+    if (MSICA_OP_TYPE_DATA(type) != 0x4)
166
+    {
167
+        msg(M_NONFATAL, "%s: Operation data type not GUID (%x)", __FUNCTION__, MSICA_OP_TYPE_DATA(type));
168
+        return NULL;
169
+    }
170
+
171
+    /* Create and fill operation struct. */
172
+    struct msica_op_guid *op = (struct msica_op_guid*)malloc(sizeof(struct msica_op_guid));
173
+    op->base.type  = type;
174
+    op->base.ticks = ticks;
175
+    op->base.next  = next;
176
+    memcpy(&op->value, value, sizeof(GUID));
177
+
178
+    return &op->base;
179
+}
180
+
181
+
182
+struct msica_op*
183
+msica_op_create_guid_string(
184
+    _In_     enum msica_op_type  type,
185
+    _In_     int                 ticks,
186
+    _In_opt_ struct msica_op    *next,
187
+    _In_     const GUID         *value_guid,
188
+    _In_z_   LPCTSTR             value_str)
189
+{
190
+    if (MSICA_OP_TYPE_DATA(type) != 0x5)
191
+    {
192
+        msg(M_NONFATAL, "%s: Operation data type not GUID-string (%x)", __FUNCTION__, MSICA_OP_TYPE_DATA(type));
193
+        return NULL;
194
+    }
195
+
196
+    /* Create and fill operation struct. */
197
+    size_t value_str_size = (_tcslen(value_str) + 1) * sizeof(TCHAR);
198
+    struct msica_op_guid_string *op = (struct msica_op_guid_string*)malloc(sizeof(struct msica_op_guid_string) + value_str_size);
199
+    op->base.type  = type;
200
+    op->base.ticks = ticks;
201
+    op->base.next  = next;
202
+    memcpy(&op->value_guid, value_guid, sizeof(GUID)  );
203
+    memcpy( op->value_str , value_str , value_str_size);
204
+
205
+    return &op->base;
206
+}
207
+
208
+
209
+void
210
+msica_op_seq_add_head(
211
+    _Inout_ struct msica_op_seq *seq,
212
+    _Inout_ struct msica_op     *operation)
213
+{
214
+    /* Insert list in the head. */
215
+    struct msica_op *op;
216
+    for (op = operation; op->next; op = op->next);
217
+    op->next = seq->head;
218
+
219
+    /* Update head (and tail). */
220
+    seq->head = operation;
221
+    if (seq->tail == NULL)
222
+        seq->tail = op;
223
+}
224
+
225
+
226
+void
227
+msica_op_seq_add_tail(
228
+    _Inout_ struct msica_op_seq *seq,
229
+    _Inout_ struct msica_op     *operation)
230
+{
231
+    /* Append list to the tail. */
232
+    struct msica_op *op;
233
+    for (op = operation; op->next; op = op->next);
234
+    if (seq->tail)
235
+        seq->tail->next = operation;
236
+    else
237
+        seq->head = operation;
238
+    seq->tail = op;
239
+}
240
+
241
+
242
+DWORD
243
+msica_op_seq_save(
244
+    _In_ const struct msica_op_seq *seq,
245
+    _In_ HANDLE                     hFile)
246
+{
247
+    DWORD dwWritten;
248
+    for (const struct msica_op *op = seq->head; op; op = op->next)
249
+    {
250
+        struct msica_op_hdr hdr;
251
+        hdr.type  = op->type;
252
+        hdr.ticks = op->ticks;
253
+
254
+        /* Calculate size of data. */
255
+        switch (MSICA_OP_TYPE_DATA(op->type))
256
+        {
257
+        case 0x1: /* msica_op_bool */
258
+            hdr.size_data = sizeof(struct msica_op_bool) - sizeof(struct msica_op);
259
+            break;
260
+
261
+        case 0x2: /* msica_op_string */
262
+            hdr.size_data =
263
+                sizeof(struct msica_op_string) - sizeof(struct msica_op) +
264
+                (DWORD)(_tcslen(((struct msica_op_string*)op)->value) + 1) * sizeof(TCHAR);
265
+            break;
266
+
267
+        case 0x3: /* msica_op_multistring */
268
+        {
269
+            LPCTSTR str;
270
+            for (str = ((struct msica_op_multistring*)op)->value; str[0]; str += _tcslen(str) + 1);
271
+            hdr.size_data =
272
+                sizeof(struct msica_op_multistring) - sizeof(struct msica_op) +
273
+                (DWORD)(str + 1 - ((struct msica_op_multistring*)op)->value) * sizeof(TCHAR);
274
+            break;
275
+        }
276
+
277
+        case 0x4: /* msica_op_guid */
278
+            hdr.size_data = sizeof(struct msica_op_guid) - sizeof(struct msica_op);
279
+            break;
280
+
281
+        case 0x5: /* msica_op_guid_string */
282
+            hdr.size_data =
283
+                sizeof(struct msica_op_guid_string) - sizeof(struct msica_op) +
284
+                (DWORD)(_tcslen(((struct msica_op_guid_string*)op)->value_str) + 1) * sizeof(TCHAR);
285
+            break;
286
+
287
+        default:
288
+            msg(M_NONFATAL, "%s: Unknown operation data type (%x)", __FUNCTION__, MSICA_OP_TYPE_DATA(op->type));
289
+            return ERROR_BAD_ARGUMENTS;
290
+        }
291
+
292
+        if (!WriteFile(hFile, &hdr, sizeof(struct msica_op_hdr), &dwWritten, NULL) ||
293
+            !WriteFile(hFile, op + 1, hdr.size_data, &dwWritten, NULL))
294
+        {
295
+            DWORD dwResult = GetLastError();
296
+            msg(M_NONFATAL | M_ERRNO, "%s: WriteFile failed", __FUNCTION__);
297
+            return dwResult;
298
+        }
299
+    }
300
+
301
+    return ERROR_SUCCESS;
302
+}
303
+
304
+
305
+DWORD
306
+msica_op_seq_load(
307
+    _Inout_ struct msica_op_seq *seq,
308
+    _In_    HANDLE               hFile)
309
+{
310
+    DWORD dwRead;
311
+
312
+    seq->head = seq->tail = NULL;
313
+
314
+    for (;;)
315
+    {
316
+        struct msica_op_hdr hdr;
317
+        if (!ReadFile(hFile, &hdr, sizeof(struct msica_op_hdr), &dwRead, NULL))
318
+        {
319
+            DWORD dwResult = GetLastError();
320
+            msg(M_NONFATAL | M_ERRNO, "%s: ReadFile failed", __FUNCTION__);
321
+            return dwResult;
322
+        }
323
+        else if (dwRead == 0)
324
+        {
325
+            /* EOF */
326
+            return ERROR_SUCCESS;
327
+        }
328
+        else if (dwRead < sizeof(struct msica_op_hdr))
329
+        {
330
+            msg(M_NONFATAL, "%s: Incomplete ReadFile", __FUNCTION__);
331
+            return ERROR_INVALID_DATA;
332
+        }
333
+        struct msica_op *op = (struct msica_op*)malloc(sizeof(struct msica_op) + hdr.size_data);
334
+        op->type  = hdr.type;
335
+        op->ticks = hdr.ticks;
336
+        op->next  = NULL;
337
+        if (!ReadFile(hFile, op + 1, hdr.size_data, &dwRead, NULL))
338
+        {
339
+            DWORD dwResult = GetLastError();
340
+            msg(M_NONFATAL | M_ERRNO, "%s: ReadFile failed", __FUNCTION__);
341
+            free(op);
342
+            return dwResult;
343
+        }
344
+        else if (dwRead < hdr.size_data)
345
+        {
346
+            msg(M_NONFATAL, "%s: Incomplete ReadFile", __FUNCTION__);
347
+            return ERROR_INVALID_DATA;
348
+        }
349
+        msica_op_seq_add_tail(seq, op);
350
+    }
351
+}
352
+
353
+
354
+static DWORD
355
+msica_op_tap_interface_create_exec(
356
+    _Inout_ const struct msica_op_string *op,
357
+    _Inout_ struct msica_session         *session)
358
+{
359
+    if (op == NULL || session == NULL)
360
+        return ERROR_BAD_ARGUMENTS;
361
+
362
+    {
363
+        /* Report the name of the interface to installer. */
364
+        MSIHANDLE hRecord = MsiCreateRecord(3);
365
+        MsiRecordSetString(hRecord, 1, TEXT("Creating TAP interface"));
366
+        MsiRecordSetString(hRecord, 2, op->value);
367
+        int iResult = MsiProcessMessage(session->hInstall, INSTALLMESSAGE_ACTIONDATA, hRecord);
368
+        MsiCloseHandle(hRecord);
369
+        if (iResult == IDCANCEL)
370
+            return ERROR_INSTALL_USEREXIT;
371
+    }
372
+
373
+    /* Get available network interfaces. */
374
+    struct tap_interface_node *pInterfaceList = NULL;
375
+    DWORD dwResult = tap_list_interfaces(NULL, &pInterfaceList);
376
+    if (dwResult == ERROR_SUCCESS)
377
+    {
378
+        /* Does interface exist? */
379
+        for (struct tap_interface_node *pInterfaceOther = pInterfaceList; ; pInterfaceOther = pInterfaceOther->pNext)
380
+        {
381
+            if (pInterfaceOther == NULL)
382
+            {
383
+                /* No interface with a same name found. Create one. */
384
+                BOOL bRebootRequired = FALSE;
385
+                GUID guidInterface;
386
+                dwResult = tap_create_interface(NULL, NULL, &bRebootRequired, &guidInterface);
387
+                if (dwResult == ERROR_SUCCESS)
388
+                {
389
+                    /* Set interface name. */
390
+                    dwResult = tap_set_interface_name(&guidInterface, op->value);
391
+                    if (dwResult == ERROR_SUCCESS)
392
+                    {
393
+                        if (session->rollback_enabled)
394
+                        {
395
+                            /* Order rollback action to delete it. */
396
+                            msica_op_seq_add_head(
397
+                                &session->seq_cleanup[MSICA_CLEANUP_ACTION_ROLLBACK],
398
+                                msica_op_create_guid(
399
+                                    msica_op_tap_interface_delete_by_guid,
400
+                                    0,
401
+                                    NULL,
402
+                                    &guidInterface));
403
+                        }
404
+                    }
405
+                    else
406
+                        tap_delete_interface(NULL, &guidInterface, &bRebootRequired);
407
+
408
+                    if (bRebootRequired)
409
+                        MsiSetMode(session->hInstall, MSIRUNMODE_REBOOTATEND, TRUE);
410
+                }
411
+                break;
412
+            }
413
+            else if (_tcsicmp(op->value, pInterfaceOther->szName) == 0)
414
+            {
415
+                /* Interface with a same name found. */
416
+                for (LPCTSTR hwid = pInterfaceOther->szzHardwareIDs; ; hwid += _tcslen(hwid) + 1)
417
+                {
418
+                    if (hwid[0] == 0)
419
+                    {
420
+                        /* This is not a TAP interface. */
421
+                        msg(M_NONFATAL, "%s: Interface with name \"%"PRIsLPTSTR"\" already exists", __FUNCTION__, pInterfaceOther->szName);
422
+                        dwResult = ERROR_ALREADY_EXISTS;
423
+                        break;
424
+                    }
425
+                    else if (
426
+                        _tcsicmp(hwid, TEXT(TAP_WIN_COMPONENT_ID)) == 0 ||
427
+                        _tcsicmp(hwid, TEXT("root\\") TEXT(TAP_WIN_COMPONENT_ID)) == 0)
428
+                    {
429
+                        /* This is a TAP interface. We already got what we wanted! */
430
+                        dwResult = ERROR_SUCCESS;
431
+                        break;
432
+                    }
433
+                }
434
+                break;
435
+            }
436
+        }
437
+
438
+        tap_free_interface_list(pInterfaceList);
439
+    }
440
+
441
+    return dwResult;
442
+}
443
+
444
+
445
+static DWORD
446
+msica_op_tap_interface_delete(
447
+    _In_    struct tap_interface_node *pInterfaceList,
448
+    _In_    struct tap_interface_node *pInterface,
449
+    _Inout_ struct msica_session      *session)
450
+{
451
+    if (pInterfaceList == NULL || pInterface == NULL || session == NULL)
452
+        return ERROR_BAD_ARGUMENTS;
453
+
454
+    DWORD dwResult;
455
+
456
+    if (session->rollback_enabled)
457
+    {
458
+        int count = 0;
459
+
460
+        do {
461
+            /* Rename the interface to keep it as a backup. */
462
+            TCHAR szNameBackup[10/*"Interface "*/ + 10/*maximum int*/ + 1/*terminator*/];
463
+            _stprintf_s(
464
+                szNameBackup, _countof(szNameBackup),
465
+                TEXT("Interface %i"),
466
+                ++count);
467
+            for (struct tap_interface_node *pInterfaceOther = pInterfaceList; ; pInterfaceOther = pInterfaceOther->pNext)
468
+            {
469
+                if (pInterfaceOther == NULL)
470
+                {
471
+                    /* No interface with a same name found. All clear to rename the interface. */
472
+                    dwResult = tap_set_interface_name(&pInterface->guid, szNameBackup);
473
+                    break;
474
+                }
475
+                else if (_tcsicmp(szNameBackup, pInterfaceOther->szName) == 0)
476
+                {
477
+                    /* Interface with a same name found. Duplicate interface names are not allowed. */
478
+                    dwResult = ERROR_ALREADY_EXISTS;
479
+                    break;
480
+                }
481
+            }
482
+        } while (dwResult == ERROR_ALREADY_EXISTS);
483
+
484
+        if (dwResult == ERROR_SUCCESS) {
485
+            /* Schedule rollback action to rename the interface back. */
486
+            msica_op_seq_add_head(
487
+                &session->seq_cleanup[MSICA_CLEANUP_ACTION_ROLLBACK],
488
+                msica_op_create_guid_string(
489
+                    msica_op_tap_interface_set_name,
490
+                    0,
491
+                    NULL,
492
+                    &pInterface->guid,
493
+                    pInterface->szName));
494
+
495
+            /* Schedule commit action to delete the interface. */
496
+            msica_op_seq_add_tail(
497
+                &session->seq_cleanup[MSICA_CLEANUP_ACTION_COMMIT],
498
+                msica_op_create_guid(
499
+                    msica_op_tap_interface_delete_by_guid,
500
+                    0,
501
+                    NULL,
502
+                    &pInterface->guid));
503
+        }
504
+    }
505
+    else
506
+    {
507
+        /* Delete the interface. */
508
+        BOOL bRebootRequired = FALSE;
509
+        dwResult = tap_delete_interface(NULL, &pInterface->guid, &bRebootRequired);
510
+        if (bRebootRequired)
511
+            MsiSetMode(session->hInstall, MSIRUNMODE_REBOOTATEND, TRUE);
512
+    }
513
+
514
+    return dwResult;
515
+}
516
+
517
+
518
+static DWORD
519
+msica_op_tap_interface_delete_by_name_exec(
520
+    _Inout_ const struct msica_op_string *op,
521
+    _Inout_ struct msica_session         *session)
522
+{
523
+    if (op == NULL || session == NULL)
524
+        return ERROR_BAD_ARGUMENTS;
525
+
526
+    {
527
+        /* Report the name of the interface to installer. */
528
+        MSIHANDLE hRecord = MsiCreateRecord(3);
529
+        MsiRecordSetString(hRecord, 1, TEXT("Deleting interface"));
530
+        MsiRecordSetString(hRecord, 2, op->value);
531
+        int iResult = MsiProcessMessage(session->hInstall, INSTALLMESSAGE_ACTIONDATA, hRecord);
532
+        MsiCloseHandle(hRecord);
533
+        if (iResult == IDCANCEL)
534
+            return ERROR_INSTALL_USEREXIT;
535
+    }
536
+
537
+    /* Get available network interfaces. */
538
+    struct tap_interface_node *pInterfaceList = NULL;
539
+    DWORD dwResult = tap_list_interfaces(NULL, &pInterfaceList);
540
+    if (dwResult == ERROR_SUCCESS)
541
+    {
542
+        /* Does interface exist? */
543
+        for (struct tap_interface_node *pInterface = pInterfaceList; ; pInterface = pInterface->pNext)
544
+        {
545
+            if (pInterface == NULL)
546
+            {
547
+                /* Interface not found. We already got what we wanted! */
548
+                dwResult = ERROR_SUCCESS;
549
+                break;
550
+            }
551
+            else if (_tcsicmp(op->value, pInterface->szName) == 0)
552
+            {
553
+                /* Interface found. */
554
+                dwResult = msica_op_tap_interface_delete(
555
+                    pInterfaceList,
556
+                    pInterface,
557
+                    session);
558
+                break;
559
+            }
560
+        }
561
+
562
+        tap_free_interface_list(pInterfaceList);
563
+    }
564
+
565
+    return dwResult;
566
+}
567
+
568
+
569
+static DWORD
570
+msica_op_tap_interface_delete_by_guid_exec(
571
+    _Inout_ const struct msica_op_guid *op,
572
+    _Inout_ struct msica_session       *session)
573
+{
574
+    if (op == NULL || session == NULL)
575
+        return ERROR_BAD_ARGUMENTS;
576
+
577
+    {
578
+        /* Report the GUID of the interface to installer. */
579
+        MSIHANDLE hRecord = MsiCreateRecord(3);
580
+        LPOLESTR szInterfaceId = NULL;
581
+        StringFromIID((REFIID)&op->value, &szInterfaceId);
582
+        MsiRecordSetString(hRecord, 1, TEXT("Deleting interface"));
583
+        MsiRecordSetString(hRecord, 2, szInterfaceId);
584
+        int iResult = MsiProcessMessage(session->hInstall, INSTALLMESSAGE_ACTIONDATA, hRecord);
585
+        CoTaskMemFree(szInterfaceId);
586
+        MsiCloseHandle(hRecord);
587
+        if (iResult == IDCANCEL)
588
+            return ERROR_INSTALL_USEREXIT;
589
+    }
590
+
591
+    /* Get available network interfaces. */
592
+    struct tap_interface_node *pInterfaceList = NULL;
593
+    DWORD dwResult = tap_list_interfaces(NULL, &pInterfaceList);
594
+    if (dwResult == ERROR_SUCCESS)
595
+    {
596
+        /* Does interface exist? */
597
+        for (struct tap_interface_node *pInterface = pInterfaceList; ; pInterface = pInterface->pNext)
598
+        {
599
+            if (pInterface == NULL)
600
+            {
601
+                /* Interface not found. We already got what we wanted! */
602
+                dwResult = ERROR_SUCCESS;
603
+                break;
604
+            }
605
+            else if (memcmp(&op->value, &pInterface->guid, sizeof(GUID)) == 0)
606
+            {
607
+                /* Interface found. */
608
+                dwResult = msica_op_tap_interface_delete(
609
+                    pInterfaceList,
610
+                    pInterface,
611
+                    session);
612
+                break;
613
+            }
614
+        }
615
+
616
+        tap_free_interface_list(pInterfaceList);
617
+    }
618
+
619
+    return dwResult;
620
+}
621
+
622
+
623
+static DWORD
624
+msica_op_tap_interface_set_name_exec(
625
+    _Inout_ const struct msica_op_guid_string *op,
626
+    _Inout_ struct msica_session              *session)
627
+{
628
+    if (op == NULL || session == NULL)
629
+        return ERROR_BAD_ARGUMENTS;
630
+
631
+    {
632
+        /* Report the GUID of the interface to installer. */
633
+        MSIHANDLE hRecord = MsiCreateRecord(3);
634
+        LPOLESTR szInterfaceId = NULL;
635
+        StringFromIID((REFIID)&op->value_guid, &szInterfaceId);
636
+        MsiRecordSetString(hRecord, 1, TEXT("Setting interface name"));
637
+        MsiRecordSetString(hRecord, 2, szInterfaceId);
638
+        MsiRecordSetString(hRecord, 3, op->value_str);
639
+        int iResult = MsiProcessMessage(session->hInstall, INSTALLMESSAGE_ACTIONDATA, hRecord);
640
+        CoTaskMemFree(szInterfaceId);
641
+        MsiCloseHandle(hRecord);
642
+        if (iResult == IDCANCEL)
643
+            return ERROR_INSTALL_USEREXIT;
644
+    }
645
+
646
+    /* Get available network interfaces. */
647
+    struct tap_interface_node *pInterfaceList = NULL;
648
+    DWORD dwResult = tap_list_interfaces(NULL, &pInterfaceList);
649
+    if (dwResult == ERROR_SUCCESS)
650
+    {
651
+        /* Does interface exist? */
652
+        for (struct tap_interface_node *pInterface = pInterfaceList; ; pInterface = pInterface->pNext)
653
+        {
654
+            if (pInterface == NULL)
655
+            {
656
+                /* Interface not found. */
657
+                LPOLESTR szInterfaceId = NULL;
658
+                StringFromIID((REFIID)&op->value_guid, &szInterfaceId);
659
+                msg(M_NONFATAL, "%s: %"PRIsLPOLESTR" interface not found", __FUNCTION__, szInterfaceId);
660
+                CoTaskMemFree(szInterfaceId);
661
+                dwResult = ERROR_FILE_NOT_FOUND;
662
+                break;
663
+            }
664
+            else if (memcmp(&op->value_guid, &pInterface->guid, sizeof(GUID)) == 0)
665
+            {
666
+                /* Interface found. */
667
+                for (struct tap_interface_node *pInterfaceOther = pInterfaceList; ; pInterfaceOther = pInterfaceOther->pNext)
668
+                {
669
+                    if (pInterfaceOther == NULL)
670
+                    {
671
+                        /* No other interface with a same name found. All clear to rename the interface. */
672
+                        dwResult = tap_set_interface_name(&pInterface->guid, op->value_str);
673
+                        if (dwResult == ERROR_SUCCESS)
674
+                        {
675
+                            if (session->rollback_enabled)
676
+                            {
677
+                                /* Order rollback action to rename it back. */
678
+                                msica_op_seq_add_head(
679
+                                    &session->seq_cleanup[MSICA_CLEANUP_ACTION_ROLLBACK],
680
+                                    msica_op_create_guid_string(
681
+                                        msica_op_tap_interface_set_name,
682
+                                        0,
683
+                                        NULL,
684
+                                        &pInterface->guid,
685
+                                        pInterface->szName));
686
+                            }
687
+                        }
688
+                        break;
689
+                    }
690
+                    else if (_tcsicmp(op->value_str, pInterfaceOther->szName) == 0)
691
+                    {
692
+                        /* Interface with a same name found. Duplicate interface names are not allowed. */
693
+                        msg(M_NONFATAL, "%s: Interface with name \"%"PRIsLPTSTR"\" already exists", __FUNCTION__, pInterfaceOther->szName);
694
+                        dwResult = ERROR_ALREADY_EXISTS;
695
+                        break;
696
+                    }
697
+                }
698
+                break;
699
+            }
700
+        }
701
+
702
+        tap_free_interface_list(pInterfaceList);
703
+    }
704
+
705
+    return dwResult;
706
+}
707
+
708
+
709
+static DWORD
710
+msica_op_file_delete_exec(
711
+    _Inout_ const struct msica_op_string *op,
712
+    _Inout_ struct msica_session         *session)
713
+{
714
+    if (op == NULL || session == NULL)
715
+        return ERROR_BAD_ARGUMENTS;
716
+
717
+    {
718
+        /* Report the name of the file to installer. */
719
+        MSIHANDLE hRecord = MsiCreateRecord(3);
720
+        MsiRecordSetString(hRecord, 1, TEXT("Deleting file"));
721
+        MsiRecordSetString(hRecord, 2, op->value);
722
+        int iResult = MsiProcessMessage(session->hInstall, INSTALLMESSAGE_ACTIONDATA, hRecord);
723
+        MsiCloseHandle(hRecord);
724
+        if (iResult == IDCANCEL)
725
+            return ERROR_INSTALL_USEREXIT;
726
+    }
727
+
728
+    DWORD dwResult;
729
+
730
+    if (session->rollback_enabled)
731
+    {
732
+        size_t sizeNameBackupLenZ = _tcslen(op->value) + 7/*" (orig "*/ + 10/*maximum int*/ + 1/*")"*/ + 1/*terminator*/;
733
+        LPTSTR szNameBackup = (LPTSTR)malloc(sizeNameBackupLenZ * sizeof(TCHAR));
734
+        int count = 0;
735
+
736
+        do {
737
+            /* Rename the file to make a backup. */
738
+            _stprintf_s(
739
+                szNameBackup, sizeNameBackupLenZ,
740
+                TEXT("%s (orig %i)"),
741
+                op->value,
742
+                ++count);
743
+            dwResult = MoveFile(op->value, szNameBackup) ? ERROR_SUCCESS : GetLastError();
744
+        } while (dwResult == ERROR_ALREADY_EXISTS);
745
+
746
+        if (dwResult == ERROR_SUCCESS)
747
+        {
748
+            /* Schedule rollback action to restore from backup. */
749
+            msica_op_seq_add_head(
750
+                &session->seq_cleanup[MSICA_CLEANUP_ACTION_ROLLBACK],
751
+                msica_op_create_multistring(
752
+                    msica_op_file_move,
753
+                    0,
754
+                    NULL,
755
+                    szNameBackup,
756
+                    op->value,
757
+                    NULL));
758
+
759
+            /* Schedule commit action to delete the backup. */
760
+            msica_op_seq_add_tail(
761
+                &session->seq_cleanup[MSICA_CLEANUP_ACTION_COMMIT],
762
+                msica_op_create_string(
763
+                    msica_op_file_delete,
764
+                    0,
765
+                    NULL,
766
+                    szNameBackup));
767
+        }
768
+        else if (dwResult == ERROR_FILE_NOT_FOUND) /* File does not exist: We already got what we wanted! */
769
+            dwResult = ERROR_SUCCESS;
770
+        else
771
+            msg(M_NONFATAL | M_ERRNO, "%s: MoveFile(\"%"PRIsLPTSTR"\", \"%"PRIsLPTSTR"\") failed", __FUNCTION__, op->value, szNameBackup);
772
+
773
+        free(szNameBackup);
774
+    }
775
+    else
776
+    {
777
+        /* Delete the file. */
778
+        dwResult = DeleteFile(op->value) ? ERROR_SUCCESS : GetLastError();
779
+        if (dwResult == ERROR_FILE_NOT_FOUND) /* File does not exist: We already got what we wanted! */
780
+            dwResult = ERROR_SUCCESS;
781
+        else if (dwResult != ERROR_SUCCESS)
782
+            msg(M_NONFATAL | M_ERRNO, "%s: DeleteFile(\"%"PRIsLPTSTR"\") failed", __FUNCTION__, op->value);
783
+    }
784
+
785
+    return dwResult;
786
+}
787
+
788
+
789
+static DWORD
790
+msica_op_file_move_exec(
791
+    _Inout_ const struct msica_op_multistring *op,
792
+    _Inout_ struct msica_session              *session)
793
+{
794
+    if (op == NULL || session == NULL)
795
+        return ERROR_BAD_ARGUMENTS;
796
+
797
+    /* Get source filename. */
798
+    LPCTSTR szNameSrc = op->value;
799
+    if (szNameSrc[0] == 0)
800
+        return ERROR_BAD_ARGUMENTS;
801
+
802
+    /* Get destination filename. */
803
+    LPCTSTR szNameDst = szNameSrc + _tcslen(szNameSrc) + 1;
804
+    if (szNameDst[0] == 0)
805
+        return ERROR_BAD_ARGUMENTS;
806
+
807
+    {
808
+        /* Report the name of the files to installer. */
809
+        MSIHANDLE hRecord = MsiCreateRecord(3);
810
+        MsiRecordSetString(hRecord, 1, TEXT("Moving file"));
811
+        MsiRecordSetString(hRecord, 2, szNameSrc);
812
+        MsiRecordSetString(hRecord, 3, szNameDst);
813
+        int iResult = MsiProcessMessage(session->hInstall, INSTALLMESSAGE_ACTIONDATA, hRecord);
814
+        MsiCloseHandle(hRecord);
815
+        if (iResult == IDCANCEL)
816
+            return ERROR_INSTALL_USEREXIT;
817
+    }
818
+
819
+    DWORD dwResult = MoveFile(szNameSrc, szNameDst) ? ERROR_SUCCESS : GetLastError();
820
+    if (dwResult == ERROR_SUCCESS) {
821
+        if (session->rollback_enabled) {
822
+            /* Order rollback action to move it back. */
823
+            msica_op_seq_add_head(
824
+                &session->seq_cleanup[MSICA_CLEANUP_ACTION_ROLLBACK],
825
+                msica_op_create_multistring(
826
+                    msica_op_file_move,
827
+                    0,
828
+                    NULL,
829
+                    szNameDst,
830
+                    szNameSrc,
831
+                    NULL));
832
+        }
833
+    }
834
+    else
835
+        msg(M_NONFATAL | M_ERRNO, "%s: MoveFile(\"%"PRIsLPTSTR"\", \"%"PRIsLPTSTR"\") failed", __FUNCTION__, szNameSrc, szNameDst);
836
+
837
+    return dwResult;
838
+}
839
+
840
+
841
+void
842
+openvpnmsica_session_init(
843
+    _Inout_ struct msica_session *session,
844
+    _In_    MSIHANDLE             hInstall,
845
+    _In_    bool                  continue_on_error,
846
+    _In_    bool                  rollback_enabled)
847
+{
848
+    session->hInstall          = hInstall;
849
+    session->continue_on_error = continue_on_error;
850
+    session->rollback_enabled  = rollback_enabled;
851
+    for (size_t i = 0; i < MSICA_CLEANUP_ACTION_COUNT; i++)
852
+        msica_op_seq_init(&session->seq_cleanup[i]);
853
+}
854
+
855
+
856
+DWORD
857
+msica_op_seq_process(
858
+    _Inout_ const struct msica_op_seq *seq,
859
+    _Inout_ struct msica_session      *session)
860
+{
861
+    DWORD dwResult;
862
+
863
+    if (seq == NULL || session == NULL)
864
+        return ERROR_BAD_ARGUMENTS;
865
+
866
+    /* Tell the installer to use explicit progress messages. */
867
+    MSIHANDLE hRecordProg = MsiCreateRecord(3);
868
+    MsiRecordSetInteger(hRecordProg, 1, 1);
869
+    MsiRecordSetInteger(hRecordProg, 2, 1);
870
+    MsiRecordSetInteger(hRecordProg, 3, 0);
871
+    MsiProcessMessage(session->hInstall, INSTALLMESSAGE_PROGRESS, hRecordProg);
872
+
873
+    /* Prepare hRecordProg for progress messages. */
874
+    MsiRecordSetInteger(hRecordProg, 1, 2);
875
+    MsiRecordSetInteger(hRecordProg, 3, 0);
876
+
877
+    for (const struct msica_op *op = seq->head; op; op = op->next)
878
+    {
879
+        switch (op->type)
880
+        {
881
+        case msica_op_rollback_enable:
882
+            session->rollback_enabled = ((const struct msica_op_bool*)op)->value;
883
+            dwResult = ERROR_SUCCESS;
884
+            break;
885
+
886
+        case msica_op_tap_interface_create:
887
+            dwResult = msica_op_tap_interface_create_exec((const struct msica_op_string*)op, session);
888
+            break;
889
+
890
+        case msica_op_tap_interface_delete_by_name:
891
+            dwResult = msica_op_tap_interface_delete_by_name_exec((const struct msica_op_string*)op, session);
892
+            break;
893
+
894
+        case msica_op_tap_interface_delete_by_guid:
895
+            dwResult = msica_op_tap_interface_delete_by_guid_exec((const struct msica_op_guid*)op, session);
896
+            break;
897
+
898
+        case msica_op_tap_interface_set_name:
899
+            dwResult = msica_op_tap_interface_set_name_exec((const struct msica_op_guid_string*)op, session);
900
+            break;
901
+
902
+        case msica_op_file_delete:
903
+            dwResult = msica_op_file_delete_exec((const struct msica_op_string*)op, session);
904
+            break;
905
+
906
+        case msica_op_file_move:
907
+            dwResult = msica_op_file_move_exec((const struct msica_op_multistring*)op, session);
908
+            break;
909
+
910
+        default:
911
+            msg(M_NONFATAL, "%s: Unknown operation type (%x)", __FUNCTION__, op->type);
912
+            dwResult = ERROR_FILE_NOT_FOUND;
913
+        }
914
+
915
+        if (!session->continue_on_error && dwResult != ERROR_SUCCESS) {
916
+            /* Operation failed. It should have sent error message to Installer. Therefore, just quit here. */
917
+            goto cleanup_hRecordProg;
918
+        }
919
+
920
+        /* Report progress and check for user cancellation. */
921
+        MsiRecordSetInteger(hRecordProg, 2, op->ticks);
922
+        if (MsiProcessMessage(session->hInstall, INSTALLMESSAGE_PROGRESS, hRecordProg) == IDCANCEL)
923
+        {
924
+            dwResult = ERROR_INSTALL_USEREXIT;
925
+            goto cleanup_hRecordProg;
926
+        }
927
+    }
928
+
929
+    dwResult = ERROR_SUCCESS;
930
+
931
+cleanup_hRecordProg:
932
+    MsiCloseHandle(hRecordProg);
933
+    return dwResult;
934
+}
0 935
new file mode 100644
... ...
@@ -0,0 +1,429 @@
0
+/*
1
+ *  openvpnmsica -- Custom Action DLL to provide OpenVPN-specific support to MSI packages
2
+ *
3
+ *  Copyright (C) 2018 Simon Rozman <simon@rozman.si>
4
+ *
5
+ *  This program is free software; you can redistribute it and/or modify
6
+ *  it under the terms of the GNU General Public License version 2
7
+ *  as published by the Free Software Foundation.
8
+ *
9
+ *  This program is distributed in the hope that it will be useful,
10
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
+ *  GNU General Public License for more details.
13
+ *
14
+ *  You should have received a copy of the GNU General Public License along
15
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
16
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
+ */
18
+
19
+#ifndef MSICA_OP_H
20
+#define MSICA_OP_H
21
+
22
+#include <windows.h>
23
+#include <msi.h>
24
+#include <stdarg.h>
25
+#include <stdbool.h>
26
+#include <tchar.h>
27
+#include "../tapctl/basic.h"
28
+
29
+#ifdef _MSC_VER
30
+#pragma warning(push)
31
+#pragma warning(disable: 4200) /* Using zero-sized arrays in struct/union. */
32
+#endif
33
+
34
+
35
+/**
36
+ * Operation type macros
37
+ */
38
+#define MSICA_MAKE_OP_TYPE(op, data)  (((op)<<4)|((data)&0xf))
39
+#define MSICA_OP_TYPE_OP(type)        ((unsigned int)(type)>>4)
40
+#define MSICA_OP_TYPE_DATA(type)      ((unsigned int)(type)&0xf)
41
+
42
+
43
+/**
44
+ * Operation types
45
+ */
46
+enum msica_op_type
47
+{
48
+    msica_op_rollback_enable              = MSICA_MAKE_OP_TYPE(0x1, 0x1),  /** Enable/disable rollback  | msica_op_bool */
49
+    msica_op_tap_interface_create         = MSICA_MAKE_OP_TYPE(0x2, 0x2),  /** Create TAP/TUN interface | msica_op_string */
50
+    msica_op_tap_interface_delete_by_name = MSICA_MAKE_OP_TYPE(0x3, 0x2),  /** Delete TAP/TUN interface | msica_op_string */
51
+    msica_op_tap_interface_delete_by_guid = MSICA_MAKE_OP_TYPE(0x3, 0x4),  /** Delete TAP/TUN interface | msica_op_guid */
52
+    msica_op_tap_interface_set_name       = MSICA_MAKE_OP_TYPE(0x4, 0x5),  /** Rename TAP/TUN interface | msica_op_guid_string */
53
+    msica_op_file_delete                  = MSICA_MAKE_OP_TYPE(0x5, 0x2),  /** Delete file              | msica_op_string */
54
+    msica_op_file_move                    = MSICA_MAKE_OP_TYPE(0x6, 0x3),  /** Move file                | msica_op_multistring (min 2 strings) */
55
+};
56
+
57
+
58
+/**
59
+ * Operation data
60
+ */
61
+struct msica_op
62
+{
63
+    enum msica_op_type type;  /** Operation type */
64
+    int ticks;                /** Number of ticks on the progress indicator this operation represents */
65
+    struct msica_op *next;    /** Pointer to the next operation in the sequence */
66
+};
67
+
68
+
69
+/**
70
+ * Operation sequence
71
+ */
72
+struct msica_op_seq
73
+{
74
+    struct msica_op *head;    /** Pointer to the first operation in the sequence */
75
+    struct msica_op *tail;    /** Pointer to the last operation in the sequence */
76
+};
77
+
78
+
79
+/**
80
+ * Initializes operation sequence
81
+ *
82
+ * @param seq           Pointer to uninitialized operation sequence
83
+ */
84
+void
85
+msica_op_seq_init(_Inout_ struct msica_op_seq *seq);
86
+
87
+
88
+/**
89
+ * Frees operation sequence
90
+ *
91
+ * @param seq           Pointer to operation sequence
92
+ */
93
+void
94
+msica_op_seq_free(_Inout_ struct msica_op_seq *seq);
95
+
96
+
97
+/**
98
+ * Operation data (bool, 0x1)
99
+ */
100
+struct msica_op_bool
101
+{
102
+    struct msica_op base;     /** Common operation data */
103
+    bool value;               /** Operation data boolean value */
104
+};
105
+
106
+
107
+/**
108
+ * Allocates and fills a new msica_op_bool operation
109
+ *
110
+ * @param type          Operation type
111
+ *
112
+ * @param ticks         Number of ticks on the progress indicator this operation represents
113
+ *
114
+ * @param next          Pointer to the next operation in the sequence
115
+ *
116
+ * @param value         Boolean value
117
+ *
118
+ * @return              A new msica_op_bool operation. Must be added to a sequence list or
119
+ *                      released using free() after use. The function returns a pointer to
120
+ *                      msica_op to reduce type-casting in code.
121
+ */
122
+struct msica_op*
123
+msica_op_create_bool(
124
+    _In_     enum msica_op_type  type,
125
+    _In_     int                 ticks,
126
+    _In_opt_ struct msica_op    *next,
127
+    _In_     bool                value);
128
+
129
+
130
+/**
131
+ * Operation data (string, 0x2)
132
+ */
133
+struct msica_op_string
134
+{
135
+    struct msica_op base;     /** Common operation data */
136
+    TCHAR value[];            /** Operation data string - the string must always be zero terminated. */
137
+};
138
+
139
+
140
+/**
141
+ * Allocates and fills a new msica_op_string operation
142
+ *
143
+ * @param type          Operation type
144
+ *
145
+ * @param ticks         Number of ticks on the progress indicator this operation represents
146
+ *
147
+ * @param next          Pointer to the next operation in the sequence
148
+ *
149
+ * @param value         String value
150
+ *
151
+ * @return              A new msica_op_string operation. Must be added to a sequence list or
152
+ *                      released using free() after use. The function returns a pointer to
153
+ *                      msica_op to reduce type-casting in code.
154
+ */
155
+struct msica_op*
156
+msica_op_create_string(
157
+    _In_     enum msica_op_type  type,
158
+    _In_     int                 ticks,
159
+    _In_opt_ struct msica_op    *next,
160
+    _In_z_   LPCTSTR             value);
161
+
162
+
163
+/**
164
+ * Operation data (multi-string, 0x3)
165
+ */
166
+struct msica_op_multistring
167
+{
168
+    struct msica_op base;     /** Common operation data */
169
+    TCHAR value[];            /** Operation data strings - each string must always be zero terminated. The last string must be double terminated. */
170
+};
171
+
172
+
173
+/**
174
+* Allocates and fills a new msica_op_multistring operation
175
+*
176
+* @param type          Operation type
177
+*
178
+* @param ticks         Number of ticks on the progress indicator this operation represents
179
+*
180
+* @param next          Pointer to the next operation in the sequence
181
+*
182
+* @param arglist       List of non-empty strings. The last string must be NULL.
183
+*
184
+* @return              A new msica_op_string operation. Must be added to a sequence list or
185
+*                      released using free() after use. The function returns a pointer to
186
+*                      msica_op to reduce type-casting in code.
187
+*/
188
+struct msica_op*
189
+msica_op_create_multistring_va(
190
+    _In_     enum msica_op_type  type,
191
+    _In_     int                 ticks,
192
+    _In_opt_ struct msica_op    *next,
193
+    _In_     va_list             arglist);
194
+
195
+
196
+/**
197
+ * Operation data (GUID, 0x4)
198
+ */
199
+struct msica_op_guid
200
+{
201
+    struct msica_op base;     /** Common operation data */
202
+    GUID value;               /** Operation data GUID */
203
+};
204
+
205
+
206
+/**
207
+ * Allocates and fills a new msica_op_guid operation
208
+ *
209
+ * @param type          Operation type
210
+ *
211
+ * @param ticks         Number of ticks on the progress indicator this operation represents
212
+ *
213
+ * @param next          Pointer to the next operation in the sequence
214
+ *
215
+ * @param value         Pointer to GUID value
216
+ *
217
+ * @return              A new msica_op_guid operation. Must be added to a sequence list or
218
+ *                      released using free() after use. The function returns a pointer to
219
+ *                      msica_op to reduce type-casting in code.
220
+ */
221
+struct msica_op*
222
+msica_op_create_guid(
223
+    _In_     enum msica_op_type  type,
224
+    _In_     int                 ticks,
225
+    _In_opt_ struct msica_op    *next,
226
+    _In_     const GUID         *value);
227
+
228
+
229
+/**
230
+ * Operation data (guid-string, 0x5)
231
+ */
232
+struct msica_op_guid_string
233
+{
234
+    struct msica_op base;     /** Common operation data */
235
+    GUID value_guid;          /** Operation data GUID */
236
+    TCHAR value_str[];        /** Operation data string - the string must always be zero terminated. */
237
+};
238
+
239
+
240
+/**
241
+ * Allocates and fills a new msica_op_guid_string operation
242
+ *
243
+ * @param type          Operation type
244
+ *
245
+ * @param ticks         Number of ticks on the progress indicator this operation represents
246
+ *
247
+ * @param next          Pointer to the next operation in the sequence
248
+ *
249
+ * @param value_guid    Pointer to GUID value
250
+ *
251
+ * @param value_str     String value
252
+ *
253
+ * @return              A new msica_op_guid_string operation. Must be added to a sequence
254
+ *                      list or released using free() after use. The function returns a
255
+ *                      pointer to msica_op to reduce type-casting in code.
256
+ */
257
+struct msica_op*
258
+msica_op_create_guid_string(
259
+    _In_     enum msica_op_type  type,
260
+    _In_     int                 ticks,
261
+    _In_opt_ struct msica_op    *next,
262
+    _In_     const GUID         *value_guid,
263
+    _In_z_   LPCTSTR             value_str);
264
+
265
+
266
+/**
267
+ * Allocates and fills a new msica_op_multistring operation. Strings must be non-empty. The
268
+ * last string passed as the input parameter must be NULL.
269
+ *
270
+ * @param type          Operation type
271
+ *
272
+ * @param ticks         Number of ticks on the progress indicator this operation represents
273
+ *
274
+ * @param next          Pointer to the next operation in the sequence
275
+ *
276
+ * @return              A new msica_op_string operation. Must be added to a sequence list or
277
+ *                      released using free() after use. The function returns a pointer to
278
+ *                      msica_op to reduce type-casting in code.
279
+ */
280
+static inline struct msica_op*
281
+msica_op_create_multistring(
282
+    _In_     enum msica_op_type  type,
283
+    _In_     int                 ticks,
284
+    _In_opt_ struct msica_op    *next,
285
+    ...)
286
+{
287
+    va_list arglist;
288
+    va_start(arglist, next);
289
+    struct msica_op *op = msica_op_create_multistring_va(type, ticks, next, arglist);
290
+    va_end(arglist);
291
+    return op;
292
+}
293
+
294
+
295
+/**
296
+ * Is operation sequence empty
297
+ *
298
+ * @param seq           Pointer to operation sequence
299
+ *
300
+ * @return true if empty; false otherwise
301
+ */
302
+static inline bool
303
+msica_op_seq_is_empty(_In_ const struct msica_op_seq *seq)
304
+{
305
+    return seq->head != NULL;
306
+}
307
+
308
+
309
+/**
310
+ * Inserts operation(s) to the beginning of the operation sequence
311
+ *
312
+ * @param seq           Pointer to operation sequence
313
+ *
314
+ * @param operation     Pointer to the operation to insert. All operations in the list are
315
+ *                      added until the list is terminated with msica_op.next field set to
316
+ *                      NULL. Operations must be allocated using malloc().
317
+ */
318
+void
319
+msica_op_seq_add_head(
320
+    _Inout_ struct msica_op_seq *seq,
321
+    _Inout_ struct msica_op     *operation);
322
+
323
+
324
+/**
325
+ * Appends operation(s) to the end of the operation sequence
326
+ *
327
+ * @param seq           Pointer to operation sequence
328
+ *
329
+ * @param operation     Pointer to the operation to append. All operations in the list are
330
+ *                      added until the list is terminated with msica_op.next field set to
331
+ *                      NULL. Operations must be allocated using malloc().
332
+ */
333
+void
334
+msica_op_seq_add_tail(
335
+    _Inout_ struct msica_op_seq *seq,
336
+    _Inout_ struct msica_op     *operation);
337
+
338
+
339
+/**
340
+ * Saves the operation sequence to the file
341
+ *
342
+ * @param seq           Pointer to operation sequence
343
+ *
344
+ * @param hFile         Handle of the file opened with GENERIC_WRITE access
345
+ *
346
+ * @return ERROR_SUCCESS on success; An error code otherwise
347
+ */
348
+DWORD
349
+msica_op_seq_save(
350
+    _In_ const struct msica_op_seq *seq,
351
+    _In_ HANDLE                     hFile);
352
+
353
+
354
+/**
355
+ * Loads the operation sequence from the file
356
+ *
357
+ * @param seq           Pointer to uninitialized or empty operation sequence
358
+ *
359
+ * @param hFile         Handle of the file opened with GENERIC_READ access
360
+ *
361
+ * @return ERROR_SUCCESS on success; An error code otherwise
362
+ */
363
+DWORD
364
+msica_op_seq_load(
365
+    _Inout_ struct msica_op_seq *seq,
366
+    _In_    HANDLE               hFile);
367
+
368
+
369
+/**
370
+* Execution session constants
371
+*/
372
+#define MSICA_CLEANUP_ACTION_COMMIT   0
373
+#define MSICA_CLEANUP_ACTION_ROLLBACK 1
374
+#define MSICA_CLEANUP_ACTION_COUNT    2
375
+
376
+
377
+/**
378
+* Execution session
379
+*/
380
+struct msica_session
381
+{
382
+    MSIHANDLE hInstall;           /** Installer handle */
383
+    bool continue_on_error;       /** Continue execution on operation error? */
384
+    bool rollback_enabled;        /** Is rollback enabled? */
385
+    struct msica_op_seq seq_cleanup[MSICA_CLEANUP_ACTION_COUNT]; /** Commit/Rollback action operation sequence */
386
+};
387
+
388
+
389
+/**
390
+ * Initializes execution session
391
+ *
392
+ * @param session       Pointer to an unitialized execution session
393
+ *
394
+ * @param hInstall      Installer handle
395
+ *
396
+ * @param continue_on_error  Continue execution on operation error?
397
+ *
398
+ * @param rollback_enabled  Is rollback enabled?
399
+ */
400
+void
401
+openvpnmsica_session_init(
402
+    _Inout_ struct msica_session *session,
403
+    _In_    MSIHANDLE             hInstall,
404
+    _In_    bool                  continue_on_error,
405
+    _In_    bool                  rollback_enabled);
406
+
407
+
408
+/**
409
+ * Executes all operations in sequence
410
+ *
411
+ * @param seq           Pointer to operation sequence
412
+ *
413
+ * @param session       MSI session. The execution updates its members, most notably
414
+ *                      rollback_enabled and fills cleanup sequences with commit/rollback
415
+ *                      operations.
416
+ *
417
+ * @return ERROR_SUCCESS on success; An error code otherwise
418
+ */
419
+DWORD
420
+msica_op_seq_process(
421
+    _Inout_ const struct msica_op_seq *seq,
422
+    _Inout_ struct msica_session      *session);
423
+
424
+#ifdef _MSC_VER
425
+#pragma warning(pop)
426
+#endif
427
+
428
+#endif
0 429
new file mode 100644
... ...
@@ -0,0 +1,205 @@
0
+/*
1
+ *  openvpnmsica -- Custom Action DLL to provide OpenVPN-specific support to MSI packages
2
+ *
3
+ *  Copyright (C) 2018 Simon Rozman <simon@rozman.si>
4
+ *
5
+ *  This program is free software; you can redistribute it and/or modify
6
+ *  it under the terms of the GNU General Public License version 2
7
+ *  as published by the Free Software Foundation.
8
+ *
9
+ *  This program is distributed in the hope that it will be useful,
10
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
+ *  GNU General Public License for more details.
13
+ *
14
+ *  You should have received a copy of the GNU General Public License along
15
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
16
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
+ */
18
+
19
+#ifdef HAVE_CONFIG_H
20
+#include <config.h>
21
+#elif defined(_MSC_VER)
22
+#include <config-msvc.h>
23
+#endif
24
+
25
+#include "msiex.h"
26
+#include "../tapctl/error.h"
27
+
28
+#include <windows.h>
29
+#include <malloc.h>
30
+#include <memory.h>
31
+#include <msiquery.h>
32
+#ifdef _MSC_VER
33
+#pragma comment(lib, "msi.lib")
34
+#endif
35
+
36
+
37
+UINT
38
+msi_get_string(
39
+    _In_   MSIHANDLE  hInstall,
40
+    _In_z_ LPCTSTR    szName,
41
+    _Out_  LPTSTR    *pszValue)
42
+{
43
+    if (pszValue == NULL)
44
+        return ERROR_BAD_ARGUMENTS;
45
+
46
+    /* Try with stack buffer first. */
47
+    TCHAR szBufStack[128];
48
+    DWORD dwLength = _countof(szBufStack);
49
+    UINT uiResult = MsiGetProperty(hInstall, szName, szBufStack, &dwLength);
50
+    if (uiResult == ERROR_SUCCESS)
51
+    {
52
+        /* Copy from stack. */
53
+        *pszValue = (LPTSTR)malloc(++dwLength * sizeof(TCHAR));
54
+        memcpy(*pszValue, szBufStack, dwLength * sizeof(TCHAR));
55
+        return ERROR_SUCCESS;
56
+    }
57
+    else if (uiResult == ERROR_MORE_DATA)
58
+    {
59
+        /* Allocate on heap and retry. */
60
+        LPTSTR szBufHeap = (LPTSTR)malloc(++dwLength * sizeof(TCHAR));
61
+        uiResult = MsiGetProperty(hInstall, szName, szBufHeap, &dwLength);
62
+        if (uiResult == ERROR_SUCCESS)
63
+            *pszValue = szBufHeap;
64
+        else
65
+            free(szBufHeap);
66
+        return uiResult;
67
+    }
68
+    else
69
+    {
70
+        SetLastError(uiResult); /* MSDN does not mention MsiGetProperty() to set GetLastError(). But we do have an error code. Set last error manually. */
71
+        msg(M_NONFATAL | M_ERRNO, "%s: MsiGetProperty failed", __FUNCTION__);
72
+        return uiResult;
73
+    }
74
+}
75
+
76
+
77
+UINT
78
+msi_get_record_string(
79
+    _In_  MSIHANDLE     hRecord,
80
+    _In_  unsigned int  iField,
81
+    _Out_ LPTSTR       *pszValue)
82
+{
83
+    if (pszValue == NULL)
84
+        return ERROR_BAD_ARGUMENTS;
85
+
86
+    /* Try with stack buffer first. */
87
+    TCHAR szBufStack[128];
88
+    DWORD dwLength = _countof(szBufStack);
89
+    UINT uiResult = MsiRecordGetString(hRecord, iField, szBufStack, &dwLength);
90
+    if (uiResult == ERROR_SUCCESS)
91
+    {
92
+        /* Copy from stack. */
93
+        *pszValue = (LPTSTR)malloc(++dwLength * sizeof(TCHAR));
94
+        memcpy(*pszValue, szBufStack, dwLength * sizeof(TCHAR));
95
+        return ERROR_SUCCESS;
96
+    }
97
+    else if (uiResult == ERROR_MORE_DATA)
98
+    {
99
+        /* Allocate on heap and retry. */
100
+        LPTSTR szBufHeap = (LPTSTR)malloc(++dwLength * sizeof(TCHAR));
101
+        uiResult = MsiRecordGetString(hRecord, iField, szBufHeap, &dwLength);
102
+        if (uiResult == ERROR_SUCCESS)
103
+            *pszValue = szBufHeap;
104
+        else
105
+            free(szBufHeap);
106
+        return uiResult;
107
+    }
108
+    else
109
+    {
110
+        SetLastError(uiResult); /* MSDN does not mention MsiRecordGetString() to set GetLastError(). But we do have an error code. Set last error manually. */
111
+        msg(M_NONFATAL | M_ERRNO, "%s: MsiRecordGetString failed", __FUNCTION__);
112
+        return uiResult;
113
+    }
114
+}
115
+
116
+
117
+UINT
118
+msi_format_record(
119
+    _In_  MSIHANDLE  hInstall,
120
+    _In_  MSIHANDLE  hRecord,
121
+    _Out_ LPTSTR    *pszValue)
122
+{
123
+    if (pszValue == NULL)
124
+        return ERROR_BAD_ARGUMENTS;
125
+
126
+    /* Try with stack buffer first. */
127
+    TCHAR szBufStack[128];
128
+    DWORD dwLength = _countof(szBufStack);
129
+    UINT uiResult = MsiFormatRecord(hInstall, hRecord, szBufStack, &dwLength);
130
+    if (uiResult == ERROR_SUCCESS)
131
+    {
132
+        /* Copy from stack. */
133
+        *pszValue = (LPTSTR)malloc(++dwLength * sizeof(TCHAR));
134
+        memcpy(*pszValue, szBufStack, dwLength * sizeof(TCHAR));
135
+        return ERROR_SUCCESS;
136
+    }
137
+    else if (uiResult == ERROR_MORE_DATA)
138
+    {
139
+        /* Allocate on heap and retry. */
140
+        LPTSTR szBufHeap = (LPTSTR)malloc(++dwLength * sizeof(TCHAR));
141
+        uiResult = MsiFormatRecord(hInstall, hRecord, szBufHeap, &dwLength);
142
+        if (uiResult == ERROR_SUCCESS)
143
+            *pszValue = szBufHeap;
144
+        else
145
+            free(szBufHeap);
146
+        return uiResult;
147
+    }
148
+    else
149
+    {
150
+        SetLastError(uiResult); /* MSDN does not mention MsiFormatRecord() to set GetLastError(). But we do have an error code. Set last error manually. */
151
+        msg(M_NONFATAL | M_ERRNO, "%s: MsiFormatRecord failed", __FUNCTION__);
152
+        return uiResult;
153
+    }
154
+}
155
+
156
+
157
+UINT
158
+msi_format_field(
159
+    _In_  MSIHANDLE     hInstall,
160
+    _In_  MSIHANDLE     hRecord,
161
+    _In_  unsigned int  iField,
162
+    _Out_ LPTSTR       *pszValue)
163
+{
164
+    if (pszValue == NULL)
165
+        return ERROR_BAD_ARGUMENTS;
166
+
167
+    /* Read string to format. */
168
+    LPTSTR szValue = NULL;
169
+    UINT uiResult = msi_get_record_string(hRecord, iField, &szValue);
170
+    if (uiResult != ERROR_SUCCESS) return uiResult;
171
+    if (szValue[0] == 0)
172
+    {
173
+        /* The string is empty. There's nothing left to do. */
174
+        *pszValue = szValue;
175
+        return ERROR_SUCCESS;
176
+    }
177
+
178
+    /* Create a temporary record. */
179
+    MSIHANDLE hRecordEx = MsiCreateRecord(1);
180
+    if (!hRecordEx)
181
+    {
182
+        uiResult = ERROR_INVALID_HANDLE;
183
+        msg(M_NONFATAL, "%s: MsiCreateRecord failed", __FUNCTION__);
184
+        goto cleanup_szValue;
185
+    }
186
+
187
+    /* Populate the record with data. */
188
+    uiResult = MsiRecordSetString(hRecordEx, 0, szValue);
189
+    if (uiResult != ERROR_SUCCESS)
190
+    {
191
+        SetLastError(uiResult); /* MSDN does not mention MsiRecordSetString() to set GetLastError(). But we do have an error code. Set last error manually. */
192
+        msg(M_NONFATAL | M_ERRNO, "%s: MsiRecordSetString failed", __FUNCTION__);
193
+        goto cleanup_hRecordEx;
194
+    }
195
+
196
+    /* Do the formatting. */
197
+    uiResult = msi_format_record(hInstall, hRecordEx, pszValue);
198
+
199
+cleanup_hRecordEx:
200
+    MsiCloseHandle(hRecordEx);
201
+cleanup_szValue:
202
+    free(szValue);
203
+    return uiResult;
204
+}
0 205
new file mode 100644
... ...
@@ -0,0 +1,111 @@
0
+/*
1
+ *  openvpnmsica -- Custom Action DLL to provide OpenVPN-specific support to MSI packages
2
+ *
3
+ *  Copyright (C) 2018 Simon Rozman <simon@rozman.si>
4
+ *
5
+ *  This program is free software; you can redistribute it and/or modify
6
+ *  it under the terms of the GNU General Public License version 2
7
+ *  as published by the Free Software Foundation.
8
+ *
9
+ *  This program is distributed in the hope that it will be useful,
10
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
+ *  GNU General Public License for more details.
13
+ *
14
+ *  You should have received a copy of the GNU General Public License along
15
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
16
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
+ */
18
+
19
+#ifndef MSIHLP_H
20
+#define MSIHLP_H
21
+
22
+#include <windows.h>
23
+#include <msi.h>
24
+#include "../tapctl/basic.h"
25
+
26
+
27
+/**
28
+ * Gets MSI property value
29
+ *
30
+ * @param hInstall      Handle to the installation provided to the DLL custom action
31
+ *
32
+ * @param szName        Property name
33
+ *
34
+ * @param pszValue      Pointer to string to retrieve property value. The string must
35
+ *                      be released with free() after use.
36
+ *
37
+ * @return ERROR_SUCCESS on success; Win32 error code otherwise
38
+ */
39
+UINT
40
+msi_get_string(
41
+    _In_   MSIHANDLE  hInstall,
42
+    _In_z_ LPCTSTR    szName,
43
+    _Out_  LPTSTR    *pszValue);
44
+
45
+
46
+/**
47
+ * Gets MSI record string value
48
+ *
49
+ * @param hRecord       Handle to the record
50
+ *
51
+ * @param iField        Field index
52
+ *
53
+ * @param pszValue      Pointer to string to retrieve field value. The string must be
54
+ *                      released with free() after use.
55
+ *
56
+ * @return ERROR_SUCCESS on success; Win32 error code otherwise
57
+ */
58
+UINT
59
+msi_get_record_string(
60
+    _In_  MSIHANDLE     hRecord,
61
+    _In_  unsigned int  iField,
62
+    _Out_ LPTSTR       *pszValue);
63
+
64
+
65
+/**
66
+* Formats MSI record
67
+*
68
+* @param hInstall      Handle to the installation. This may be omitted, in which case only the
69
+*                      record field parameters are processed and properties are not available
70
+*                      for substitution.
71
+*
72
+* @param hRecord       Handle to the record to format. The template string must be stored in
73
+*                      record field 0 followed by referenced data parameters.
74
+*
75
+* @param pszValue      Pointer to string to retrieve formatted value. The string must be
76
+*                      released with free() after use.
77
+*
78
+* @return ERROR_SUCCESS on success; Win32 error code otherwise
79
+*/
80
+UINT
81
+msi_format_record(
82
+    _In_  MSIHANDLE  hInstall,
83
+    _In_  MSIHANDLE  hRecord,
84
+    _Out_ LPTSTR    *pszValue);
85
+
86
+
87
+/**
88
+* Formats MSI record field
89
+*
90
+* @param hInstall      Handle to the installation. This may be omitted, in which case only the
91
+*                      record field parameters are processed and properties are not available
92
+*                      for substitution.
93
+*
94
+* @param hRecord       Handle to the field record
95
+*
96
+* @param iField        Field index
97
+*
98
+* @param pszValue      Pointer to string to retrieve formatted value. The string must be
99
+*                      released with free() after use.
100
+*
101
+* @return ERROR_SUCCESS on success; Win32 error code otherwise
102
+*/
103
+UINT
104
+msi_format_field(
105
+    _In_  MSIHANDLE     hInstall,
106
+    _In_  MSIHANDLE     hRecord,
107
+    _In_  unsigned int  iField,
108
+    _Out_ LPTSTR       *pszValue);
109
+
110
+#endif
0 111
new file mode 100644
... ...
@@ -0,0 +1,14 @@
0
+<?xml version="1.0" encoding="utf-8"?>
1
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
2
+  <ImportGroup Label="PropertySheets">
3
+    <Import Project="openvpnmsica.props" />
4
+  </ImportGroup>
5
+  <PropertyGroup Label="UserMacros" />
6
+  <PropertyGroup />
7
+  <ItemDefinitionGroup>
8
+    <ClCompile>
9
+      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
10
+    </ClCompile>
11
+  </ItemDefinitionGroup>
12
+  <ItemGroup />
13
+</Project>
0 14
\ No newline at end of file
1 15
new file mode 100644
... ...
@@ -0,0 +1,14 @@
0
+<?xml version="1.0" encoding="utf-8"?>
1
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
2
+  <ImportGroup Label="PropertySheets">
3
+    <Import Project="openvpnmsica.props" />
4
+  </ImportGroup>
5
+  <PropertyGroup Label="UserMacros" />
6
+  <PropertyGroup />
7
+  <ItemDefinitionGroup>
8
+    <ClCompile>
9
+      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
10
+    </ClCompile>
11
+  </ItemDefinitionGroup>
12
+  <ItemGroup />
13
+</Project>
0 14
\ No newline at end of file
1 15
new file mode 100644
... ...
@@ -0,0 +1,668 @@
0
+/*
1
+ *  openvpnmsica -- Custom Action DLL to provide OpenVPN-specific support to MSI packages
2
+ *
3
+ *  Copyright (C) 2018 Simon Rozman <simon@rozman.si>
4
+ *
5
+ *  This program is free software; you can redistribute it and/or modify
6
+ *  it under the terms of the GNU General Public License version 2
7
+ *  as published by the Free Software Foundation.
8
+ *
9
+ *  This program is distributed in the hope that it will be useful,
10
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
+ *  GNU General Public License for more details.
13
+ *
14
+ *  You should have received a copy of the GNU General Public License along
15
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
16
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
+ */
18
+
19
+#ifdef HAVE_CONFIG_H
20
+#include <config.h>
21
+#elif defined(_MSC_VER)
22
+#include <config-msvc.h>
23
+#endif
24
+
25
+#include "openvpnmsica.h"
26
+#include "msica_op.h"
27
+#include "msiex.h"
28
+
29
+#include "../tapctl/basic.h"
30
+#include "../tapctl/error.h"
31
+#include "../tapctl/tap.h"
32
+
33
+#include <windows.h>
34
+#include <malloc.h>
35
+#include <memory.h>
36
+#include <msiquery.h>
37
+#include <shlwapi.h>
38
+#ifdef _MSC_VER
39
+#pragma comment(lib, "shlwapi.lib")
40
+#endif
41
+#include <stdbool.h>
42
+#include <stdlib.h>
43
+#include <tchar.h>
44
+
45
+
46
+/**
47
+ * Local constants
48
+ */
49
+
50
+#define MSICA_INTERFACE_TICK_SIZE (16*1024) /** Amount of tick space to reserve for one TAP/TUN interface creation/deletition. */
51
+
52
+
53
+/**
54
+ * Cleanup actions
55
+ */
56
+static const struct {
57
+    LPCTSTR szName;               /** Name of the cleanup action. This name is appended to the deferred custom action name (e.g. "InstallTAPInterfaces" >> "InstallTAPInterfacesCommit"). */
58
+    TCHAR szSuffix[3];            /** Two-character suffix to append to the cleanup operation sequence filename */
59
+} openvpnmsica_cleanup_action_seqs[MSICA_CLEANUP_ACTION_COUNT] =
60
+{
61
+    { TEXT("Commit"  ), TEXT("cm") }, /* MSICA_CLEANUP_ACTION_COMMIT   */
62
+    { TEXT("Rollback"), TEXT("rb") }, /* MSICA_CLEANUP_ACTION_ROLLBACK */
63
+};
64
+
65
+
66
+/**
67
+ * Creates a new sequence file in the current user's temporary folder and sets MSI property
68
+ * to its absolute path.
69
+ *
70
+ * @param hInstall      Handle to the installation provided to the DLL custom action
71
+ *
72
+ * @param szProperty    MSI property name to set to the absolute path of the sequence file.
73
+ *
74
+ * @param szFilename    String of minimum MAXPATH+1 characters where the zero-terminated
75
+ *                      file absolute path is stored.
76
+ *
77
+ * @return ERROR_SUCCESS on success; An error code otherwise
78
+ */
79
+static DWORD
80
+openvpnmsica_setup_sequence_filename(
81
+    _In_                     MSIHANDLE hInstall,
82
+    _In_z_                   LPCTSTR   szProperty,
83
+    _Out_z_cap_(MAXPATH + 1) LPTSTR    szFilename)
84
+{
85
+    DWORD dwResult;
86
+
87
+    if (szFilename == NULL)
88
+        return ERROR_BAD_ARGUMENTS;
89
+
90
+    /* Generate a random filename in the temporary folder. */
91
+    if (GetTempPath(MAX_PATH + 1, szFilename) == 0)
92
+    {
93
+        dwResult = GetLastError();
94
+        msg(M_NONFATAL | M_ERRNO, "%s: GetTempPath failed", __FUNCTION__);
95
+        return dwResult;
96
+    }
97
+    if (GetTempFileName(szFilename, szProperty, 0, szFilename) == 0)
98
+    {
99
+        dwResult = GetLastError();
100
+        msg(M_NONFATAL | M_ERRNO, "%s: GetTempFileName failed", __FUNCTION__);
101
+        return dwResult;
102
+    }
103
+
104
+    /* Store sequence filename to property for deferred custom action. */
105
+    dwResult = MsiSetProperty(hInstall, szProperty, szFilename);
106
+    if (dwResult != ERROR_SUCCESS)
107
+    {
108
+        SetLastError(dwResult); /* MSDN does not mention MsiSetProperty() to set GetLastError(). But we do have an error code. Set last error manually. */
109
+        msg(M_NONFATAL | M_ERRNO, "%s: MsiSetProperty(\"%"PRIsLPTSTR"\") failed", __FUNCTION__, szProperty);
110
+        return dwResult;
111
+    }
112
+
113
+    /* Generate and store cleanup operation sequence filenames to properties. */
114
+    LPTSTR szExtension = PathFindExtension(szFilename);
115
+    TCHAR szFilenameEx[MAX_PATH + 1/*dash*/ + 2/*suffix*/ + 1/*terminator*/];
116
+    size_t len_property_name = _tcslen(szProperty);
117
+    for (size_t i = 0; i < MSICA_CLEANUP_ACTION_COUNT; i++)
118
+    {
119
+        size_t len_action_name_z = _tcslen(openvpnmsica_cleanup_action_seqs[i].szName) + 1;
120
+        TCHAR *szPropertyEx = (TCHAR*)malloc((len_property_name + len_action_name_z) * sizeof(TCHAR));
121
+        memcpy(szPropertyEx                    , szProperty                         , len_property_name * sizeof(TCHAR));
122
+        memcpy(szPropertyEx + len_property_name, openvpnmsica_cleanup_action_seqs[i].szName, len_action_name_z * sizeof(TCHAR));
123
+        _stprintf_s(
124
+            szFilenameEx, _countof(szFilenameEx),
125
+            TEXT("%.*s-%.2s%s"),
126
+            (int)(szExtension - szFilename), szFilename,
127
+            openvpnmsica_cleanup_action_seqs[i].szSuffix,
128
+            szExtension);
129
+        dwResult = MsiSetProperty(hInstall, szPropertyEx, szFilenameEx);
130
+        if (dwResult != ERROR_SUCCESS)
131
+        {
132
+            SetLastError(dwResult); /* MSDN does not mention MsiSetProperty() to set GetLastError(). But we do have an error code. Set last error manually. */
133
+            msg(M_NONFATAL | M_ERRNO, "%s: MsiSetProperty(\"%"PRIsLPTSTR"\") failed", __FUNCTION__, szPropertyEx);
134
+            free(szPropertyEx);
135
+            return dwResult;
136
+        }
137
+        free(szPropertyEx);
138
+    }
139
+
140
+    return ERROR_SUCCESS;
141
+}
142
+
143
+
144
+UINT __stdcall
145
+FindTAPInterfaces(_In_ MSIHANDLE hInstall)
146
+{
147
+#ifdef _DEBUG
148
+    MessageBox(NULL, TEXT("Attach debugger!"), TEXT(__FUNCTION__) TEXT(" v")  TEXT(PACKAGE_VERSION), MB_OK);
149
+#endif
150
+
151
+    UINT uiResult;
152
+    BOOL bIsCoInitialized = SUCCEEDED(CoInitialize(NULL));
153
+
154
+    /* Set MSI session handle in TLS. */
155
+    struct openvpnmsica_tls_data *s = (struct openvpnmsica_tls_data *)TlsGetValue(openvpnmsica_tlsidx_session);
156
+    s->hInstall = hInstall;
157
+
158
+    /* Get available network interfaces. */
159
+    struct tap_interface_node *pInterfaceList = NULL;
160
+    uiResult = tap_list_interfaces(NULL, &pInterfaceList);
161
+    if (uiResult != ERROR_SUCCESS)
162
+        goto cleanup_CoInitialize;
163
+
164
+    /* Enumerate interfaces. */
165
+    struct interface_node
166
+    {
167
+        const struct tap_interface_node *iface;
168
+        struct interface_node *next;
169
+    } *interfaces_head = NULL, *interfaces_tail = NULL;
170
+    size_t interface_count = 0;
171
+    MSIHANDLE hRecord = MsiCreateRecord(1);
172
+    for (struct tap_interface_node *pInterface = pInterfaceList; pInterface; pInterface = pInterface->pNext)
173
+    {
174
+        for (LPCTSTR hwid = pInterface->szzHardwareIDs; hwid[0]; hwid += _tcslen(hwid) + 1)
175
+        {
176
+            if (_tcsicmp(hwid, TEXT(TAP_WIN_COMPONENT_ID)) == 0 ||
177
+                _tcsicmp(hwid, TEXT("root\\") TEXT(TAP_WIN_COMPONENT_ID)) == 0)
178
+            {
179
+                /* TAP interface found. */
180
+
181
+                /* Report the GUID of the interface to installer. */
182
+                LPOLESTR szInterfaceId = NULL;
183
+                StringFromIID((REFIID)&pInterface->guid, &szInterfaceId);
184
+                MsiRecordSetString(hRecord, 1, szInterfaceId);
185
+                MsiProcessMessage(hInstall, INSTALLMESSAGE_ACTIONDATA, hRecord);
186
+                CoTaskMemFree(szInterfaceId);
187
+
188
+                /* Append interface to the list. */
189
+                struct interface_node *node = (struct interface_node*)malloc(sizeof(struct interface_node));
190
+                node->iface = pInterface;
191
+                node->next = NULL;
192
+                if (interfaces_head)
193
+                    interfaces_tail = interfaces_tail->next = node;
194
+                else
195
+                    interfaces_head = interfaces_tail = node;
196
+                interface_count++;
197
+                break;
198
+            }
199
+        }
200
+    }
201
+    MsiCloseHandle(hRecord);
202
+
203
+    if (interface_count)
204
+    {
205
+        /* Prepare semicolon delimited list of TAP interface ID(s). */
206
+        LPTSTR
207
+            szTAPInterfaces = (LPTSTR)malloc(interface_count * (38/*GUID*/ + 1/*separator/terminator*/) * sizeof(TCHAR)),
208
+            szTAPInterfacesTail = szTAPInterfaces;
209
+        while (interfaces_head)
210
+        {
211
+            LPOLESTR szInterfaceId = NULL;
212
+            StringFromIID((REFIID)&interfaces_head->iface->guid, &szInterfaceId);
213
+            memcpy(szTAPInterfacesTail, szInterfaceId, 38 * sizeof(TCHAR));
214
+            szTAPInterfacesTail += 38;
215
+            CoTaskMemFree(szInterfaceId);
216
+            szTAPInterfacesTail[0] = interfaces_head->next ? TEXT(';') : 0;
217
+            szTAPInterfacesTail++;
218
+
219
+            struct interface_node *p = interfaces_head;
220
+            interfaces_head = interfaces_head->next;
221
+            free(p);
222
+        }
223
+
224
+        /* Set Installer TAPINTERFACES property. */
225
+        uiResult = MsiSetProperty(hInstall, TEXT("TAPINTERFACES"), szTAPInterfaces);
226
+        if (uiResult != ERROR_SUCCESS)
227
+        {
228
+            SetLastError(uiResult); /* MSDN does not mention MsiSetProperty() to set GetLastError(). But we do have an error code. Set last error manually. */
229
+            msg(M_NONFATAL | M_ERRNO, "%s: MsiSetProperty(\"TAPINTERFACES\") failed", __FUNCTION__);
230
+            goto cleanup_szTAPInterfaces;
231
+        }
232
+
233
+    cleanup_szTAPInterfaces:
234
+        free(szTAPInterfaces);
235
+    }
236
+    else
237
+        uiResult = ERROR_SUCCESS;
238
+
239
+    tap_free_interface_list(pInterfaceList);
240
+cleanup_CoInitialize:
241
+    if (bIsCoInitialized) CoUninitialize();
242
+    return uiResult;
243
+}
244
+
245
+
246
+UINT __stdcall
247
+EvaluateTAPInterfaces(_In_ MSIHANDLE hInstall)
248
+{
249
+#ifdef _DEBUG
250
+    MessageBox(NULL, TEXT("Attach debugger!"), TEXT(__FUNCTION__) TEXT(" v")  TEXT(PACKAGE_VERSION), MB_OK);
251
+#endif
252
+
253
+    UINT uiResult;
254
+    BOOL bIsCoInitialized = SUCCEEDED(CoInitialize(NULL));
255
+
256
+    /* Set MSI session handle in TLS. */
257
+    struct openvpnmsica_tls_data *s = (struct openvpnmsica_tls_data *)TlsGetValue(openvpnmsica_tlsidx_session);
258
+    s->hInstall = hInstall;
259
+
260
+    /* List of deferred custom actions EvaluateTAPInterfaces prepares operation sequence for. */
261
+    static const LPCTSTR szActionNames[] =
262
+    {
263
+        TEXT("InstallTAPInterfaces"),
264
+        TEXT("UninstallTAPInterfaces"),
265
+    };
266
+    struct msica_op_seq exec_seq[_countof(szActionNames)];
267
+    for (size_t i = 0; i < _countof(szActionNames); i++)
268
+        msica_op_seq_init(&exec_seq[i]);
269
+
270
+    {
271
+        /* Check and store the rollback enabled state. */
272
+        TCHAR szValue[128];
273
+        DWORD dwLength = _countof(szValue);
274
+        bool enable_rollback = MsiGetProperty(hInstall, TEXT("RollbackDisabled"), szValue, &dwLength) == ERROR_SUCCESS ?
275
+            _ttoi(szValue) || _totlower(szValue[0]) == TEXT('y') ? false : true :
276
+            true;
277
+        for (size_t i = 0; i < _countof(szActionNames); i++)
278
+            msica_op_seq_add_tail(
279
+                &exec_seq[i],
280
+                msica_op_create_bool(
281
+                    msica_op_rollback_enable,
282
+                    0,
283
+                    NULL,
284
+                    enable_rollback));
285
+    }
286
+
287
+    /* Open MSI database. */
288
+    MSIHANDLE hDatabase = MsiGetActiveDatabase(hInstall);
289
+    if (hDatabase == 0)
290
+    {
291
+        msg(M_NONFATAL, "%s: MsiGetActiveDatabase failed", __FUNCTION__);
292
+        uiResult = ERROR_INVALID_HANDLE; goto cleanup_exec_seq;
293
+    }
294
+
295
+    /* Check if TAPInterface table exists. If it doesn't exist, there's nothing to do. */
296
+    switch (MsiDatabaseIsTablePersistent(hDatabase, TEXT("TAPInterface")))
297
+    {
298
+    case MSICONDITION_FALSE:
299
+    case MSICONDITION_TRUE : break;
300
+    default:
301
+        uiResult = ERROR_SUCCESS;
302
+        goto cleanup_hDatabase;
303
+    }
304
+
305
+    /* Prepare a query to get a list/view of interfaces. */
306
+    MSIHANDLE hViewST = 0;
307
+    LPCTSTR szQuery = TEXT("SELECT `Interface`,`DisplayName`,`Condition`,`Component_` FROM `TAPInterface`");
308
+    uiResult = MsiDatabaseOpenView(hDatabase, szQuery, &hViewST);
309
+    if (uiResult != ERROR_SUCCESS)
310
+    {
311
+        SetLastError(uiResult); /* MSDN does not mention MsiDatabaseOpenView() to set GetLastError(). But we do have an error code. Set last error manually. */
312
+        msg(M_NONFATAL | M_ERRNO, "%s: MsiDatabaseOpenView(\"%"PRIsLPTSTR"\") failed", __FUNCTION__, szQuery);
313
+        goto cleanup_hDatabase;
314
+    }
315
+
316
+    /* Execute query! */
317
+    uiResult = MsiViewExecute(hViewST, 0);
318
+    if (uiResult != ERROR_SUCCESS)
319
+    {
320
+        SetLastError(uiResult); /* MSDN does not mention MsiViewExecute() to set GetLastError(). But we do have an error code. Set last error manually. */
321
+        msg(M_NONFATAL | M_ERRNO, "%s: MsiViewExecute(\"%"PRIsLPTSTR"\") failed", __FUNCTION__, szQuery);
322
+        goto cleanup_hViewST;
323
+    }
324
+
325
+    /* Create a record to report progress with. */
326
+    MSIHANDLE hRecordProg = MsiCreateRecord(2);
327
+    if (!hRecordProg)
328
+    {
329
+        uiResult = ERROR_INVALID_HANDLE;
330
+        msg(M_NONFATAL, "%s: MsiCreateRecord failed", __FUNCTION__);
331
+        goto cleanup_hViewST_close;
332
+    }
333
+
334
+    for (;;)
335
+    {
336
+        /* Fetch one record from the view. */
337
+        MSIHANDLE hRecord = 0;
338
+        uiResult = MsiViewFetch(hViewST, &hRecord);
339
+        if (uiResult == ERROR_NO_MORE_ITEMS) {
340
+            uiResult = ERROR_SUCCESS;
341
+            break;
342
+        }
343
+        else if (uiResult != ERROR_SUCCESS)
344
+        {
345
+            SetLastError(uiResult); /* MSDN does not mention MsiViewFetch() to set GetLastError(). But we do have an error code. Set last error manually. */
346
+            msg(M_NONFATAL | M_ERRNO, "%s: MsiViewFetch failed", __FUNCTION__);
347
+            goto cleanup_hRecordProg;
348
+        }
349
+
350
+        INSTALLSTATE iInstalled, iAction;
351
+        {
352
+            /* Read interface component ID (`Component_` is field #4). */
353
+            LPTSTR szValue = NULL;
354
+            uiResult = msi_get_record_string(hRecord, 4, &szValue);
355
+            if (uiResult != ERROR_SUCCESS) goto cleanup_hRecord;
356
+
357
+            /* Get the component state. */
358
+            uiResult = MsiGetComponentState(hInstall, szValue, &iInstalled, &iAction);
359
+            if (uiResult != ERROR_SUCCESS)
360
+            {
361
+                SetLastError(uiResult); /* MSDN does not mention MsiGetComponentState() to set GetLastError(). But we do have an error code. Set last error manually. */
362
+                msg(M_NONFATAL | M_ERRNO, "%s: MsiGetComponentState(\"%"PRIsLPTSTR"\") failed", __FUNCTION__, szValue);
363
+                free(szValue);
364
+                goto cleanup_hRecord;
365
+            }
366
+            free(szValue);
367
+        }
368
+
369
+        /* Get interface display name (`DisplayName` is field #2). */
370
+        LPTSTR szDisplayName = NULL;
371
+        uiResult = msi_format_field(hInstall, hRecord, 2, &szDisplayName);
372
+        if (uiResult != ERROR_SUCCESS)
373
+            goto cleanup_hRecord;
374
+
375
+        if (iAction > INSTALLSTATE_BROKEN)
376
+        {
377
+            if (iAction >= INSTALLSTATE_LOCAL) {
378
+                /* Read and evaluate interface condition (`Condition` is field #3). */
379
+                LPTSTR szValue = NULL;
380
+                uiResult = msi_get_record_string(hRecord, 3, &szValue);
381
+                if (uiResult != ERROR_SUCCESS) goto cleanup_szDisplayName;
382
+#ifdef __GNUC__
383
+/*
384
+ * warning: enumeration value ‘MSICONDITION_TRUE’ not handled in switch
385
+ * warning: enumeration value ‘MSICONDITION_NONE’ not handled in switch
386
+ */
387
+#pragma GCC diagnostic push
388
+#pragma GCC diagnostic ignored "-Wswitch"
389
+#endif
390
+                switch (MsiEvaluateCondition(hInstall, szValue))
391
+                {
392
+                case MSICONDITION_FALSE:
393
+                    free(szValue);
394
+                    goto cleanup_szDisplayName;
395
+                case MSICONDITION_ERROR:
396
+                    uiResult = ERROR_INVALID_FIELD;
397
+                    msg(M_NONFATAL | M_ERRNO, "%s: MsiEvaluateCondition(\"%"PRIsLPTSTR"\") failed", __FUNCTION__, szValue);
398
+                    free(szValue);
399
+                    goto cleanup_szDisplayName;
400
+                }
401
+#ifdef __GNUC__
402
+#pragma GCC diagnostic pop
403
+#endif
404
+                free(szValue);
405
+
406
+                /* Component is or should be installed. Schedule interface creation. */
407
+                msica_op_seq_add_tail(
408
+                    &exec_seq[0],
409
+                    msica_op_create_string(
410
+                        msica_op_tap_interface_create,
411
+                        MSICA_INTERFACE_TICK_SIZE,
412
+                        NULL,
413
+                        szDisplayName));
414
+            }
415
+            else
416
+            {
417
+                /* Component is installed, but should be degraded to advertised/removed. Schedule interface deletition. */
418
+                msica_op_seq_add_tail(
419
+                    &exec_seq[1],
420
+                    msica_op_create_string(
421
+                        msica_op_tap_interface_delete_by_name,
422
+                        MSICA_INTERFACE_TICK_SIZE,
423
+                        NULL,
424
+                        szDisplayName));
425
+            }
426
+
427
+            /* The amount of tick space to add for each interface to progress indicator. */
428
+            MsiRecordSetInteger(hRecordProg, 1, 3 /* OP3 = Add ticks to the expected total number of progress of the progress bar */);
429
+            MsiRecordSetInteger(hRecordProg, 2, MSICA_INTERFACE_TICK_SIZE);
430
+            if (MsiProcessMessage(hInstall, INSTALLMESSAGE_PROGRESS, hRecordProg) == IDCANCEL)
431
+            {
432
+                uiResult = ERROR_INSTALL_USEREXIT;
433
+                goto cleanup_szDisplayName;
434
+            }
435
+        }
436
+
437
+    cleanup_szDisplayName:
438
+        free(szDisplayName);
439
+    cleanup_hRecord:
440
+        MsiCloseHandle(hRecord);
441
+        if (uiResult != ERROR_SUCCESS)
442
+            goto cleanup_hRecordProg;
443
+    }
444
+
445
+    /*
446
+    Write sequence files.
447
+    The InstallTAPInterfaces and UninstallTAPInterfaces are deferred custom actions, thus all this information
448
+    will be unavailable to them. Therefore save all required operations and their info to sequence files.
449
+    */
450
+    TCHAR szSeqFilename[_countof(szActionNames)][MAX_PATH + 1];
451
+    for (size_t i = 0; i < _countof(szActionNames); i++)
452
+        szSeqFilename[i][0] = 0;
453
+    for (size_t i = 0; i < _countof(szActionNames); i++)
454
+    {
455
+        uiResult = openvpnmsica_setup_sequence_filename(hInstall, szActionNames[i], szSeqFilename[i]);
456
+        if (uiResult != ERROR_SUCCESS)
457
+            goto cleanup_szSeqFilename;
458
+        HANDLE hSeqFile = CreateFile(
459
+            szSeqFilename[i],
460
+            GENERIC_WRITE,
461
+            FILE_SHARE_READ,
462
+            NULL,
463
+            CREATE_ALWAYS,
464
+            FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
465
+            NULL);
466
+        if (hSeqFile == INVALID_HANDLE_VALUE)
467
+        {
468
+            uiResult = GetLastError();
469
+            msg(M_NONFATAL | M_ERRNO, "%s: CreateFile(\"%.*"PRIsLPTSTR"\") failed", __FUNCTION__, _countof(szSeqFilename[i]), szSeqFilename[i]);
470
+            goto cleanup_szSeqFilename;
471
+        }
472
+        uiResult = msica_op_seq_save(&exec_seq[i], hSeqFile);
473
+        CloseHandle(hSeqFile);
474
+        if (uiResult != ERROR_SUCCESS)
475
+            goto cleanup_szSeqFilename;
476
+    }
477
+
478
+    uiResult = ERROR_SUCCESS;
479
+
480
+cleanup_szSeqFilename:
481
+    if (uiResult != ERROR_SUCCESS)
482
+    {
483
+        /* Clean-up sequence files. */
484
+        for (size_t i = _countof(szActionNames); i--;)
485
+            if (szSeqFilename[i][0])
486
+                DeleteFile(szSeqFilename[i]);
487
+    }
488
+cleanup_hRecordProg:
489
+    MsiCloseHandle(hRecordProg);
490
+cleanup_hViewST_close:
491
+    MsiViewClose(hViewST);
492
+cleanup_hViewST:
493
+    MsiCloseHandle(hViewST);
494
+cleanup_hDatabase:
495
+    MsiCloseHandle(hDatabase);
496
+cleanup_exec_seq:
497
+    for (size_t i = 0; i < _countof(szActionNames); i++)
498
+        msica_op_seq_free(&exec_seq[i]);
499
+    if (bIsCoInitialized) CoUninitialize();
500
+    return uiResult;
501
+}
502
+
503
+
504
+UINT __stdcall
505
+ProcessDeferredAction(_In_ MSIHANDLE hInstall)
506
+{
507
+#ifdef _DEBUG
508
+    MessageBox(NULL, TEXT("Attach debugger!"), TEXT(__FUNCTION__) TEXT(" v")  TEXT(PACKAGE_VERSION), MB_OK);
509
+#endif
510
+
511
+    UINT uiResult;
512
+    BOOL bIsCoInitialized = SUCCEEDED(CoInitialize(NULL));
513
+
514
+    /* Set MSI session handle in TLS. */
515
+    struct openvpnmsica_tls_data *s = (struct openvpnmsica_tls_data *)TlsGetValue(openvpnmsica_tlsidx_session);
516
+    s->hInstall = hInstall;
517
+
518
+    BOOL bIsCleanup = MsiGetMode(hInstall, MSIRUNMODE_COMMIT) || MsiGetMode(hInstall, MSIRUNMODE_ROLLBACK);
519
+
520
+    /* Get sequence filename and open the file. */
521
+    LPTSTR szSeqFilename = NULL;
522
+    uiResult = msi_get_string(hInstall, TEXT("CustomActionData"), &szSeqFilename);
523
+    if (uiResult != ERROR_SUCCESS)
524
+        goto cleanup_CoInitialize;
525
+    struct msica_op_seq seq = { .head = NULL, .tail = NULL };
526
+    {
527
+        HANDLE hSeqFile = CreateFile(
528
+            szSeqFilename,
529
+            GENERIC_READ,
530
+            FILE_SHARE_READ,
531
+            NULL,
532
+            OPEN_EXISTING,
533
+            FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
534
+            NULL);
535
+        if (hSeqFile == INVALID_HANDLE_VALUE)
536
+        {
537
+            uiResult = GetLastError();
538
+            if (uiResult == ERROR_FILE_NOT_FOUND && bIsCleanup)
539
+            {
540
+                /*
541
+                Sequence file not found and this is rollback/commit action. Either of the following scenarios are possible:
542
+                - The delayed action failed to save the rollback/commit sequence to file. The delayed action performed cleanup itself. No further operation is required.
543
+                - Somebody removed the rollback/commit file between delayed action and rollback/commit action. No further operation is possible.
544
+                */
545
+                uiResult = ERROR_SUCCESS;
546
+                goto cleanup_szSeqFilename;
547
+            }
548
+            msg(M_NONFATAL | M_ERRNO, "%s: CreateFile(\"%"PRIsLPTSTR"\") failed", __FUNCTION__, szSeqFilename);
549
+            goto cleanup_szSeqFilename;
550
+        }
551
+
552
+        /* Load sequence. */
553
+        uiResult = msica_op_seq_load(&seq, hSeqFile);
554
+        CloseHandle(hSeqFile);
555
+        if (uiResult != ERROR_SUCCESS)
556
+            goto cleanup_seq;
557
+    }
558
+
559
+    /* Prepare session context. */
560
+    struct msica_session session;
561
+    openvpnmsica_session_init(
562
+        &session,
563
+        hInstall,
564
+        bIsCleanup, /* In case of commit/rollback, continue sequence on error, to do as much cleanup as possible. */
565
+        false);
566
+
567
+    /* Execute sequence. */
568
+    uiResult = msica_op_seq_process(&seq, &session);
569
+    if (!bIsCleanup)
570
+    {
571
+        /*
572
+        Save cleanup scripts of delayed action regardless of action's execution status.
573
+        Rollback action MUST be scheduled in InstallExecuteSequence before this action! Otherwise cleanup won't be performed in case this action execution failed.
574
+        */
575
+        DWORD dwResultEx; /* Don't overwrite uiResult. */
576
+        LPCTSTR szExtension = PathFindExtension(szSeqFilename);
577
+        TCHAR szFilenameEx[MAX_PATH + 1/*dash*/ + 2/*suffix*/ + 1/*terminator*/];
578
+        for (size_t i = 0; i < MSICA_CLEANUP_ACTION_COUNT; i++)
579
+        {
580
+            _stprintf_s(
581
+                szFilenameEx, _countof(szFilenameEx),
582
+                TEXT("%.*s-%.2s%s"),
583
+                (int)(szExtension - szSeqFilename), szSeqFilename,
584
+                openvpnmsica_cleanup_action_seqs[i].szSuffix,
585
+                szExtension);
586
+
587
+            /* After commit, delete rollback file. After rollback, delete commit file. */
588
+            msica_op_seq_add_tail(
589
+                &session.seq_cleanup[MSICA_CLEANUP_ACTION_COUNT - 1 - i],
590
+                msica_op_create_string(
591
+                    msica_op_file_delete,
592
+                    0,
593
+                    NULL,
594
+                    szFilenameEx));
595
+        }
596
+        for (size_t i = 0; i < MSICA_CLEANUP_ACTION_COUNT; i++)
597
+        {
598
+            _stprintf_s(
599
+                szFilenameEx, _countof(szFilenameEx),
600
+                TEXT("%.*s-%.2s%s"),
601
+                (int)(szExtension - szSeqFilename), szSeqFilename,
602
+                openvpnmsica_cleanup_action_seqs[i].szSuffix,
603
+                szExtension);
604
+
605
+            /* Save the cleanup sequence file. */
606
+            HANDLE hSeqFile = CreateFile(
607
+                szFilenameEx,
608
+                GENERIC_WRITE,
609
+                FILE_SHARE_READ,
610
+                NULL,
611
+                CREATE_ALWAYS,
612
+                FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
613
+                NULL);
614
+            if (hSeqFile == INVALID_HANDLE_VALUE)
615
+            {
616
+                dwResultEx = GetLastError();
617
+                msg(M_NONFATAL | M_ERRNO, "%s: CreateFile(\"%.*"PRIsLPTSTR"\") failed", __FUNCTION__, _countof(szFilenameEx), szFilenameEx);
618
+                goto cleanup_session;
619
+            }
620
+            dwResultEx = msica_op_seq_save(&session.seq_cleanup[i], hSeqFile);
621
+            CloseHandle(hSeqFile);
622
+            if (dwResultEx != ERROR_SUCCESS)
623
+                goto cleanup_session;
624
+        }
625
+
626
+    cleanup_session:
627
+        if (dwResultEx != ERROR_SUCCESS)
628
+        {
629
+            /* The commit and/or rollback scripts were not written to file successfully. Perform the cleanup immediately. */
630
+            struct msica_session session_cleanup;
631
+            openvpnmsica_session_init(
632
+                &session_cleanup,
633
+                hInstall,
634
+                true,
635
+                false);
636
+            msica_op_seq_process(&session.seq_cleanup[MSICA_CLEANUP_ACTION_ROLLBACK], &session_cleanup);
637
+
638
+            szExtension = PathFindExtension(szSeqFilename);
639
+            for (size_t i = 0; i < MSICA_CLEANUP_ACTION_COUNT; i++)
640
+            {
641
+                _stprintf_s(
642
+                    szFilenameEx, _countof(szFilenameEx),
643
+                    TEXT("%.*s-%.2s%s"),
644
+                    (int)(szExtension - szSeqFilename), szSeqFilename,
645
+                    openvpnmsica_cleanup_action_seqs[i].szSuffix,
646
+                    szExtension);
647
+                DeleteFile(szFilenameEx);
648
+            }
649
+        }
650
+    }
651
+    else
652
+    {
653
+        /* No cleanup after cleanup support. */
654
+        uiResult = ERROR_SUCCESS;
655
+    }
656
+
657
+    for (size_t i = MSICA_CLEANUP_ACTION_COUNT; i--;)
658
+        msica_op_seq_free(&session.seq_cleanup[i]);
659
+    DeleteFile(szSeqFilename);
660
+cleanup_seq:
661
+    msica_op_seq_free(&seq);
662
+cleanup_szSeqFilename:
663
+    free(szSeqFilename);
664
+cleanup_CoInitialize:
665
+    if (bIsCoInitialized) CoUninitialize();
666
+    return uiResult;
667
+}
0 668
new file mode 100644
... ...
@@ -0,0 +1,99 @@
0
+/*
1
+ *  openvpnmsica -- Custom Action DLL to provide OpenVPN-specific support to MSI packages
2
+ *
3
+ *  Copyright (C) 2018 Simon Rozman <simon@rozman.si>
4
+ *
5
+ *  This program is free software; you can redistribute it and/or modify
6
+ *  it under the terms of the GNU General Public License version 2
7
+ *  as published by the Free Software Foundation.
8
+ *
9
+ *  This program is distributed in the hope that it will be useful,
10
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
+ *  GNU General Public License for more details.
13
+ *
14
+ *  You should have received a copy of the GNU General Public License along
15
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
16
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
+ */
18
+
19
+#ifndef MSICA_H
20
+#define MSICA_H
21
+
22
+#include <windows.h>
23
+#include <msi.h>
24
+#include "../tapctl/basic.h"
25
+
26
+
27
+ /*
28
+  * Error codes (next unused 2552L)
29
+  */
30
+#define ERROR_MSICA       2550L
31
+#define ERROR_MSICA_ERRNO 2551L
32
+
33
+
34
+/**
35
+ * TLS data
36
+ */
37
+struct openvpnmsica_tls_data
38
+{
39
+    MSIHANDLE hInstall; /** Handle to the installation session. */
40
+};
41
+
42
+
43
+/**
44
+ * MSI session handle TLS index
45
+ */
46
+extern DWORD openvpnmsica_tlsidx_session;
47
+
48
+
49
+/*
50
+ * Exported DLL Functions
51
+ */
52
+
53
+#ifdef __cplusplus
54
+extern "C" {
55
+#endif
56
+
57
+/**
58
+ * Find existing TAP interfaces and set TAPINTERFACES property with semicolon delimited list
59
+ * of installed TAP interface GUIDs.
60
+ *
61
+ * @param hInstall      Handle to the installation provided to the DLL custom action
62
+ *
63
+ * @return ERROR_SUCCESS on success; An error code otherwise
64
+ *         See: https://msdn.microsoft.com/en-us/library/windows/desktop/aa368072.aspx
65
+ */
66
+__declspec(dllexport) UINT __stdcall
67
+FindTAPInterfaces(_In_ MSIHANDLE hInstall);
68
+
69
+
70
+/**
71
+ * Evaluate the TAPInterface table of the MSI package database and prepare a list of TAP
72
+ * interfaces to install/remove.
73
+ *
74
+ * @param hInstall      Handle to the installation provided to the DLL custom action
75
+ *
76
+ * @return ERROR_SUCCESS on success; An error code otherwise
77
+ *         See: https://msdn.microsoft.com/en-us/library/windows/desktop/aa368072.aspx
78
+ */
79
+__declspec(dllexport) UINT __stdcall
80
+EvaluateTAPInterfaces(_In_ MSIHANDLE hInstall);
81
+
82
+
83
+/**
84
+ * Perform scheduled deferred action.
85
+ *
86
+ * @param hInstall      Handle to the installation provided to the DLL custom action
87
+ *
88
+ * @return ERROR_SUCCESS on success; An error code otherwise
89
+ *         See: https://msdn.microsoft.com/en-us/library/windows/desktop/aa368072.aspx
90
+ */
91
+__declspec(dllexport) UINT __stdcall
92
+ProcessDeferredAction(_In_ MSIHANDLE hInstall);
93
+
94
+#ifdef __cplusplus
95
+}
96
+#endif
97
+
98
+#endif
0 99
new file mode 100644
... ...
@@ -0,0 +1,15 @@
0
+<?xml version="1.0" encoding="utf-8"?>
1
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
2
+  <ImportGroup Label="PropertySheets" />
3
+  <PropertyGroup Label="UserMacros" />
4
+  <PropertyGroup />
5
+  <ItemDefinitionGroup>
6
+    <ClCompile>
7
+      <AdditionalIncludeDirectories>..\compat;$(TAP_WINDOWS_HOME)/include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
8
+    </ClCompile>
9
+    <Link>
10
+      <SubSystem>Windows</SubSystem>
11
+    </Link>
12
+  </ItemDefinitionGroup>
13
+  <ItemGroup />
14
+</Project>
0 15
\ No newline at end of file
1 16
new file mode 100644
... ...
@@ -0,0 +1,142 @@
0
+<?xml version="1.0" encoding="utf-8"?>
1
+<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
2
+  <ItemGroup Label="ProjectConfigurations">
3
+    <ProjectConfiguration Include="Debug|ARM64">
4
+      <Configuration>Debug</Configuration>
5
+      <Platform>ARM64</Platform>
6
+    </ProjectConfiguration>
7
+    <ProjectConfiguration Include="Debug|Win32">
8
+      <Configuration>Debug</Configuration>
9
+      <Platform>Win32</Platform>
10
+    </ProjectConfiguration>
11
+    <ProjectConfiguration Include="Debug|x64">
12
+      <Configuration>Debug</Configuration>
13
+      <Platform>x64</Platform>
14
+    </ProjectConfiguration>
15
+    <ProjectConfiguration Include="Release|ARM64">
16
+      <Configuration>Release</Configuration>
17
+      <Platform>ARM64</Platform>
18
+    </ProjectConfiguration>
19
+    <ProjectConfiguration Include="Release|Win32">
20
+      <Configuration>Release</Configuration>
21
+      <Platform>Win32</Platform>
22
+    </ProjectConfiguration>
23
+    <ProjectConfiguration Include="Release|x64">
24
+      <Configuration>Release</Configuration>
25
+      <Platform>x64</Platform>
26
+    </ProjectConfiguration>
27
+  </ItemGroup>
28
+  <PropertyGroup Label="Globals">
29
+    <VCProjectVersion>15.0</VCProjectVersion>
30
+    <ProjectGuid>{D41AA9D6-B818-476E-992E-0E16EB86BEE2}</ProjectGuid>
31
+    <Keyword>Win32Proj</Keyword>
32
+    <RootNamespace>openvpnmsica</RootNamespace>
33
+    <WindowsTargetPlatformVersion>10.0.17134.0</WindowsTargetPlatformVersion>
34
+  </PropertyGroup>
35
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
36
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="Configuration">
37
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
38
+    <UseDebugLibraries>true</UseDebugLibraries>
39
+    <PlatformToolset>v141</PlatformToolset>
40
+    <CharacterSet>Unicode</CharacterSet>
41
+    <WindowsSDKDesktopARM64Support>true</WindowsSDKDesktopARM64Support>
42
+  </PropertyGroup>
43
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
44
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
45
+    <UseDebugLibraries>true</UseDebugLibraries>
46
+    <PlatformToolset>v141</PlatformToolset>
47
+    <CharacterSet>Unicode</CharacterSet>
48
+  </PropertyGroup>
49
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
50
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
51
+    <UseDebugLibraries>true</UseDebugLibraries>
52
+    <PlatformToolset>v141</PlatformToolset>
53
+    <CharacterSet>Unicode</CharacterSet>
54
+  </PropertyGroup>
55
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="Configuration">
56
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
57
+    <UseDebugLibraries>false</UseDebugLibraries>
58
+    <PlatformToolset>v141</PlatformToolset>
59
+    <WholeProgramOptimization>true</WholeProgramOptimization>
60
+    <CharacterSet>Unicode</CharacterSet>
61
+    <WindowsSDKDesktopARM64Support>true</WindowsSDKDesktopARM64Support>
62
+  </PropertyGroup>
63
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
64
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
65
+    <UseDebugLibraries>false</UseDebugLibraries>
66
+    <PlatformToolset>v141</PlatformToolset>
67
+    <WholeProgramOptimization>true</WholeProgramOptimization>
68
+    <CharacterSet>Unicode</CharacterSet>
69
+  </PropertyGroup>
70
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
71
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
72
+    <UseDebugLibraries>false</UseDebugLibraries>
73
+    <PlatformToolset>v141</PlatformToolset>
74
+    <WholeProgramOptimization>true</WholeProgramOptimization>
75
+    <CharacterSet>Unicode</CharacterSet>
76
+  </PropertyGroup>
77
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
78
+  <ImportGroup Label="ExtensionSettings">
79
+  </ImportGroup>
80
+  <ImportGroup Label="Shared">
81
+  </ImportGroup>
82
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
83
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
84
+    <Import Project="..\compat\Debug.props" />
85
+    <Import Project="openvpnmsica-Debug.props" />
86
+  </ImportGroup>
87
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
88
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
89
+    <Import Project="..\compat\Debug.props" />
90
+    <Import Project="openvpnmsica-Debug.props" />
91
+  </ImportGroup>
92
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
93
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
94
+    <Import Project="..\compat\Debug.props" />
95
+    <Import Project="openvpnmsica-Debug.props" />
96
+  </ImportGroup>
97
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
98
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
99
+    <Import Project="..\compat\Release.props" />
100
+    <Import Project="openvpnmsica-Release.props" />
101
+  </ImportGroup>
102
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
103
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
104
+    <Import Project="..\compat\Release.props" />
105
+    <Import Project="openvpnmsica-Release.props" />
106
+  </ImportGroup>
107
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
108
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
109
+    <Import Project="..\compat\Release.props" />
110
+    <Import Project="openvpnmsica-Release.props" />
111
+  </ImportGroup>
112
+  <PropertyGroup Label="UserMacros" />
113
+  <ItemGroup>
114
+    <ClCompile Include="..\tapctl\error.c" />
115
+    <ClCompile Include="..\tapctl\tap.c" />
116
+    <ClCompile Include="dllmain.c" />
117
+    <ClCompile Include="msiex.c" />
118
+    <ClCompile Include="msica_op.c" />
119
+    <ClCompile Include="openvpnmsica.c" />
120
+  </ItemGroup>
121
+  <ItemGroup>
122
+    <ClInclude Include="..\tapctl\basic.h" />
123
+    <ClInclude Include="..\tapctl\error.h" />
124
+    <ClInclude Include="..\tapctl\tap.h" />
125
+    <ClInclude Include="msiex.h" />
126
+    <ClInclude Include="msica_op.h" />
127
+    <ClInclude Include="openvpnmsica.h" />
128
+  </ItemGroup>
129
+  <ItemGroup>
130
+    <ResourceCompile Include="openvpnmsica_resources.rc" />
131
+  </ItemGroup>
132
+  <ItemGroup>
133
+    <ProjectReference Include="..\..\build\msvc\msvc-generate\msvc-generate.vcxproj">
134
+      <Project>{8598c2c8-34c4-47a1-99b0-7c295a890615}</Project>
135
+      <ReferenceOutputAssembly>false</ReferenceOutputAssembly>
136
+    </ProjectReference>
137
+  </ItemGroup>
138
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
139
+  <ImportGroup Label="ExtensionTargets">
140
+  </ImportGroup>
141
+</Project>
0 142
\ No newline at end of file
1 143
new file mode 100644
... ...
@@ -0,0 +1,62 @@
0
+<?xml version="1.0" encoding="utf-8"?>
1
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
2
+  <ItemGroup>
3
+    <Filter Include="Source Files">
4
+      <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
5
+      <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
6
+    </Filter>
7
+    <Filter Include="Header Files">
8
+      <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
9
+      <Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
10
+    </Filter>
11
+    <Filter Include="Resource Files">
12
+      <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
13
+      <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
14
+    </Filter>
15
+  </ItemGroup>
16
+  <ItemGroup>
17
+    <ClCompile Include="dllmain.c">
18
+      <Filter>Source Files</Filter>
19
+    </ClCompile>
20
+    <ClCompile Include="..\tapctl\error.c">
21
+      <Filter>Source Files</Filter>
22
+    </ClCompile>
23
+    <ClCompile Include="msiex.c">
24
+      <Filter>Source Files</Filter>
25
+    </ClCompile>
26
+    <ClCompile Include="openvpnmsica.c">
27
+      <Filter>Source Files</Filter>
28
+    </ClCompile>
29
+    <ClCompile Include="msica_op.c">
30
+      <Filter>Source Files</Filter>
31
+    </ClCompile>
32
+    <ClCompile Include="..\tapctl\tap.c">
33
+      <Filter>Source Files</Filter>
34
+    </ClCompile>
35
+  </ItemGroup>
36
+  <ItemGroup>
37
+    <ClInclude Include="openvpnmsica.h">
38
+      <Filter>Header Files</Filter>
39
+    </ClInclude>
40
+    <ClInclude Include="msiex.h">
41
+      <Filter>Header Files</Filter>
42
+    </ClInclude>
43
+    <ClInclude Include="msica_op.h">
44
+      <Filter>Header Files</Filter>
45
+    </ClInclude>
46
+    <ClInclude Include="..\tapctl\tap.h">
47
+      <Filter>Header Files</Filter>
48
+    </ClInclude>
49
+    <ClInclude Include="..\tapctl\error.h">
50
+      <Filter>Header Files</Filter>
51
+    </ClInclude>
52
+    <ClInclude Include="..\tapctl\basic.h">
53
+      <Filter>Header Files</Filter>
54
+    </ClInclude>
55
+  </ItemGroup>
56
+  <ItemGroup>
57
+    <ResourceCompile Include="openvpnmsica_resources.rc">
58
+      <Filter>Resource Files</Filter>
59
+    </ResourceCompile>
60
+  </ItemGroup>
61
+</Project>
0 62
\ No newline at end of file
1 63
new file mode 100644
... ...
@@ -0,0 +1,62 @@
0
+/*
1
+ *  openvpnmsica -- Custom Action DLL to provide OpenVPN-specific support to MSI packages
2
+ *
3
+ *  Copyright (C) 2018 Simon Rozman <simon@rozman.si>
4
+ *
5
+ *  This program is free software; you can redistribute it and/or modify
6
+ *  it under the terms of the GNU General Public License version 2
7
+ *  as published by the Free Software Foundation.
8
+ *
9
+ *  This program is distributed in the hope that it will be useful,
10
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
+ *  GNU General Public License for more details.
13
+ *
14
+ *  You should have received a copy of the GNU General Public License along
15
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
16
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
+ */
18
+
19
+#ifdef HAVE_CONFIG_H
20
+#include <config.h>
21
+#else
22
+#include <config-msvc-version.h>
23
+#endif
24
+#include <winresrc.h>
25
+
26
+#pragma code_page(65001) /* UTF8 */
27
+
28
+LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
29
+
30
+VS_VERSION_INFO VERSIONINFO
31
+    FILEVERSION OPENVPN_VERSION_RESOURCE
32
+    PRODUCTVERSION OPENVPN_VERSION_RESOURCE
33
+    FILEFLAGSMASK VS_FF_DEBUG | VS_FF_PRERELEASE | VS_FF_PATCHED | VS_FF_PRIVATEBUILD | VS_FF_SPECIALBUILD
34
+#ifdef _DEBUG
35
+    FILEFLAGS VS_FF_DEBUG
36
+#else
37
+    FILEFLAGS 0x0L
38
+#endif
39
+    FILEOS VOS_NT_WINDOWS32
40
+    FILETYPE VFT_DLL
41
+    FILESUBTYPE 0x0L
42
+BEGIN
43
+    BLOCK "StringFileInfo"
44
+    BEGIN
45
+        BLOCK "040904b0"
46
+        BEGIN
47
+            VALUE "CompanyName", "The OpenVPN Project"
48
+            VALUE "FileDescription", "Custom Action DLL to provide OpenVPN-specific support to MSI packages"
49
+            VALUE "FileVersion", PACKAGE_VERSION ".0"
50
+            VALUE "InternalName", "OpenVPN"
51
+            VALUE "LegalCopyright", "Copyright © The OpenVPN Project"
52
+            VALUE "OriginalFilename", "openvpnmsica.dll"
53
+            VALUE "ProductName", "OpenVPN"
54
+            VALUE "ProductVersion", PACKAGE_VERSION ".0"
55
+        END
56
+    END
57
+    BLOCK "VarFileInfo"
58
+    BEGIN
59
+        VALUE "Translation", 0x409, 1200
60
+    END
61
+END
0 62
new file mode 100644
... ...
@@ -0,0 +1,51 @@
0
+#
1
+#  tapctl -- Utility to manipulate TUN/TAP interfaces on Windows
2
+#
3
+#  Copyright (C) 2002-2018 OpenVPN Inc <sales@openvpn.net>
4
+#  Copyright (C) 2018 Simon Rozman <simon@rozman.si>
5
+#
6
+#  This program is free software; you can redistribute it and/or modify
7
+#  it under the terms of the GNU General Public License version 2
8
+#  as published by the Free Software Foundation.
9
+#
10
+#  This program is distributed in the hope that it will be useful,
11
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
+#  GNU General Public License for more details.
14
+#
15
+#  You should have received a copy of the GNU General Public License along
16
+#  with this program; if not, write to the Free Software Foundation, Inc.,
17
+#  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18
+#
19
+
20
+include $(top_srcdir)/build/ltrc.inc
21
+
22
+MAINTAINERCLEANFILES = $(srcdir)/Makefile.in
23
+
24
+EXTRA_DIST = \
25
+	tapctl.vcxproj \
26
+	tapctl.vcxproj.filters \
27
+	tapctl.props \
28
+	tapctl.exe.manifest
29
+
30
+AM_CPPFLAGS = \
31
+	-I$(top_srcdir)/include -I$(top_srcdir)/src/compat
32
+
33
+AM_CFLAGS = \
34
+	$(TAP_CFLAGS)
35
+
36
+if WIN32
37
+sbin_PROGRAMS = tapctl
38
+tapctl_CFLAGS = \
39
+	-municode -D_UNICODE \
40
+	-UNTDDI_VERSION -U_WIN32_WINNT \
41
+	-D_WIN32_WINNT=_WIN32_WINNT_VISTA
42
+tapctl_LDADD = -ladvapi32 -lole32 -lsetupapi
43
+endif
44
+
45
+tapctl_SOURCES = \
46
+	basic.h \
47
+	error.c error.h \
48
+	main.c \
49
+	tap.c tap.h \
50
+	tapctl_resources.rc
0 51
new file mode 100644
... ...
@@ -0,0 +1,54 @@
0
+/*
1
+ *  basic -- Basic macros
2
+ *
3
+ *  Copyright (C) 2002-2018 OpenVPN Inc <sales@openvpn.net>
4
+ *  Copyright (C) 2018 Simon Rozman <simon@rozman.si>
5
+ *
6
+ *  This program is free software; you can redistribute it and/or modify
7
+ *  it under the terms of the GNU General Public License version 2
8
+ *  as published by the Free Software Foundation.
9
+ *
10
+ *  This program is distributed in the hope that it will be useful,
11
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
+ *  GNU General Public License for more details.
14
+ *
15
+ *  You should have received a copy of the GNU General Public License along
16
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
17
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18
+ */
19
+
20
+#ifndef BASIC_H
21
+#define BASIC_H
22
+
23
+#ifdef _UNICODE
24
+#define PRIsLPTSTR "ls"
25
+#define PRIsLPOLESTR  "ls"
26
+#else
27
+#define PRIsLPTSTR "s"
28
+#define PRIsLPOLESTR  "ls"
29
+#endif
30
+
31
+#ifndef _In_
32
+#define _In_
33
+#endif
34
+#ifndef _In_opt_
35
+#define _In_opt_
36
+#endif
37
+#ifndef _In_z_
38
+#define _In_z_
39
+#endif
40
+#ifndef _Inout_
41
+#define _Inout_
42
+#endif
43
+#ifndef _Out_
44
+#define _Out_
45
+#endif
46
+#ifndef _Out_opt_
47
+#define _Out_opt_
48
+#endif
49
+#ifndef _Out_z_cap_
50
+#define _Out_z_cap_(n)
51
+#endif
52
+
53
+#endif
0 54
new file mode 100644
... ...
@@ -0,0 +1,35 @@
0
+/*
1
+ *  error -- OpenVPN compatible error reporting API
2
+ *
3
+ *  Copyright (C) 2002-2018 OpenVPN Inc <sales@openvpn.net>
4
+ *  Copyright (C) 2018 Simon Rozman <simon@rozman.si>
5
+ *
6
+ *  This program is free software; you can redistribute it and/or modify
7
+ *  it under the terms of the GNU General Public License version 2
8
+ *  as published by the Free Software Foundation.
9
+ *
10
+ *  This program is distributed in the hope that it will be useful,
11
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
+ *  GNU General Public License for more details.
14
+ *
15
+ *  You should have received a copy of the GNU General Public License along
16
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
17
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18
+ */
19
+
20
+#include "error.h"
21
+
22
+
23
+/* Globals */
24
+unsigned int x_debug_level; /* GLOBAL */
25
+
26
+
27
+void
28
+x_msg(const unsigned int flags, const char *format, ...)
29
+{
30
+    va_list arglist;
31
+    va_start(arglist, format);
32
+    x_msg_va(flags, format, arglist);
33
+    va_end(arglist);
34
+}
0 35
new file mode 100644
... ...
@@ -0,0 +1,95 @@
0
+/*
1
+ *  error -- OpenVPN compatible error reporting API
2
+ *
3
+ *  Copyright (C) 2002-2018 OpenVPN Inc <sales@openvpn.net>
4
+ *  Copyright (C) 2018 Simon Rozman <simon@rozman.si>
5
+ *
6
+ *  This program is free software; you can redistribute it and/or modify
7
+ *  it under the terms of the GNU General Public License version 2
8
+ *  as published by the Free Software Foundation.
9
+ *
10
+ *  This program is distributed in the hope that it will be useful,
11
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
+ *  GNU General Public License for more details.
14
+ *
15
+ *  You should have received a copy of the GNU General Public License along
16
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
17
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18
+ */
19
+
20
+#ifndef ERROR_H
21
+#define ERROR_H
22
+
23
+#include <stdarg.h>
24
+#include <stdbool.h>
25
+#include <stdlib.h>
26
+
27
+/*
28
+ * These globals should not be accessed directly,
29
+ * but rather through macros or inline functions defined below.
30
+ */
31
+extern unsigned int x_debug_level;
32
+extern int x_msg_line_num;
33
+
34
+/* msg() flags */
35
+
36
+#define M_DEBUG_LEVEL     (0x0F)         /* debug level mask */
37
+
38
+#define M_FATAL           (1<<4)         /* exit program */
39
+#define M_NONFATAL        (1<<5)         /* non-fatal error */
40
+#define M_WARN            (1<<6)         /* call syslog with LOG_WARNING */
41
+#define M_DEBUG           (1<<7)
42
+
43
+#define M_ERRNO           (1<<8)         /* show errno description */
44
+
45
+#define M_NOMUTE          (1<<11)        /* don't do mute processing */
46
+#define M_NOPREFIX        (1<<12)        /* don't show date/time prefix */
47
+#define M_USAGE_SMALL     (1<<13)        /* fatal options error, call usage_small */
48
+#define M_MSG_VIRT_OUT    (1<<14)        /* output message through msg_status_output callback */
49
+#define M_OPTERR          (1<<15)        /* print "Options error:" prefix */
50
+#define M_NOLF            (1<<16)        /* don't print new line */
51
+#define M_NOIPREFIX       (1<<17)        /* don't print instance prefix */
52
+
53
+/* flag combinations which are frequently used */
54
+#define M_ERR     (M_FATAL | M_ERRNO)
55
+#define M_USAGE   (M_USAGE_SMALL | M_NOPREFIX | M_OPTERR)
56
+#define M_CLIENT  (M_MSG_VIRT_OUT | M_NOMUTE | M_NOIPREFIX)
57
+
58
+
59
+/** Check muting filter */
60
+bool dont_mute(unsigned int flags);
61
+
62
+/* Macro to ensure (and teach static analysis tools) we exit on fatal errors */
63
+#ifdef _MSC_VER
64
+#pragma warning(disable: 4127) /* EXIT_FATAL(flags) macro raises "warning C4127: conditional expression is constant" on each non M_FATAL invocation. */
65
+#endif
66
+#define EXIT_FATAL(flags) do { if ((flags) & M_FATAL) {_exit(1);}} while (false)
67
+
68
+#define HAVE_VARARG_MACROS
69
+#define msg(flags, ...) do { if (msg_test(flags)) {x_msg((flags), __VA_ARGS__);} EXIT_FATAL(flags); } while (false)
70
+#ifdef ENABLE_DEBUG
71
+#define dmsg(flags, ...) do { if (msg_test(flags)) {x_msg((flags), __VA_ARGS__);} EXIT_FATAL(flags); } while (false)
72
+#else
73
+#define dmsg(flags, ...)
74
+#endif
75
+
76
+void x_msg(const unsigned int flags, const char *format, ...);     /* should be called via msg above */
77
+void x_msg_va(const unsigned int flags, const char *format, va_list arglist);
78
+
79
+/* Inline functions */
80
+
81
+static inline bool
82
+check_debug_level(unsigned int level)
83
+{
84
+    return (level & M_DEBUG_LEVEL) <= x_debug_level;
85
+}
86
+
87
+/** Return true if flags represent and enabled, not muted log level */
88
+static inline bool
89
+msg_test(unsigned int flags)
90
+{
91
+    return check_debug_level(flags) && dont_mute(flags);
92
+}
93
+
94
+#endif
0 95
new file mode 100644
... ...
@@ -0,0 +1,385 @@
0
+/*
1
+ *  tapctl -- Utility to manipulate TUN/TAP interfaces on Windows
2
+ *
3
+ *  Copyright (C) 2002-2018 OpenVPN Inc <sales@openvpn.net>
4
+ *  Copyright (C) 2008-2013 David Sommerseth <dazo@users.sourceforge.net>
5
+ *  Copyright (C) 2018 Simon Rozman <simon@rozman.si>
6
+ *
7
+ *  This program is free software; you can redistribute it and/or modify
8
+ *  it under the terms of the GNU General Public License version 2
9
+ *  as published by the Free Software Foundation.
10
+ *
11
+ *  This program is distributed in the hope that it will be useful,
12
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ *  GNU General Public License for more details.
15
+ *
16
+ *  You should have received a copy of the GNU General Public License along
17
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
18
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
+ */
20
+
21
+#ifdef HAVE_CONFIG_H
22
+#include <config.h>
23
+#elif defined(_MSC_VER)
24
+#include <config-msvc.h>
25
+#endif
26
+#ifdef HAVE_CONFIG_VERSION_H
27
+#include <config-version.h>
28
+#endif
29
+
30
+#include "tap.h"
31
+#include "error.h"
32
+
33
+#include <objbase.h>
34
+#include <setupapi.h>
35
+#include <stdio.h>
36
+#include <tchar.h>
37
+
38
+#ifdef _MSC_VER
39
+#pragma comment(lib, "ole32.lib")
40
+#pragma comment(lib, "setupapi.lib")
41
+#endif
42
+
43
+
44
+const TCHAR title_string[] =
45
+    TEXT(PACKAGE_NAME) TEXT(" ") TEXT(PACKAGE_VERSION)
46
+    TEXT(" built on ") TEXT(__DATE__)
47
+;
48
+
49
+static const TCHAR usage_message[] =
50
+    TEXT("%s\n")
51
+    TEXT("\n")
52
+    TEXT("Usage:\n")
53
+    TEXT("\n")
54
+    TEXT("tapctl <command> [<command specific options>]\n")
55
+    TEXT("\n")
56
+    TEXT("Commands:\n")
57
+    TEXT("\n")
58
+    TEXT("create     Create a new TUN/TAP interface\n")
59
+    TEXT("list       List network interfaces\n")
60
+    TEXT("delete     Delete specified network interface\n")
61
+    TEXT("help       Display this text\n")
62
+    TEXT("\n")
63
+    TEXT("Hint: Use \"tapctl help <command>\" to display help for particular command.\n")
64
+    ;
65
+
66
+static const TCHAR usage_message_create[] =
67
+    TEXT("%s\n")
68
+    TEXT("\n")
69
+    TEXT("Creates a new TUN/TAP interface\n")
70
+    TEXT("\n")
71
+    TEXT("Usage:\n")
72
+    TEXT("\n")
73
+    TEXT("tapctl create [<options>]\n")
74
+    TEXT("\n")
75
+    TEXT("Options:\n")
76
+    TEXT("\n")
77
+    TEXT("--name <name>  Set TUN/TAP interface name. Should the interface with given name\n")
78
+    TEXT("               already exist, an error is returned. If this option is not      \n")
79
+    TEXT("               specified, a default interface name is chosen by Windows.       \n")
80
+    TEXT("               Note: This name can also be specified as OpenVPN's --dev-node   \n")
81
+    TEXT("               option.                                                         \n")
82
+    TEXT("\n")
83
+    TEXT("Output:\n")
84
+    TEXT("\n")
85
+    TEXT("This command prints newly created TUN/TAP interface's GUID to stdout.          \n")
86
+    ;
87
+
88
+static const TCHAR usage_message_list[] =
89
+    TEXT("%s\n")
90
+    TEXT("\n")
91
+    TEXT("Lists network interfaces\n")
92
+    TEXT("\n")
93
+    TEXT("Usage:\n")
94
+    TEXT("\n")
95
+    TEXT("tapctl list\n")
96
+    TEXT("\n")
97
+    TEXT("Output:\n")
98
+    TEXT("\n")
99
+    TEXT("This command prints all network interfaces to stdout.                          \n")
100
+    ;
101
+
102
+static const TCHAR usage_message_delete[] =
103
+    TEXT("%s\n")
104
+    TEXT("\n")
105
+    TEXT("Deletes the specified network interface\n")
106
+    TEXT("\n")
107
+    TEXT("Usage:\n")
108
+    TEXT("\n")
109
+    TEXT("tapctl delete <interface GUID | interface name>\n")
110
+    ;
111
+
112
+
113
+/**
114
+ * Print the help message.
115
+ */
116
+static void
117
+usage(void)
118
+{
119
+    _ftprintf(stderr,
120
+        usage_message,
121
+        title_string);
122
+}
123
+
124
+
125
+/**
126
+ * Program entry point
127
+ */
128
+int __cdecl
129
+_tmain(int argc, LPCTSTR argv[])
130
+{
131
+    int iResult;
132
+    BOOL bRebootRequired = FALSE;
133
+
134
+    /* Ask SetupAPI to keep quiet. */
135
+    SetupSetNonInteractiveMode(TRUE);
136
+
137
+    if (argc < 2)
138
+    {
139
+        usage();
140
+        return 1;
141
+    }
142
+    else if (_tcsicmp(argv[1], TEXT("help")) == 0)
143
+    {
144
+        /* Output help. */
145
+        if (argc < 3)
146
+            usage();
147
+        else if (_tcsicmp(argv[2], TEXT("create")) == 0)
148
+            _ftprintf(stderr, usage_message_create, title_string);
149
+        else if (_tcsicmp(argv[2], TEXT("list")) == 0)
150
+            _ftprintf(stderr, usage_message_list, title_string);
151
+        else if (_tcsicmp(argv[2], TEXT("delete")) == 0)
152
+            _ftprintf(stderr, usage_message_delete, title_string);
153
+        else
154
+            _ftprintf(stderr, TEXT("Unknown command \"%s\". Please, use \"tapctl help\" to list supported commands.\n"), argv[2]);
155
+
156
+        return 1;
157
+    }
158
+    else if (_tcsicmp(argv[1], TEXT("create")) == 0)
159
+    {
160
+        LPCTSTR szName = NULL;
161
+
162
+        /* Parse options. */
163
+        for (int i = 2; i < argc; i++)
164
+        {
165
+            if (_tcsicmp(argv[i], TEXT("--name")) == 0)
166
+                szName = argv[++i];
167
+            else
168
+                _ftprintf(stderr, TEXT("Unknown option \"%s\". Please, use \"tapctl help create\" to list supported options. Ignored.\n"), argv[i]);
169
+        }
170
+
171
+        /* Create TUN/TAP interface. */
172
+        GUID guidInterface;
173
+        LPOLESTR szInterfaceId = NULL;
174
+        DWORD dwResult = tap_create_interface(
175
+            NULL,
176
+            TEXT("Virtual Ethernet"),
177
+            &bRebootRequired,
178
+            &guidInterface);
179
+        if (dwResult != ERROR_SUCCESS)
180
+        {
181
+            _ftprintf(stderr, TEXT("Creating TUN/TAP interface failed (error 0x%x).\n"), dwResult);
182
+            iResult = 1; goto quit;
183
+        }
184
+
185
+        if (szName)
186
+        {
187
+            /* Get the list of available interfaces. */
188
+            struct tap_interface_node *pInterfaceList = NULL;
189
+            dwResult = tap_list_interfaces(NULL, &pInterfaceList);
190
+            if (dwResult != ERROR_SUCCESS)
191
+            {
192
+                _ftprintf(stderr, TEXT("Enumerating interfaces failed (error 0x%x).\n"), dwResult);
193
+                iResult = 1; goto create_delete_interface;
194
+            }
195
+
196
+            /* Check for duplicates. */
197
+            for (struct tap_interface_node *pInterface = pInterfaceList; pInterface; pInterface = pInterface->pNext)
198
+            {
199
+                if (_tcsicmp(szName, pInterface->szName) == 0)
200
+                {
201
+                    StringFromIID((REFIID)&pInterface->guid, &szInterfaceId);
202
+                    _ftprintf(stderr, TEXT("Interface \"%s\" already exists (GUID %") TEXT(PRIsLPOLESTR) TEXT(").\n"), pInterface->szName, szInterfaceId);
203
+                    CoTaskMemFree(szInterfaceId);
204
+                    iResult = 1; goto create_cleanup_pInterfaceList;
205
+                }
206
+            }
207
+
208
+            /* Rename the interface. */
209
+            dwResult = tap_set_interface_name(&guidInterface, szName);
210
+            if (dwResult != ERROR_SUCCESS)
211
+            {
212
+                StringFromIID((REFIID)&guidInterface, &szInterfaceId);
213
+                _ftprintf(stderr, TEXT("Renaming TUN/TAP interface %") TEXT(PRIsLPOLESTR) TEXT(" to \"%s\" failed (error 0x%x).\n"), szInterfaceId, szName, dwResult);
214
+                CoTaskMemFree(szInterfaceId);
215
+                iResult = 1; goto quit;
216
+            }
217
+
218
+            iResult = 0;
219
+
220
+        create_cleanup_pInterfaceList:
221
+            tap_free_interface_list(pInterfaceList);
222
+            if (iResult)
223
+                goto create_delete_interface;
224
+        }
225
+
226
+        /* Output interface GUID. */
227
+        StringFromIID((REFIID)&guidInterface, &szInterfaceId);
228
+        _ftprintf(stdout, TEXT("%") TEXT(PRIsLPOLESTR) TEXT("\n"), szInterfaceId);
229
+        CoTaskMemFree(szInterfaceId);
230
+
231
+        iResult = 0; goto quit;
232
+
233
+    create_delete_interface:
234
+        tap_delete_interface(
235
+            NULL,
236
+            &guidInterface,
237
+            &bRebootRequired);
238
+        iResult = 1; goto quit;
239
+    }
240
+    else if (_tcsicmp(argv[1], TEXT("list")) == 0)
241
+    {
242
+        /* Output list of network interfaces. */
243
+        struct tap_interface_node *pInterfaceList = NULL;
244
+        DWORD dwResult = tap_list_interfaces(NULL, &pInterfaceList);
245
+        if (dwResult != ERROR_SUCCESS)
246
+        {
247
+            _ftprintf(stderr, TEXT("Enumerating interfaces failed (error 0x%x).\n"), dwResult);
248
+            iResult = 1; goto quit;
249
+        }
250
+
251
+        for (struct tap_interface_node *pInterface = pInterfaceList; pInterface; pInterface = pInterface->pNext)
252
+        {
253
+            LPOLESTR szInterfaceId = NULL;
254
+            StringFromIID((REFIID)&pInterface->guid, &szInterfaceId);
255
+            _ftprintf(stdout, TEXT("%") TEXT(PRIsLPOLESTR) TEXT("\t%") TEXT(PRIsLPTSTR) TEXT("\n"), szInterfaceId, pInterface->szName);
256
+            CoTaskMemFree(szInterfaceId);
257
+        }
258
+
259
+        iResult = 0;
260
+        tap_free_interface_list(pInterfaceList);
261
+    }
262
+    else if (_tcsicmp(argv[1], TEXT("delete")) == 0)
263
+    {
264
+        if (argc < 3)
265
+        {
266
+            _ftprintf(stderr, TEXT("Missing interface GUID or name. Please, use \"tapctl help delete\" for usage info.\n"));
267
+            return 1;
268
+        }
269
+
270
+        GUID guidInterface;
271
+        if (FAILED(IIDFromString(argv[2], (LPIID)&guidInterface)))
272
+        {
273
+            /* The argument failed to covert to GUID. Treat it as the interface name. */
274
+            struct tap_interface_node *pInterfaceList = NULL;
275
+            DWORD dwResult = tap_list_interfaces(NULL, &pInterfaceList);
276
+            if (dwResult != ERROR_SUCCESS)
277
+            {
278
+                _ftprintf(stderr, TEXT("Enumerating interfaces failed (error 0x%x).\n"), dwResult);
279
+                iResult = 1; goto quit;
280
+            }
281
+
282
+            for (struct tap_interface_node *pInterface = pInterfaceList; ; pInterface = pInterface->pNext)
283
+            {
284
+                if (pInterface == NULL)
285
+                {
286
+                    _ftprintf(stderr, TEXT("\"%s\" interface not found.\n"), argv[2]);
287
+                    iResult = 1; goto delete_cleanup_pInterfaceList;
288
+                }
289
+                else if (_tcsicmp(argv[2], pInterface->szName) == 0)
290
+                {
291
+                    memcpy(&guidInterface, &pInterface->guid, sizeof(GUID));
292
+                    break;
293
+                }
294
+            }
295
+
296
+            iResult = 0;
297
+
298
+        delete_cleanup_pInterfaceList:
299
+            tap_free_interface_list(pInterfaceList);
300
+            if (iResult)
301
+                goto quit;
302
+        }
303
+
304
+        /* Delete the network interface. */
305
+        DWORD dwResult = tap_delete_interface(
306
+            NULL,
307
+            &guidInterface,
308
+            &bRebootRequired);
309
+        if (dwResult != ERROR_SUCCESS)
310
+        {
311
+            _ftprintf(stderr, TEXT("Deleting interface \"%s\" failed (error 0x%x).\n"), argv[2], dwResult);
312
+            iResult = 1; goto quit;
313
+        }
314
+
315
+        iResult = 0; goto quit;
316
+    }
317
+    else
318
+    {
319
+        _ftprintf(stderr, TEXT("Unknown command \"%s\". Please, use \"tapctl help\" to list supported commands.\n"), argv[1]);
320
+        return 1;
321
+    }
322
+
323
+quit:
324
+    if (bRebootRequired)
325
+        _ftprintf(stderr, TEXT("A system reboot is required.\n"));
326
+
327
+    return iResult;
328
+}
329
+
330
+
331
+bool
332
+dont_mute(unsigned int flags)
333
+{
334
+    UNREFERENCED_PARAMETER(flags);
335
+
336
+    return true;
337
+}
338
+
339
+
340
+void
341
+x_msg_va(const unsigned int flags, const char *format, va_list arglist)
342
+{
343
+    /* Output message string. Note: Message strings don't contain line terminators. */
344
+    vfprintf(stderr, format, arglist);
345
+    _ftprintf(stderr, TEXT("\n"));
346
+
347
+    if ((flags & M_ERRNO) != 0)
348
+    {
349
+        /* Output system error message (if possible). */
350
+        DWORD dwResult = GetLastError();
351
+        LPTSTR szErrMessage = NULL;
352
+        if (FormatMessage(
353
+            FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS,
354
+            0,
355
+            dwResult,
356
+            0,
357
+            (LPTSTR)&szErrMessage,
358
+            0,
359
+            NULL) && szErrMessage)
360
+        {
361
+            /* Trim trailing whitespace. Set terminator after the last non-whitespace character. This prevents excessive trailing line breaks. */
362
+            for (size_t i = 0, i_last = 0; ; i++)
363
+            {
364
+                if (szErrMessage[i])
365
+                {
366
+                    if (!_istspace(szErrMessage[i]))
367
+                        i_last = i + 1;
368
+                }
369
+                else
370
+                {
371
+                    szErrMessage[i_last] = 0;
372
+                    break;
373
+                }
374
+            }
375
+
376
+            /* Output error message. */
377
+            _ftprintf(stderr, TEXT("Error 0x%x: %s\n"), dwResult, szErrMessage);
378
+
379
+            LocalFree(szErrMessage);
380
+        }
381
+        else
382
+            _ftprintf(stderr, TEXT("Error 0x%x\n"), dwResult);
383
+    }
384
+}
0 385
new file mode 100644
... ...
@@ -0,0 +1,1038 @@
0
+/*
1
+ *  tapctl -- Utility to manipulate TUN/TAP interfaces on Windows
2
+ *
3
+ *  Copyright (C) 2018 Simon Rozman <simon@rozman.si>
4
+ *
5
+ *  This program is free software; you can redistribute it and/or modify
6
+ *  it under the terms of the GNU General Public License version 2
7
+ *  as published by the Free Software Foundation.
8
+ *
9
+ *  This program is distributed in the hope that it will be useful,
10
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
+ *  GNU General Public License for more details.
13
+ *
14
+ *  You should have received a copy of the GNU General Public License along
15
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
16
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
+ */
18
+
19
+#ifdef HAVE_CONFIG_H
20
+#include <config.h>
21
+#elif defined(_MSC_VER)
22
+#include <config-msvc.h>
23
+#endif
24
+
25
+#include "tap.h"
26
+#include "error.h"
27
+
28
+#include <windows.h>
29
+#include <cfgmgr32.h>
30
+#include <objbase.h>
31
+#include <setupapi.h>
32
+#include <tchar.h>
33
+
34
+#ifdef _MSC_VER
35
+#pragma comment(lib, "advapi32.lib")
36
+#pragma comment(lib, "ole32.lib")
37
+#pragma comment(lib, "setupapi.lib")
38
+#endif
39
+
40
+const static GUID GUID_DEVCLASS_NET = { 0x4d36e972L, 0xe325, 0x11ce, { 0xbf, 0xc1, 0x08, 0x00, 0x2b, 0xe1, 0x03, 0x18 } };
41
+
42
+const static TCHAR szzHardwareIDs[] = TEXT("root\\") TEXT(TAP_WIN_COMPONENT_ID) TEXT("\0");
43
+
44
+const static TCHAR szInterfaceRegKeyPathTemplate[] = TEXT("SYSTEM\\CurrentControlSet\\Control\\Network\\%") TEXT(PRIsLPOLESTR) TEXT("\\%") TEXT(PRIsLPOLESTR) TEXT("\\Connection");
45
+#define INTERFACE_REGKEY_PATH_MAX (_countof(TEXT("SYSTEM\\CurrentControlSet\\Control\\Network\\")) - 1 + 38 + _countof(TEXT("\\")) - 1 + 38 + _countof(TEXT("\\Connection")))
46
+
47
+
48
+/**
49
+ * Checks device install parameters if a system reboot is required.
50
+ *
51
+ * @param hDeviceInfoSet  A handle to a device information set that contains a device
52
+ *                      information element that represents the device for which to
53
+ *
54
+ * @param pDeviceInfoData  A pointer to an SP_DEVINFO_DATA structure that specifies the
55
+ *                      device information element in hDeviceInfoSet.
56
+ *
57
+ * @param pbRebootRequired  A pointer to a BOOL flag. If the interface installation requires
58
+ *                      a system restart, this flag is set to TRUE. Otherwise, the flag is
59
+ *                      left unmodified. This allows the flag to be globally initialized to
60
+ *                      FALSE and reused for multiple interface installations.
61
+ *
62
+ * @return ERROR_SUCCESS on success; Win32 error code otherwise
63
+ **/
64
+static DWORD
65
+check_reboot(
66
+    _In_    HDEVINFO         hDeviceInfoSet,
67
+    _In_    PSP_DEVINFO_DATA pDeviceInfoData,
68
+    _Inout_ LPBOOL           pbRebootRequired)
69
+{
70
+    if (pbRebootRequired == NULL)
71
+        return ERROR_BAD_ARGUMENTS;
72
+
73
+    SP_DEVINSTALL_PARAMS devinstall_params = { .cbSize = sizeof(SP_DEVINSTALL_PARAMS) };
74
+    if (!SetupDiGetDeviceInstallParams(
75
+        hDeviceInfoSet,
76
+        pDeviceInfoData,
77
+        &devinstall_params))
78
+    {
79
+        DWORD dwResult = GetLastError();
80
+        msg(M_NONFATAL | M_ERRNO, "%s: SetupDiGetDeviceInstallParams failed", __FUNCTION__);
81
+        return dwResult;
82
+    }
83
+
84
+    if ((devinstall_params.Flags & (DI_NEEDREBOOT | DI_NEEDRESTART)) != 0)
85
+        *pbRebootRequired = TRUE;
86
+
87
+    return ERROR_SUCCESS;
88
+}
89
+
90
+
91
+/**
92
+ * Reads string value from registry key.
93
+ *
94
+ * @param hKey          Handle of the registry key to read from. Must be opened with read
95
+ *                      access.
96
+ *
97
+ * @param szName        Name of the value to read.
98
+ *
99
+ * @param pszValue      Pointer to string to retrieve registry value. If the value type is
100
+ *                      REG_EXPAND_SZ the value is expanded using ExpandEnvironmentStrings().
101
+ *                      The string must be released with free() after use.
102
+ *
103
+ * @return ERROR_SUCCESS on success; Win32 error code otherwise
104
+ */
105
+static DWORD
106
+get_reg_string(
107
+    _In_  HKEY     hKey,
108
+    _In_  LPCTSTR  szName,
109
+    _Out_ LPTSTR  *pszValue)
110
+{
111
+    if (pszValue == NULL)
112
+        return ERROR_BAD_ARGUMENTS;
113
+
114
+    DWORD dwValueType = REG_NONE, dwSize = 0;
115
+    DWORD dwResult = RegQueryValueEx(
116
+        hKey,
117
+        szName,
118
+        NULL,
119
+        &dwValueType,
120
+        NULL,
121
+        &dwSize);
122
+    if (dwResult != ERROR_SUCCESS)
123
+    {
124
+        SetLastError(dwResult); /* MSDN does not mention RegQueryValueEx() to set GetLastError(). But we do have an error code. Set last error manually. */
125
+        msg(M_NONFATAL | M_ERRNO, "%s: enumerating \"%"PRIsLPTSTR"\" registry value failed", __FUNCTION__, szName);
126
+        return dwResult;
127
+    }
128
+
129
+    switch (dwValueType)
130
+    {
131
+    case REG_SZ:
132
+    case REG_EXPAND_SZ:
133
+        {
134
+            /* Read value. */
135
+            LPTSTR szValue = (LPTSTR)malloc(dwSize);
136
+            dwResult = RegQueryValueEx(
137
+                hKey,
138
+                szName,
139
+                NULL,
140
+                NULL,
141
+                (LPBYTE)szValue,
142
+                &dwSize);
143
+            if (dwResult != ERROR_SUCCESS)
144
+            {
145
+                SetLastError(dwResult); /* MSDN does not mention RegQueryValueEx() to set GetLastError(). But we do have an error code. Set last error manually. */
146
+                msg(M_NONFATAL | M_ERRNO, "%s: reading \"%"PRIsLPTSTR"\" registry value failed", __FUNCTION__, szName);
147
+                free(szValue);
148
+                return dwResult;
149
+            }
150
+
151
+            if (dwValueType == REG_EXPAND_SZ)
152
+            {
153
+                /* Expand the environment strings. */
154
+                DWORD
155
+                    dwSizeExp = dwSize * 2,
156
+                    dwCountExp =
157
+#ifdef UNICODE
158
+                        dwSizeExp / sizeof(TCHAR);
159
+#else
160
+                        dwSizeExp / sizeof(TCHAR) - 1; /* Note: ANSI version requires one extra char. */
161
+#endif
162
+                LPTSTR szValueExp = (LPTSTR)malloc(dwSizeExp);
163
+                DWORD dwCountExpResult = ExpandEnvironmentStrings(
164
+                    szValue,
165
+                    szValueExp, dwCountExp
166
+                );
167
+                if (dwCountExpResult == 0)
168
+                {
169
+                    msg(M_NONFATAL | M_ERRNO, "%s: expanding \"%"PRIsLPTSTR"\" registry value failed", __FUNCTION__, szName);
170
+                    free(szValueExp);
171
+                    free(szValue);
172
+                    return dwResult;
173
+                }
174
+                else if (dwCountExpResult <= dwCountExp)
175
+                {
176
+                    /* The buffer was big enough. */
177
+                    free(szValue);
178
+                    *pszValue = szValueExp;
179
+                    return ERROR_SUCCESS;
180
+                }
181
+                else
182
+                {
183
+                    /* Retry with a bigger buffer. */
184
+                    free(szValueExp);
185
+#ifdef UNICODE
186
+                    dwSizeExp = dwCountExpResult * sizeof(TCHAR);
187
+#else
188
+                    /* Note: ANSI version requires one extra char. */
189
+                    dwSizeExp = (dwCountExpResult + 1) * sizeof(TCHAR);
190
+#endif
191
+                    dwCountExp = dwCountExpResult;
192
+                    szValueExp = (LPTSTR)malloc(dwSizeExp);
193
+                    dwCountExpResult = ExpandEnvironmentStrings(
194
+                        szValue,
195
+                        szValueExp, dwCountExp);
196
+                    free(szValue);
197
+                    *pszValue = szValueExp;
198
+                    return ERROR_SUCCESS;
199
+                }
200
+            }
201
+            else
202
+            {
203
+                *pszValue = szValue;
204
+                return ERROR_SUCCESS;
205
+            }
206
+        }
207
+
208
+    default:
209
+        msg(M_NONFATAL, "%s: \"%"PRIsLPTSTR"\" registry value is not string (type %u)", __FUNCTION__, dwValueType);
210
+        return ERROR_UNSUPPORTED_TYPE;
211
+    }
212
+}
213
+
214
+
215
+/**
216
+ * Returns network interface ID.
217
+ *
218
+ * @param hDeviceInfoSet  A handle to a device information set that contains a device
219
+ *                      information element that represents the device for which to
220
+ *
221
+ * @param pDeviceInfoData  A pointer to an SP_DEVINFO_DATA structure that specifies the
222
+ *                      device information element in hDeviceInfoSet.
223
+ *
224
+ * @param iNumAttempts  After the device is created, it might take some time before the
225
+ *                      registry key is populated. This parameter specifies the number of
226
+ *                      attempts to read NetCfgInstanceId value from registry. A 1sec sleep
227
+ *                      is inserted between retry attempts.
228
+ *
229
+ * @param pguidInterface  A pointer to GUID that receives network interface ID.
230
+ *
231
+ * @return ERROR_SUCCESS on success; Win32 error code otherwise
232
+ **/
233
+static DWORD
234
+get_net_interface_guid(
235
+    _In_  HDEVINFO         hDeviceInfoSet,
236
+    _In_  PSP_DEVINFO_DATA pDeviceInfoData,
237
+    _In_  int              iNumAttempts,
238
+    _Out_ LPGUID           pguidInterface)
239
+{
240
+    DWORD dwResult = ERROR_BAD_ARGUMENTS;
241
+
242
+    if (pguidInterface == NULL || iNumAttempts < 1)
243
+        return ERROR_BAD_ARGUMENTS;
244
+
245
+    /* Open HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class\<class>\<id> registry key. */
246
+    HKEY hKey = SetupDiOpenDevRegKey(
247
+        hDeviceInfoSet,
248
+        pDeviceInfoData,
249
+        DICS_FLAG_GLOBAL,
250
+        0,
251
+        DIREG_DRV,
252
+        KEY_READ);
253
+    if (hKey == INVALID_HANDLE_VALUE)
254
+    {
255
+        dwResult = GetLastError();
256
+        msg(M_NONFATAL | M_ERRNO, "%s: SetupDiOpenDevRegKey failed", __FUNCTION__);
257
+        return dwResult;
258
+    }
259
+
260
+    while (iNumAttempts > 0)
261
+    {
262
+        /* Query the NetCfgInstanceId value. Using get_reg_string() right on might clutter the output with error messages while the registry is still being populated. */
263
+        LPTSTR szCfgGuidString = NULL;
264
+        dwResult = RegQueryValueEx(hKey, TEXT("NetCfgInstanceId"), NULL, NULL, NULL, NULL);
265
+        if (dwResult != ERROR_SUCCESS)
266
+        {
267
+            if (dwResult == ERROR_FILE_NOT_FOUND && --iNumAttempts > 0)
268
+            {
269
+                /* Wait and retry. */
270
+                Sleep(1000);
271
+                continue;
272
+            }
273
+
274
+            SetLastError(dwResult); /* MSDN does not mention RegQueryValueEx() to set GetLastError(). But we do have an error code. Set last error manually. */
275
+            msg(M_NONFATAL | M_ERRNO, "%s: querying \"NetCfgInstanceId\" registry value failed", __FUNCTION__);
276
+            break;
277
+        }
278
+
279
+        /* Read the NetCfgInstanceId value now. */
280
+        dwResult = get_reg_string(
281
+            hKey,
282
+            TEXT("NetCfgInstanceId"),
283
+            &szCfgGuidString);
284
+        if (dwResult != ERROR_SUCCESS)
285
+            break;
286
+
287
+        dwResult = SUCCEEDED(CLSIDFromString(szCfgGuidString, (LPCLSID)pguidInterface)) ? ERROR_SUCCESS : ERROR_INVALID_DATA;
288
+        free(szCfgGuidString);
289
+        break;
290
+    }
291
+
292
+    RegCloseKey(hKey);
293
+    return dwResult;
294
+}
295
+
296
+
297
+/**
298
+* Returns a specified Plug and Play device property.
299
+*
300
+* @param hDeviceInfoSet  A handle to a device information set that contains a device
301
+*                      information element that represents the device for which to
302
+*                      retrieve a Plug and Play property.
303
+*
304
+* @param pDeviceInfoData  A pointer to an SP_DEVINFO_DATA structure that specifies the
305
+*                      device information element in hDeviceInfoSet.
306
+*
307
+* @param dwProperty     Specifies the property to be retrieved. See
308
+*                       https://msdn.microsoft.com/en-us/library/windows/hardware/ff551967.aspx
309
+*
310
+* @pdwPropertyRegDataType  A pointer to a variable that receives the data type of the
311
+*                       property that is being retrieved. This is one of the standard
312
+*                       registry data types. This parameter is optional and can be NULL.
313
+*
314
+* @param ppData         A pointer to pointer to data that receives the device propery. The
315
+*                       data must be released with free() after use.
316
+*
317
+* @return ERROR_SUCCESS on success; Win32 error code otherwise
318
+**/
319
+static DWORD
320
+get_device_reg_property(
321
+    _In_      HDEVINFO          hDeviceInfoSet,
322
+    _In_      PSP_DEVINFO_DATA  pDeviceInfoData,
323
+    _In_      DWORD             dwProperty,
324
+    _Out_opt_ LPDWORD           pdwPropertyRegDataType,
325
+    _Out_     LPVOID            *ppData)
326
+{
327
+    DWORD dwResult = ERROR_BAD_ARGUMENTS;
328
+
329
+    if (ppData == NULL)
330
+        return ERROR_BAD_ARGUMENTS;
331
+
332
+    /* Try with stack buffer first. */
333
+    BYTE bBufStack[128];
334
+    DWORD dwRequiredSize = 0;
335
+    if (SetupDiGetDeviceRegistryProperty(
336
+        hDeviceInfoSet,
337
+        pDeviceInfoData,
338
+        dwProperty,
339
+        pdwPropertyRegDataType,
340
+        bBufStack,
341
+        sizeof(bBufStack),
342
+        &dwRequiredSize))
343
+    {
344
+        /* Copy from stack. */
345
+        *ppData = malloc(dwRequiredSize);
346
+        memcpy(*ppData, bBufStack, dwRequiredSize);
347
+        return ERROR_SUCCESS;
348
+    }
349
+    else
350
+    {
351
+        dwResult = GetLastError();
352
+        if (dwResult == ERROR_INSUFFICIENT_BUFFER)
353
+        {
354
+            /* Allocate on heap and retry. */
355
+            *ppData = malloc(dwRequiredSize);
356
+            if (SetupDiGetDeviceRegistryProperty(
357
+                hDeviceInfoSet,
358
+                pDeviceInfoData,
359
+                dwProperty,
360
+                pdwPropertyRegDataType,
361
+                *ppData,
362
+                dwRequiredSize,
363
+                &dwRequiredSize))
364
+                return ERROR_SUCCESS;
365
+            else
366
+            {
367
+                dwResult = GetLastError();
368
+                msg(M_NONFATAL | M_ERRNO, "%s: SetupDiGetDeviceRegistryProperty(%u) failed", __FUNCTION__, dwProperty);
369
+                return dwResult;
370
+            }
371
+        }
372
+        else
373
+        {
374
+            msg(M_NONFATAL | M_ERRNO, "%s: SetupDiGetDeviceRegistryProperty(%u) failed", __FUNCTION__, dwProperty);
375
+            return dwResult;
376
+        }
377
+    }
378
+}
379
+
380
+
381
+/**
382
+* Returns length of list of strings
383
+*
384
+* @param str              Pointer to a list of strings terminated by an empty string.
385
+*
386
+* @return Number of characters not counting the final zero terminator
387
+**/
388
+static inline size_t
389
+_tcszlen(_In_ LPCTSTR str)
390
+{
391
+    LPCTSTR s;
392
+    for (s = str; s[0]; s += _tcslen(s) + 1);
393
+    return s - str;
394
+}
395
+
396
+
397
+DWORD
398
+tap_create_interface(
399
+    _In_opt_ HWND    hwndParent,
400
+    _In_opt_ LPCTSTR szDeviceDescription,
401
+    _Inout_  LPBOOL  pbRebootRequired,
402
+    _Out_    LPGUID  pguidInterface)
403
+{
404
+    DWORD dwResult;
405
+
406
+    if (pbRebootRequired == NULL ||
407
+        pguidInterface == NULL)
408
+        return ERROR_BAD_ARGUMENTS;
409
+
410
+    /* Create an empty device info set for network adapter device class. */
411
+    HDEVINFO hDevInfoList = SetupDiCreateDeviceInfoList(&GUID_DEVCLASS_NET, hwndParent);
412
+    if (hDevInfoList == INVALID_HANDLE_VALUE)
413
+    {
414
+        dwResult = GetLastError();
415
+        msg(M_NONFATAL, "%s: SetupDiCreateDeviceInfoList failed", __FUNCTION__);
416
+        return dwResult;
417
+    }
418
+
419
+    /* Get the device class name from GUID. */
420
+    TCHAR szClassName[MAX_CLASS_NAME_LEN];
421
+    if (!SetupDiClassNameFromGuid(
422
+        &GUID_DEVCLASS_NET,
423
+        szClassName,
424
+        _countof(szClassName),
425
+        NULL))
426
+    {
427
+        dwResult = GetLastError();
428
+        msg(M_NONFATAL, "%s: SetupDiClassNameFromGuid failed", __FUNCTION__);
429
+        goto cleanup_hDevInfoList;
430
+    }
431
+
432
+    /* Create a new device info element and add it to the device info set. */
433
+    SP_DEVINFO_DATA devinfo_data = { .cbSize = sizeof(SP_DEVINFO_DATA) };
434
+    if (!SetupDiCreateDeviceInfo(
435
+        hDevInfoList,
436
+        szClassName,
437
+        &GUID_DEVCLASS_NET,
438
+        szDeviceDescription,
439
+        hwndParent,
440
+        DICD_GENERATE_ID,
441
+        &devinfo_data))
442
+    {
443
+        dwResult = GetLastError();
444
+        msg(M_NONFATAL, "%s: SetupDiClassNameFromGuid failed", __FUNCTION__);
445
+        goto cleanup_hDevInfoList;
446
+    }
447
+
448
+    /* Set a device information element as the selected member of a device information set. */
449
+    if (!SetupDiSetSelectedDevice(
450
+        hDevInfoList,
451
+        &devinfo_data))
452
+    {
453
+        dwResult = GetLastError();
454
+        msg(M_NONFATAL, "%s: SetupDiSetSelectedDevice failed", __FUNCTION__);
455
+        goto cleanup_hDevInfoList;
456
+    }
457
+
458
+    /* Set Plug&Play device hardware ID property. */
459
+    if (!SetupDiSetDeviceRegistryProperty(
460
+        hDevInfoList,
461
+        &devinfo_data,
462
+        SPDRP_HARDWAREID,
463
+        (const BYTE *)szzHardwareIDs, sizeof(szzHardwareIDs)))
464
+    {
465
+        dwResult = GetLastError();
466
+        msg(M_NONFATAL, "%s: SetupDiSetDeviceRegistryProperty failed", __FUNCTION__);
467
+        goto cleanup_hDevInfoList;
468
+    }
469
+
470
+    /* Search for the driver. */
471
+    if (!SetupDiBuildDriverInfoList(
472
+        hDevInfoList,
473
+        &devinfo_data,
474
+        SPDIT_CLASSDRIVER))
475
+    {
476
+        dwResult = GetLastError();
477
+        msg(M_NONFATAL, "%s: SetupDiBuildDriverInfoList failed", __FUNCTION__);
478
+        goto cleanup_hDevInfoList;
479
+    }
480
+    DWORDLONG dwlDriverVersion = 0;
481
+    DWORD drvinfo_detail_data_size = sizeof(SP_DRVINFO_DETAIL_DATA) + 0x100;
482
+    SP_DRVINFO_DETAIL_DATA *drvinfo_detail_data = (SP_DRVINFO_DETAIL_DATA*)malloc(drvinfo_detail_data_size);
483
+    for (DWORD dwIndex = 0; ; dwIndex++)
484
+    {
485
+        /* Get a driver from the list. */
486
+        SP_DRVINFO_DATA drvinfo_data = { .cbSize = sizeof(SP_DRVINFO_DATA) };
487
+        if (!SetupDiEnumDriverInfo(
488
+            hDevInfoList,
489
+            &devinfo_data,
490
+            SPDIT_CLASSDRIVER,
491
+            dwIndex,
492
+            &drvinfo_data))
493
+        {
494
+            if (GetLastError() == ERROR_NO_MORE_ITEMS)
495
+                break;
496
+            else
497
+            {
498
+                /* Something is wrong with this driver. Skip it. */
499
+                msg(M_WARN | M_ERRNO, "%s: SetupDiEnumDriverInfo(%u) failed", __FUNCTION__, dwIndex);
500
+                continue;
501
+            }
502
+        }
503
+
504
+        /* Get driver info details. */
505
+        DWORD dwSize;
506
+        drvinfo_detail_data->cbSize = sizeof(SP_DRVINFO_DETAIL_DATA);
507
+        if (!SetupDiGetDriverInfoDetail(
508
+            hDevInfoList,
509
+            &devinfo_data,
510
+            &drvinfo_data,
511
+            drvinfo_detail_data,
512
+            drvinfo_detail_data_size,
513
+            &dwSize))
514
+        {
515
+            if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
516
+            {
517
+                /* (Re)allocate buffer. */
518
+                if (drvinfo_detail_data)
519
+                    free(drvinfo_detail_data);
520
+
521
+                drvinfo_detail_data_size = dwSize;
522
+                drvinfo_detail_data = (SP_DRVINFO_DETAIL_DATA*)malloc(drvinfo_detail_data_size);
523
+
524
+                /* Re-get driver info details. */
525
+                drvinfo_detail_data->cbSize = sizeof(SP_DRVINFO_DETAIL_DATA);
526
+                if (!SetupDiGetDriverInfoDetail(
527
+                    hDevInfoList,
528
+                    &devinfo_data,
529
+                    &drvinfo_data,
530
+                    drvinfo_detail_data,
531
+                    drvinfo_detail_data_size,
532
+                    &dwSize))
533
+                {
534
+                    /* Something is wrong with this driver. Skip it. */
535
+                    continue;
536
+                }
537
+            }
538
+            else
539
+            {
540
+                /* Something is wrong with this driver. Skip it. */
541
+                msg(M_WARN | M_ERRNO, "%s: SetupDiGetDriverInfoDetail(\"%hs\") failed", __FUNCTION__, drvinfo_data.Description);
542
+                continue;
543
+            }
544
+        }
545
+
546
+        /* Check the driver version first, since the check is trivial and will save us iterating over hardware IDs for any driver versioned prior our best match. */
547
+        if (dwlDriverVersion < drvinfo_data.DriverVersion)
548
+        {
549
+            /* Search the list of hardware IDs. */
550
+            for (LPTSTR szHwdID = drvinfo_detail_data->HardwareID; szHwdID && szHwdID[0]; szHwdID += _tcslen(szHwdID) + 1)
551
+            {
552
+                if (_tcsicmp(szHwdID, szzHardwareIDs) == 0)
553
+                {
554
+                    /* Matching hardware ID found. Select the driver. */
555
+                    if (!SetupDiSetSelectedDriver(
556
+                        hDevInfoList,
557
+                        &devinfo_data,
558
+                        &drvinfo_data))
559
+                    {
560
+                        /* Something is wrong with this driver. Skip it. */
561
+                        msg(M_WARN | M_ERRNO, "%s: SetupDiSetSelectedDriver(\"%hs\") failed", __FUNCTION__, drvinfo_data.Description);
562
+                        break;
563
+                    }
564
+
565
+                    dwlDriverVersion = drvinfo_data.DriverVersion;
566
+                    break;
567
+                }
568
+            }
569
+        }
570
+    }
571
+    if (drvinfo_detail_data)
572
+        free(drvinfo_detail_data);
573
+
574
+    /* Call appropriate class installer. */
575
+    if (!SetupDiCallClassInstaller(
576
+        DIF_REGISTERDEVICE,
577
+        hDevInfoList,
578
+        &devinfo_data))
579
+    {
580
+        dwResult = GetLastError();
581
+        msg(M_NONFATAL, "%s: SetupDiCallClassInstaller(DIF_REGISTERDEVICE) failed", __FUNCTION__);
582
+        goto cleanup_DriverInfoList;
583
+    }
584
+
585
+    /* Register device co-installers if any. */
586
+    if (!SetupDiCallClassInstaller(
587
+        DIF_REGISTER_COINSTALLERS,
588
+        hDevInfoList,
589
+        &devinfo_data))
590
+    {
591
+        dwResult = GetLastError();
592
+        msg(M_WARN | M_ERRNO, "%s: SetupDiCallClassInstaller(DIF_REGISTER_COINSTALLERS) failed", __FUNCTION__);
593
+    }
594
+
595
+    /* Install interfaces if any. */
596
+    if (!SetupDiCallClassInstaller(
597
+        DIF_INSTALLINTERFACES,
598
+        hDevInfoList,
599
+        &devinfo_data))
600
+    {
601
+        dwResult = GetLastError();
602
+        msg(M_WARN | M_ERRNO, "%s: SetupDiCallClassInstaller(DIF_INSTALLINTERFACES) failed", __FUNCTION__);
603
+    }
604
+
605
+    /* Install the device. */
606
+    if (!SetupDiCallClassInstaller(
607
+        DIF_INSTALLDEVICE,
608
+        hDevInfoList,
609
+        &devinfo_data))
610
+    {
611
+        dwResult = GetLastError();
612
+        msg(M_NONFATAL | M_ERRNO, "%s: SetupDiCallClassInstaller(DIF_INSTALLDEVICE) failed", __FUNCTION__);
613
+        goto cleanup_remove_device;
614
+    }
615
+
616
+    /* Check if a system reboot is required. (Ignore errors) */
617
+    check_reboot(hDevInfoList, &devinfo_data, pbRebootRequired);
618
+
619
+    /* Get network interface ID from registry. Retry for max 30sec. */
620
+    dwResult = get_net_interface_guid(hDevInfoList, &devinfo_data, 30, pguidInterface);
621
+
622
+cleanup_remove_device:
623
+    if (dwResult != ERROR_SUCCESS)
624
+    {
625
+        /* The interface was installed. But, the interface ID was unobtainable. Clean-up. */
626
+        SP_REMOVEDEVICE_PARAMS removedevice_params =
627
+        {
628
+            .ClassInstallHeader =
629
+            {
630
+                .cbSize = sizeof(SP_CLASSINSTALL_HEADER),
631
+                .InstallFunction = DIF_REMOVE,
632
+            },
633
+            .Scope = DI_REMOVEDEVICE_GLOBAL,
634
+            .HwProfile = 0,
635
+        };
636
+
637
+        /* Set class installer parameters for DIF_REMOVE. */
638
+        if (SetupDiSetClassInstallParams(
639
+            hDevInfoList,
640
+            &devinfo_data,
641
+            &removedevice_params.ClassInstallHeader,
642
+            sizeof(SP_REMOVEDEVICE_PARAMS)))
643
+        {
644
+            /* Call appropriate class installer. */
645
+            if (SetupDiCallClassInstaller(
646
+                    DIF_REMOVE,
647
+                    hDevInfoList,
648
+                    &devinfo_data))
649
+            {
650
+                /* Check if a system reboot is required. */
651
+                check_reboot(hDevInfoList, &devinfo_data, pbRebootRequired);
652
+            }
653
+            else
654
+                msg(M_NONFATAL | M_ERRNO, "%s: SetupDiCallClassInstaller(DIF_REMOVE) failed", __FUNCTION__);
655
+        }
656
+        else
657
+            msg(M_NONFATAL | M_ERRNO, "%s: SetupDiSetClassInstallParams failed", __FUNCTION__);
658
+    }
659
+
660
+cleanup_DriverInfoList:
661
+    SetupDiDestroyDriverInfoList(
662
+        hDevInfoList,
663
+        &devinfo_data,
664
+        SPDIT_CLASSDRIVER);
665
+
666
+cleanup_hDevInfoList:
667
+    SetupDiDestroyDeviceInfoList(hDevInfoList);
668
+    return dwResult;
669
+}
670
+
671
+
672
+DWORD tap_delete_interface(
673
+    _In_opt_ HWND    hwndParent,
674
+    _In_     LPCGUID pguidInterface,
675
+    _Inout_  LPBOOL  pbRebootRequired)
676
+{
677
+    DWORD dwResult;
678
+
679
+    if (pguidInterface == NULL)
680
+        return ERROR_BAD_ARGUMENTS;
681
+
682
+    /* Create a list of network devices. */
683
+    HDEVINFO hDevInfoList = SetupDiGetClassDevsEx(
684
+        &GUID_DEVCLASS_NET,
685
+        NULL,
686
+        hwndParent,
687
+        DIGCF_PRESENT,
688
+        NULL,
689
+        NULL,
690
+        NULL);
691
+    if (hDevInfoList == INVALID_HANDLE_VALUE)
692
+    {
693
+        dwResult = GetLastError();
694
+        msg(M_NONFATAL, "%s: SetupDiGetClassDevsEx failed", __FUNCTION__);
695
+        return dwResult;
696
+    }
697
+
698
+    /* Retrieve information associated with a device information set. */
699
+    SP_DEVINFO_LIST_DETAIL_DATA devinfo_list_detail_data = { .cbSize = sizeof(SP_DEVINFO_LIST_DETAIL_DATA) };
700
+    if (!SetupDiGetDeviceInfoListDetail(hDevInfoList, &devinfo_list_detail_data))
701
+    {
702
+        dwResult = GetLastError();
703
+        msg(M_NONFATAL, "%s: SetupDiGetDeviceInfoListDetail failed", __FUNCTION__);
704
+        goto cleanup_hDevInfoList;
705
+    }
706
+
707
+    /* Iterate. */
708
+    for (DWORD dwIndex = 0; ; dwIndex++)
709
+    {
710
+        /* Get the device from the list. */
711
+        SP_DEVINFO_DATA devinfo_data = { .cbSize = sizeof(SP_DEVINFO_DATA) };
712
+        if (!SetupDiEnumDeviceInfo(
713
+            hDevInfoList,
714
+            dwIndex,
715
+            &devinfo_data))
716
+        {
717
+            if (GetLastError() == ERROR_NO_MORE_ITEMS)
718
+            {
719
+                LPOLESTR szInterfaceId = NULL;
720
+                StringFromIID((REFIID)pguidInterface, &szInterfaceId);
721
+                msg(M_NONFATAL, "%s: Interface %"PRIsLPOLESTR" not found", __FUNCTION__, szInterfaceId);
722
+                CoTaskMemFree(szInterfaceId);
723
+                dwResult = ERROR_FILE_NOT_FOUND;
724
+                goto cleanup_hDevInfoList;
725
+            }
726
+            else
727
+            {
728
+                /* Something is wrong with this device. Skip it. */
729
+                msg(M_WARN | M_ERRNO, "%s: SetupDiEnumDeviceInfo(%u) failed", __FUNCTION__, dwIndex);
730
+                continue;
731
+            }
732
+        }
733
+
734
+        /* Get interface GUID. */
735
+        GUID guidInterface;
736
+        dwResult = get_net_interface_guid(hDevInfoList, &devinfo_data, 1, &guidInterface);
737
+        if (dwResult != ERROR_SUCCESS) {
738
+            /* Something is wrong with this device. Skip it. */
739
+            continue;
740
+        }
741
+
742
+        /* Compare GUIDs. */
743
+        if (memcmp(pguidInterface, &guidInterface, sizeof(GUID)) == 0)
744
+        {
745
+            /* Remove the device. */
746
+            SP_REMOVEDEVICE_PARAMS removedevice_params =
747
+            {
748
+                .ClassInstallHeader =
749
+                {
750
+                    .cbSize = sizeof(SP_CLASSINSTALL_HEADER),
751
+                    .InstallFunction = DIF_REMOVE,
752
+                },
753
+                .Scope = DI_REMOVEDEVICE_GLOBAL,
754
+                .HwProfile = 0,
755
+            };
756
+
757
+            /* Set class installer parameters for DIF_REMOVE. */
758
+            if (!SetupDiSetClassInstallParams(
759
+                hDevInfoList,
760
+                &devinfo_data,
761
+                &removedevice_params.ClassInstallHeader,
762
+                sizeof(SP_REMOVEDEVICE_PARAMS)))
763
+            {
764
+                dwResult = GetLastError();
765
+                msg(M_NONFATAL, "%s: SetupDiSetClassInstallParams failed", __FUNCTION__);
766
+                goto cleanup_hDevInfoList;
767
+            }
768
+
769
+            /* Call appropriate class installer. */
770
+            if (!SetupDiCallClassInstaller(
771
+                DIF_REMOVE,
772
+                hDevInfoList,
773
+                &devinfo_data))
774
+            {
775
+                dwResult = GetLastError();
776
+                msg(M_NONFATAL, "%s: SetupDiCallClassInstaller(DIF_REMOVE) failed", __FUNCTION__);
777
+                goto cleanup_hDevInfoList;
778
+            }
779
+
780
+            /* Check if a system reboot is required. */
781
+            check_reboot(hDevInfoList, &devinfo_data, pbRebootRequired);
782
+            dwResult = ERROR_SUCCESS;
783
+            break;
784
+        }
785
+    }
786
+
787
+cleanup_hDevInfoList:
788
+    SetupDiDestroyDeviceInfoList(hDevInfoList);
789
+    return dwResult;
790
+}
791
+
792
+
793
+DWORD
794
+tap_set_interface_name(
795
+    _In_ LPCGUID pguidInterface,
796
+    _In_ LPCTSTR szName)
797
+{
798
+    DWORD dwResult;
799
+
800
+    if (pguidInterface == NULL || szName == NULL)
801
+        return ERROR_BAD_ARGUMENTS;
802
+
803
+    /* Get the device class GUID as string. */
804
+    LPOLESTR szDevClassNetId = NULL;
805
+    StringFromIID((REFIID)&GUID_DEVCLASS_NET, &szDevClassNetId);
806
+
807
+    /* Get the interface GUID as string. */
808
+    LPOLESTR szInterfaceId = NULL;
809
+    StringFromIID((REFIID)pguidInterface, &szInterfaceId);
810
+
811
+    /* Render registry key path. */
812
+    TCHAR szRegKey[INTERFACE_REGKEY_PATH_MAX];
813
+    _stprintf_s(
814
+        szRegKey, _countof(szRegKey),
815
+        szInterfaceRegKeyPathTemplate,
816
+        szDevClassNetId,
817
+        szInterfaceId);
818
+
819
+    /* Open network interface registry key. */
820
+    HKEY hKey = NULL;
821
+    dwResult = RegOpenKeyEx(
822
+        HKEY_LOCAL_MACHINE,
823
+        szRegKey,
824
+        0,
825
+        KEY_SET_VALUE,
826
+        &hKey);
827
+    if (dwResult != ERROR_SUCCESS)
828
+    {
829
+        SetLastError(dwResult); /* MSDN does not mention RegOpenKeyEx() to set GetLastError(). But we do have an error code. Set last error manually. */
830
+        msg(M_NONFATAL | M_ERRNO, "%s: RegOpenKeyEx(HKLM, \"%"PRIsLPTSTR"\") failed", __FUNCTION__, szRegKey);
831
+        goto cleanup_szInterfaceId;
832
+    }
833
+
834
+    /* Set the interface name. */
835
+    size_t sizeName = ((_tcslen(szName) + 1) * sizeof(TCHAR));
836
+#ifdef _WIN64
837
+    if (sizeName > DWORD_MAX)
838
+    {
839
+        dwResult = ERROR_BAD_ARGUMENTS;
840
+        msg(M_NONFATAL, "%s: string too big (size %u).", __FUNCTION__, sizeName);
841
+        goto cleanup_hKey;
842
+    }
843
+#endif
844
+    dwResult = RegSetKeyValue(
845
+        hKey,
846
+        NULL,
847
+        TEXT("Name"),
848
+        REG_SZ,
849
+        szName,
850
+        (DWORD)sizeName);
851
+    if (dwResult != ERROR_SUCCESS)
852
+    {
853
+        SetLastError(dwResult); /* MSDN does not mention RegSetKeyValue() to set GetLastError(). But we do have an error code. Set last error manually. */
854
+        msg(M_NONFATAL | M_ERRNO, "%s: RegSetKeyValue(\"Name\") failed", __FUNCTION__);
855
+        goto cleanup_hKey;
856
+    }
857
+
858
+cleanup_hKey:
859
+    RegCloseKey(hKey);
860
+cleanup_szInterfaceId:
861
+    CoTaskMemFree(szInterfaceId);
862
+    CoTaskMemFree(szDevClassNetId);
863
+    return dwResult;
864
+}
865
+
866
+
867
+DWORD
868
+tap_list_interfaces(
869
+    _In_opt_ HWND                        hwndParent,
870
+    _Out_    struct tap_interface_node **ppInterface)
871
+{
872
+    DWORD dwResult;
873
+
874
+    if (ppInterface == NULL)
875
+        return ERROR_BAD_ARGUMENTS;
876
+
877
+    /* Create a list of network devices. */
878
+    HDEVINFO hDevInfoList = SetupDiGetClassDevsEx(
879
+        &GUID_DEVCLASS_NET,
880
+        NULL,
881
+        hwndParent,
882
+        DIGCF_PRESENT,
883
+        NULL,
884
+        NULL,
885
+        NULL);
886
+    if (hDevInfoList == INVALID_HANDLE_VALUE)
887
+    {
888
+        dwResult = GetLastError();
889
+        msg(M_NONFATAL, "%s: SetupDiGetClassDevsEx failed", __FUNCTION__);
890
+        return dwResult;
891
+    }
892
+
893
+    /* Retrieve information associated with a device information set. */
894
+    SP_DEVINFO_LIST_DETAIL_DATA devinfo_list_detail_data = { .cbSize = sizeof(SP_DEVINFO_LIST_DETAIL_DATA) };
895
+    if (!SetupDiGetDeviceInfoListDetail(hDevInfoList, &devinfo_list_detail_data))
896
+    {
897
+        dwResult = GetLastError();
898
+        msg(M_NONFATAL, "%s: SetupDiGetDeviceInfoListDetail failed", __FUNCTION__);
899
+        goto cleanup_hDevInfoList;
900
+    }
901
+
902
+    /* Get the device class GUID as string. */
903
+    LPOLESTR szDevClassNetId = NULL;
904
+    StringFromIID((REFIID)&GUID_DEVCLASS_NET, &szDevClassNetId);
905
+
906
+    /* Iterate. */
907
+    *ppInterface = NULL;
908
+    struct tap_interface_node *pInterfaceTail = NULL;
909
+    for (DWORD dwIndex = 0; ; dwIndex++)
910
+    {
911
+        /* Get the device from the list. */
912
+        SP_DEVINFO_DATA devinfo_data = { .cbSize = sizeof(SP_DEVINFO_DATA) };
913
+        if (!SetupDiEnumDeviceInfo(
914
+            hDevInfoList,
915
+            dwIndex,
916
+            &devinfo_data))
917
+        {
918
+            if (GetLastError() == ERROR_NO_MORE_ITEMS)
919
+                break;
920
+            else
921
+            {
922
+                /* Something is wrong with this device. Skip it. */
923
+                msg(M_WARN | M_ERRNO, "%s: SetupDiEnumDeviceInfo(%u) failed", __FUNCTION__, dwIndex);
924
+                continue;
925
+            }
926
+        }
927
+
928
+        /* Get interface GUID. */
929
+        GUID guidInterface;
930
+        dwResult = get_net_interface_guid(hDevInfoList, &devinfo_data, 1, &guidInterface);
931
+        if (dwResult != ERROR_SUCCESS) {
932
+            /* Something is wrong with this device. Skip it. */
933
+            continue;
934
+        }
935
+
936
+        /* Get the interface GUID as string. */
937
+        LPOLESTR szInterfaceId = NULL;
938
+        StringFromIID((REFIID)&guidInterface, &szInterfaceId);
939
+
940
+        /* Get device hardware ID(s). */
941
+        DWORD dwDataType = REG_NONE;
942
+        LPTSTR szzDeviceHardwareIDs = NULL;
943
+        dwResult = get_device_reg_property(
944
+            hDevInfoList,
945
+            &devinfo_data,
946
+            SPDRP_HARDWAREID,
947
+            &dwDataType,
948
+            (LPVOID)&szzDeviceHardwareIDs);
949
+        if (dwResult != ERROR_SUCCESS)
950
+            goto cleanup_szInterfaceId;
951
+
952
+        /* Render registry key path. */
953
+        TCHAR szRegKey[INTERFACE_REGKEY_PATH_MAX];
954
+        _stprintf_s(
955
+            szRegKey, _countof(szRegKey),
956
+            szInterfaceRegKeyPathTemplate,
957
+            szDevClassNetId,
958
+            szInterfaceId);
959
+
960
+        /* Open network interface registry key. */
961
+        HKEY hKey = NULL;
962
+        dwResult = RegOpenKeyEx(
963
+            HKEY_LOCAL_MACHINE,
964
+            szRegKey,
965
+            0,
966
+            KEY_READ,
967
+            &hKey);
968
+        if (dwResult != ERROR_SUCCESS)
969
+        {
970
+            SetLastError(dwResult); /* MSDN does not mention RegOpenKeyEx() to set GetLastError(). But we do have an error code. Set last error manually. */
971
+            msg(M_WARN | M_ERRNO, "%s: RegOpenKeyEx(HKLM, \"%"PRIsLPTSTR"\") failed", __FUNCTION__, szRegKey);
972
+            goto cleanup_szzDeviceHardwareIDs;
973
+        }
974
+
975
+        /* Read interface name. */
976
+        LPTSTR szName = NULL;
977
+        dwResult = get_reg_string(
978
+            hKey,
979
+            TEXT("Name"),
980
+            &szName);
981
+        if (dwResult != ERROR_SUCCESS)
982
+        {
983
+            SetLastError(dwResult);
984
+            msg(M_WARN | M_ERRNO, "%s: Cannot determine %"PRIsLPOLESTR" interface name", __FUNCTION__, szInterfaceId);
985
+            goto cleanup_hKey;
986
+        }
987
+
988
+        /* Append to the list. */
989
+        size_t hwid_size = (_tcszlen(szzDeviceHardwareIDs) + 1) * sizeof(TCHAR);
990
+        size_t name_size = (_tcslen(szName) + 1) * sizeof(TCHAR);
991
+        struct tap_interface_node *node = (struct tap_interface_node*)malloc(sizeof(struct tap_interface_node) + hwid_size + name_size);
992
+        memcpy(&node->guid, &guidInterface, sizeof(GUID));
993
+        node->szzHardwareIDs = (LPTSTR)(node + 1);
994
+        memcpy(node->szzHardwareIDs, szzDeviceHardwareIDs, hwid_size);
995
+        node->szName = (LPTSTR)((LPBYTE)node->szzHardwareIDs + hwid_size);
996
+        memcpy(node->szName, szName, name_size);
997
+        node->pNext = NULL;
998
+        if (pInterfaceTail)
999
+        {
1000
+            pInterfaceTail->pNext = node;
1001
+            pInterfaceTail = node;
1002
+        }
1003
+        else
1004
+            *ppInterface = pInterfaceTail = node;
1005
+
1006
+        free(szName);
1007
+    cleanup_hKey:
1008
+        RegCloseKey(hKey);
1009
+    cleanup_szzDeviceHardwareIDs:
1010
+        free(szzDeviceHardwareIDs);
1011
+    cleanup_szInterfaceId:
1012
+        CoTaskMemFree(szInterfaceId);
1013
+    }
1014
+
1015
+    dwResult = ERROR_SUCCESS;
1016
+
1017
+    CoTaskMemFree(szDevClassNetId);
1018
+cleanup_hDevInfoList:
1019
+    SetupDiDestroyDeviceInfoList(hDevInfoList);
1020
+    return dwResult;
1021
+}
1022
+
1023
+
1024
+void
1025
+tap_free_interface_list(
1026
+    _In_ struct tap_interface_node *pInterfaceList)
1027
+{
1028
+    /* Iterate over all nodes of the list. */
1029
+    while (pInterfaceList)
1030
+    {
1031
+        struct tap_interface_node *node = pInterfaceList;
1032
+        pInterfaceList = pInterfaceList->pNext;
1033
+
1034
+        /* Free the interface node. */
1035
+        free(node);
1036
+    }
1037
+}
0 1038
new file mode 100644
... ...
@@ -0,0 +1,139 @@
0
+/*
1
+ *  tapctl -- Utility to manipulate TUN/TAP interfaces on Windows
2
+ *
3
+ *  Copyright (C) 2018 Simon Rozman <simon@rozman.si>
4
+ *
5
+ *  This program is free software; you can redistribute it and/or modify
6
+ *  it under the terms of the GNU General Public License version 2
7
+ *  as published by the Free Software Foundation.
8
+ *
9
+ *  This program is distributed in the hope that it will be useful,
10
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
+ *  GNU General Public License for more details.
13
+ *
14
+ *  You should have received a copy of the GNU General Public License along
15
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
16
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
+ */
18
+
19
+#ifndef TAP_H
20
+#define TAP_H
21
+
22
+#include <windows.h>
23
+#include "basic.h"
24
+
25
+
26
+/**
27
+ * Creates a TUN/TAP interface.
28
+ *
29
+ * @param hwndParent    A handle to the top-level window to use for any user interface that is
30
+ *                      related to non-device-specific actions (such as a select-device dialog
31
+ *                      box that uses the global class driver list). This handle is optional
32
+ *                      and can be NULL. If a specific top-level window is not required, set
33
+ *                      hwndParent to NULL.
34
+ *
35
+ * @param szDeviceDescription  A pointer to a NULL-terminated string that supplies the text
36
+ *                      description of the device. This pointer is optional and can be NULL.
37
+ *
38
+ * @param pbRebootRequired  A pointer to a BOOL flag. If the interface installation requires
39
+ *                      a system restart, this flag is set to TRUE. Otherwise, the flag is
40
+ *                      left unmodified. This allows the flag to be globally initialized to
41
+ *                      FALSE and reused for multiple interface installations.
42
+ *
43
+ * @param pguidInterface  A pointer to GUID that receives network interface ID.
44
+ *
45
+ * @return ERROR_SUCCESS on success; Win32 error code otherwise
46
+ **/
47
+DWORD
48
+tap_create_interface(
49
+    _In_opt_ HWND    hwndParent,
50
+    _In_opt_ LPCTSTR szDeviceDescription,
51
+    _Inout_  LPBOOL  pbRebootRequired,
52
+    _Out_    LPGUID  pguidInterface);
53
+
54
+
55
+/**
56
+ * Deletes a TUN/TAP interface.
57
+ *
58
+ * @param hwndParent    A handle to the top-level window to use for any user interface that is
59
+ *                      related to non-device-specific actions (such as a select-device dialog
60
+ *                      box that uses the global class driver list). This handle is optional
61
+ *                      and can be NULL. If a specific top-level window is not required, set
62
+ *                      hwndParent to NULL.
63
+ *
64
+ * @param pguidInterface  A pointer to GUID that contains network interface ID.
65
+ *
66
+ * @param pbRebootRequired  A pointer to a BOOL flag. If the interface installation requires
67
+ *                      a system restart, this flag is set to TRUE. Otherwise, the flag is
68
+ *                      left unmodified. This allows the flag to be globally initialized to
69
+ *                      FALSE and reused for multiple interface installations.
70
+ *
71
+ * @return ERROR_SUCCESS on success; Win32 error code otherwise
72
+ **/
73
+DWORD
74
+tap_delete_interface(
75
+    _In_opt_ HWND    hwndParent,
76
+    _In_     LPCGUID pguidInterface,
77
+    _Inout_  LPBOOL  pbRebootRequired);
78
+
79
+
80
+/**
81
+ * Sets interface name.
82
+ *
83
+ * @param pguidInterface  A pointer to GUID that contains network interface ID.
84
+ *
85
+ * @param szName        New interface name - must be unique
86
+ *
87
+ * @return ERROR_SUCCESS on success; Win32 error code otherwise
88
+ **/
89
+DWORD
90
+tap_set_interface_name(
91
+    _In_ LPCGUID pguidInterface,
92
+    _In_ LPCTSTR szName);
93
+
94
+
95
+/**
96
+ * Network interface list node
97
+ */
98
+struct tap_interface_node
99
+{
100
+    GUID   guid;           /** Interface GUID */
101
+    LPTSTR szzHardwareIDs; /** Device hardware ID(s) */
102
+    LPTSTR szName;         /** Interface name */
103
+
104
+    struct tap_interface_node *pNext; /** Pointer to next interface */
105
+};
106
+
107
+
108
+/**
109
+ * Creates a list of available network interfaces.
110
+ *
111
+ * @param hwndParent    A handle to the top-level window to use for any user interface that is
112
+ *                      related to non-device-specific actions (such as a select-device dialog
113
+ *                      box that uses the global class driver list). This handle is optional
114
+ *                      and can be NULL. If a specific top-level window is not required, set
115
+ *                      hwndParent to NULL.
116
+ *
117
+ * @param ppInterfaceList  A pointer to the list to receive pointer to the first interface in
118
+ *                      the list. After the list is no longer required, free it using
119
+ *                      tap_free_interface_list().
120
+ *
121
+ * @return ERROR_SUCCESS on success; Win32 error code otherwise
122
+ */
123
+DWORD
124
+tap_list_interfaces(
125
+    _In_opt_ HWND                        hwndParent,
126
+    _Out_    struct tap_interface_node **ppInterfaceList);
127
+
128
+
129
+/**
130
+ * Frees a list of network interfaces.
131
+ *
132
+ * @param pInterfaceList  A pointer to the first interface in the list to free.
133
+ */
134
+void
135
+tap_free_interface_list(
136
+    _In_ struct tap_interface_node *pInterfaceList);
137
+
138
+#endif
0 139
new file mode 100644
... ...
@@ -0,0 +1,10 @@
0
+<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
1
+<assembly xmlns='urn:schemas-microsoft-com:asm.v1' manifestVersion='1.0'>
2
+  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
3
+    <security>
4
+      <requestedPrivileges>
5
+        <requestedExecutionLevel level='requireAdministrator' uiAccess='false' />
6
+      </requestedPrivileges>
7
+    </security>
8
+  </trustInfo>
9
+</assembly>
0 10
new file mode 100644
... ...
@@ -0,0 +1,18 @@
0
+<?xml version="1.0" encoding="utf-8"?>
1
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
2
+  <ImportGroup Label="PropertySheets" />
3
+  <PropertyGroup Label="UserMacros" />
4
+  <PropertyGroup>
5
+    <GenerateManifest>false</GenerateManifest>
6
+  </PropertyGroup>
7
+  <ItemDefinitionGroup>
8
+    <ClCompile>
9
+      <PreprocessorDefinitions>_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
10
+      <AdditionalIncludeDirectories>..\compat;$(TAP_WINDOWS_HOME)/include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
11
+    </ClCompile>
12
+    <Link>
13
+      <SubSystem>Console</SubSystem>
14
+    </Link>
15
+  </ItemDefinitionGroup>
16
+  <ItemGroup />
17
+</Project>
0 18
\ No newline at end of file
1 19
new file mode 100644
... ...
@@ -0,0 +1,145 @@
0
+<?xml version="1.0" encoding="utf-8"?>
1
+<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
2
+  <ItemGroup Label="ProjectConfigurations">
3
+    <ProjectConfiguration Include="Debug|ARM64">
4
+      <Configuration>Debug</Configuration>
5
+      <Platform>ARM64</Platform>
6
+    </ProjectConfiguration>
7
+    <ProjectConfiguration Include="Debug|Win32">
8
+      <Configuration>Debug</Configuration>
9
+      <Platform>Win32</Platform>
10
+    </ProjectConfiguration>
11
+    <ProjectConfiguration Include="Debug|x64">
12
+      <Configuration>Debug</Configuration>
13
+      <Platform>x64</Platform>
14
+    </ProjectConfiguration>
15
+    <ProjectConfiguration Include="Release|ARM64">
16
+      <Configuration>Release</Configuration>
17
+      <Platform>ARM64</Platform>
18
+    </ProjectConfiguration>
19
+    <ProjectConfiguration Include="Release|Win32">
20
+      <Configuration>Release</Configuration>
21
+      <Platform>Win32</Platform>
22
+    </ProjectConfiguration>
23
+    <ProjectConfiguration Include="Release|x64">
24
+      <Configuration>Release</Configuration>
25
+      <Platform>x64</Platform>
26
+    </ProjectConfiguration>
27
+  </ItemGroup>
28
+  <PropertyGroup Label="Globals">
29
+    <VCProjectVersion>15.0</VCProjectVersion>
30
+    <ProjectGuid>{A06436E7-D576-490D-8BA0-0751D920334A}</ProjectGuid>
31
+    <Keyword>Win32Proj</Keyword>
32
+    <RootNamespace>tapctl</RootNamespace>
33
+    <WindowsTargetPlatformVersion>10.0.17134.0</WindowsTargetPlatformVersion>
34
+  </PropertyGroup>
35
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
36
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="Configuration">
37
+    <ConfigurationType>Application</ConfigurationType>
38
+    <UseDebugLibraries>true</UseDebugLibraries>
39
+    <PlatformToolset>v141</PlatformToolset>
40
+    <CharacterSet>Unicode</CharacterSet>
41
+    <WindowsSDKDesktopARM64Support>true</WindowsSDKDesktopARM64Support>
42
+  </PropertyGroup>
43
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
44
+    <ConfigurationType>Application</ConfigurationType>
45
+    <UseDebugLibraries>true</UseDebugLibraries>
46
+    <PlatformToolset>v141</PlatformToolset>
47
+    <CharacterSet>Unicode</CharacterSet>
48
+  </PropertyGroup>
49
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
50
+    <ConfigurationType>Application</ConfigurationType>
51
+    <UseDebugLibraries>true</UseDebugLibraries>
52
+    <PlatformToolset>v141</PlatformToolset>
53
+    <CharacterSet>Unicode</CharacterSet>
54
+  </PropertyGroup>
55
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="Configuration">
56
+    <ConfigurationType>Application</ConfigurationType>
57
+    <UseDebugLibraries>false</UseDebugLibraries>
58
+    <PlatformToolset>v141</PlatformToolset>
59
+    <WholeProgramOptimization>true</WholeProgramOptimization>
60
+    <CharacterSet>Unicode</CharacterSet>
61
+    <WindowsSDKDesktopARM64Support>true</WindowsSDKDesktopARM64Support>
62
+  </PropertyGroup>
63
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
64
+    <ConfigurationType>Application</ConfigurationType>
65
+    <UseDebugLibraries>false</UseDebugLibraries>
66
+    <PlatformToolset>v141</PlatformToolset>
67
+    <WholeProgramOptimization>true</WholeProgramOptimization>
68
+    <CharacterSet>Unicode</CharacterSet>
69
+  </PropertyGroup>
70
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
71
+    <ConfigurationType>Application</ConfigurationType>
72
+    <UseDebugLibraries>false</UseDebugLibraries>
73
+    <PlatformToolset>v141</PlatformToolset>
74
+    <WholeProgramOptimization>true</WholeProgramOptimization>
75
+    <CharacterSet>Unicode</CharacterSet>
76
+  </PropertyGroup>
77
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
78
+  <ImportGroup Label="ExtensionSettings">
79
+  </ImportGroup>
80
+  <ImportGroup Label="Shared">
81
+  </ImportGroup>
82
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
83
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
84
+    <Import Project="..\compat\Debug.props" />
85
+    <Import Project="tapctl.props" />
86
+  </ImportGroup>
87
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
88
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
89
+    <Import Project="..\compat\Debug.props" />
90
+    <Import Project="tapctl.props" />
91
+  </ImportGroup>
92
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
93
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
94
+    <Import Project="..\compat\Debug.props" />
95
+    <Import Project="tapctl.props" />
96
+  </ImportGroup>
97
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
98
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
99
+    <Import Project="..\compat\Release.props" />
100
+    <Import Project="tapctl.props" />
101
+  </ImportGroup>
102
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
103
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
104
+    <Import Project="..\compat\Release.props" />
105
+    <Import Project="tapctl.props" />
106
+  </ImportGroup>
107
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
108
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
109
+    <Import Project="..\compat\Release.props" />
110
+    <Import Project="tapctl.props" />
111
+  </ImportGroup>
112
+  <PropertyGroup Label="UserMacros" />
113
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" />
114
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" />
115
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" />
116
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" />
117
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" />
118
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" />
119
+  <ItemGroup>
120
+    <ClCompile Include="error.c" />
121
+    <ClCompile Include="tap.c" />
122
+    <ClCompile Include="main.c" />
123
+  </ItemGroup>
124
+  <ItemGroup>
125
+    <ClInclude Include="basic.h" />
126
+    <ClInclude Include="error.h" />
127
+    <ClInclude Include="tap.h" />
128
+  </ItemGroup>
129
+  <ItemGroup>
130
+    <ResourceCompile Include="tapctl_resources.rc" />
131
+  </ItemGroup>
132
+  <ItemGroup>
133
+    <ProjectReference Include="..\..\build\msvc\msvc-generate\msvc-generate.vcxproj">
134
+      <Project>{8598c2c8-34c4-47a1-99b0-7c295a890615}</Project>
135
+      <ReferenceOutputAssembly>false</ReferenceOutputAssembly>
136
+    </ProjectReference>
137
+  </ItemGroup>
138
+  <ItemGroup>
139
+    <Manifest Include="tapctl.exe.manifest" />
140
+  </ItemGroup>
141
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
142
+  <ImportGroup Label="ExtensionTargets">
143
+  </ImportGroup>
144
+</Project>
0 145
\ No newline at end of file
1 146
new file mode 100644
... ...
@@ -0,0 +1,49 @@
0
+<?xml version="1.0" encoding="utf-8"?>
1
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
2
+  <ItemGroup>
3
+    <Filter Include="Source Files">
4
+      <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
5
+      <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
6
+    </Filter>
7
+    <Filter Include="Header Files">
8
+      <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
9
+      <Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
10
+    </Filter>
11
+    <Filter Include="Resource Files">
12
+      <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
13
+      <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
14
+    </Filter>
15
+  </ItemGroup>
16
+  <ItemGroup>
17
+    <ClCompile Include="tap.c">
18
+      <Filter>Source Files</Filter>
19
+    </ClCompile>
20
+    <ClCompile Include="main.c">
21
+      <Filter>Source Files</Filter>
22
+    </ClCompile>
23
+    <ClCompile Include="error.c">
24
+      <Filter>Source Files</Filter>
25
+    </ClCompile>
26
+  </ItemGroup>
27
+  <ItemGroup>
28
+    <ClInclude Include="tap.h">
29
+      <Filter>Header Files</Filter>
30
+    </ClInclude>
31
+    <ClInclude Include="error.h">
32
+      <Filter>Header Files</Filter>
33
+    </ClInclude>
34
+    <ClInclude Include="basic.h">
35
+      <Filter>Header Files</Filter>
36
+    </ClInclude>
37
+  </ItemGroup>
38
+  <ItemGroup>
39
+    <ResourceCompile Include="tapctl_resources.rc">
40
+      <Filter>Resource Files</Filter>
41
+    </ResourceCompile>
42
+  </ItemGroup>
43
+  <ItemGroup>
44
+    <Manifest Include="tapctl.exe.manifest">
45
+      <Filter>Resource Files</Filter>
46
+    </Manifest>
47
+  </ItemGroup>
48
+</Project>
0 49
\ No newline at end of file
1 50
new file mode 100644
... ...
@@ -0,0 +1,64 @@
0
+/*
1
+ *  tapctl -- Utility to manipulate TUN/TAP interfaces on Windows
2
+ *
3
+ *  Copyright (C) 2018 Simon Rozman <simon@rozman.si>
4
+ *
5
+ *  This program is free software; you can redistribute it and/or modify
6
+ *  it under the terms of the GNU General Public License version 2
7
+ *  as published by the Free Software Foundation.
8
+ *
9
+ *  This program is distributed in the hope that it will be useful,
10
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
+ *  GNU General Public License for more details.
13
+ *
14
+ *  You should have received a copy of the GNU General Public License along
15
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
16
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
+ */
18
+
19
+#ifdef HAVE_CONFIG_H
20
+#include <config.h>
21
+#else
22
+#include <config-msvc-version.h>
23
+#endif
24
+#include <winresrc.h>
25
+
26
+#pragma code_page(65001) /* UTF8 */
27
+
28
+LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
29
+
30
+VS_VERSION_INFO VERSIONINFO
31
+    FILEVERSION OPENVPN_VERSION_RESOURCE
32
+    PRODUCTVERSION OPENVPN_VERSION_RESOURCE
33
+    FILEFLAGSMASK VS_FF_DEBUG | VS_FF_PRERELEASE | VS_FF_PATCHED | VS_FF_PRIVATEBUILD | VS_FF_SPECIALBUILD
34
+#ifdef _DEBUG
35
+    FILEFLAGS VS_FF_DEBUG
36
+#else
37
+    FILEFLAGS 0x0L
38
+#endif
39
+    FILEOS VOS_NT_WINDOWS32
40
+    FILETYPE VFT_APP
41
+    FILESUBTYPE 0x0L
42
+BEGIN
43
+    BLOCK "StringFileInfo"
44
+    BEGIN
45
+        BLOCK "040904b0"
46
+        BEGIN
47
+            VALUE "CompanyName", "The OpenVPN Project"
48
+            VALUE "FileDescription", "Utility to manipulate TUN/TAP interfaces on Windows"
49
+            VALUE "FileVersion", PACKAGE_VERSION ".0"
50
+            VALUE "InternalName", "OpenVPN"
51
+            VALUE "LegalCopyright", "Copyright © The OpenVPN Project"
52
+            VALUE "OriginalFilename", "tapctl.exe"
53
+            VALUE "ProductName", "OpenVPN"
54
+            VALUE "ProductVersion", PACKAGE_VERSION ".0"
55
+        END
56
+    END
57
+    BLOCK "VarFileInfo"
58
+    BEGIN
59
+        VALUE "Translation", 0x409, 1200
60
+    END
61
+END
62
+
63
+1 RT_MANIFEST "tapctl.exe.manifest"