OpenVPN
test_openvpnserv.c
Go to the documentation of this file.
1/*
2 * OpenVPN -- An application to securely tunnel IP networks
3 * over a single UDP port, with support for SSL/TLS-based
4 * session authentication and key exchange,
5 * packet encryption, packet authentication, and
6 * packet compression.
7 *
8 * Copyright (C) 2025 Frank Lichtenheld <frank@lichtenheld.com>
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by the
12 * Free Software Foundation, either version 2 of the License,
13 * or (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License along
21 * with this program; if not, see <https://www.gnu.org/licenses/>.
22 */
23
24#ifdef HAVE_CONFIG_H
25#include "config.h"
26#endif
27
28#include <setjmp.h>
29#include <cmocka.h>
30#include "test_common.h"
31
32#include <winsock2.h>
33#include <windows.h>
34
35#include "interactive.c"
36
37BOOL
38ReportStatusToSCMgr(SERVICE_STATUS_HANDLE service, SERVICE_STATUS *status)
39{
40 return TRUE;
41}
42
43static void
45{
46 PCWSTR domain = L"openvpn.net";
47 size_t domain_len = wcslen(domain);
48 assert_true(ListContainsDomain(domain, domain, domain_len));
49 assert_true(ListContainsDomain(L"openvpn.com,openvpn.net", domain, domain_len));
50 assert_true(ListContainsDomain(L"openvpn.net,openvpn.com", domain, domain_len));
51 assert_true(ListContainsDomain(L"openvpn.org,openvpn.net,openvpn.com", domain, domain_len));
52
53 assert_false(ListContainsDomain(L"openvpn.com", domain, domain_len));
54 assert_false(ListContainsDomain(L"internal.openvpn.net", domain, domain_len));
55 assert_false(ListContainsDomain(L"openvpn.com,internal.openvpn.net", domain, domain_len));
56 assert_false(ListContainsDomain(L"internal.openvpn.net,openvpn.com", domain, domain_len));
57}
58
59#define BUF_SIZE 64
60static void
62{
63 DWORD size, len;
64 LSTATUS err;
65 const DWORD glyph_size = sizeof(wchar_t);
66
67 /* Remove the domain from a single-entry list */
68 wchar_t domains_0[BUF_SIZE] = L"openvpn.com";
69 len = (DWORD)wcslen(domains_0) + 1;
70 size = len * glyph_size;
71 err = ConvertItfDnsDomains(L"openvpn.com", domains_0, &size, sizeof(domains_0));
72 assert_int_equal(size, 0);
73 assert_int_equal(err, ERROR_FILE_NOT_FOUND);
74
75 /* Remove no domain from a single-entry list */
76 wchar_t domains_1[BUF_SIZE] = L"openvpn.com";
77 len = (DWORD)wcslen(domains_1) + 1;
78 size = len * glyph_size;
79 wchar_t domains_1_res[] = L".openvpn.com\0";
80 err = ConvertItfDnsDomains(L"openvpn.net", domains_1, &size, sizeof(domains_1));
81 assert_memory_equal(domains_1, domains_1_res, size);
82 assert_int_equal(size, sizeof(domains_1_res));
83 assert_int_equal(err, NO_ERROR);
84
85 /* Remove the second domain from a two-entry list */
86 wchar_t domains_2[BUF_SIZE] = L"openvpn.com,openvpn.net";
87 len = (DWORD)wcslen(domains_2) + 1;
88 size = len * glyph_size;
89 wchar_t domains_2_res[] = L".openvpn.com\0";
90 err = ConvertItfDnsDomains(L"openvpn.net", domains_2, &size, sizeof(domains_2));
91 assert_memory_equal(domains_2, domains_2_res, size);
92 assert_int_equal(size, sizeof(domains_2_res));
93 assert_int_equal(err, NO_ERROR);
94
95 /* Remove the first domain from a two-entry list */
96 wchar_t domains_3[BUF_SIZE] = L"openvpn.com,openvpn.net";
97 len = (DWORD)wcslen(domains_3) + 1;
98 size = len * glyph_size;
99 wchar_t domains_3_res[] = L".openvpn.net\0";
100 err = ConvertItfDnsDomains(L"openvpn.com", domains_3, &size, sizeof(domains_3));
101 assert_memory_equal(domains_3, domains_3_res, size);
102 assert_int_equal(size, sizeof(domains_3_res));
103 assert_int_equal(err, NO_ERROR);
104
105 /* Remove no domain from a two-entry list */
106 wchar_t domains_4[BUF_SIZE] = L"openvpn.com,openvpn.net";
107 len = (DWORD)wcslen(domains_4) + 1;
108 size = len * glyph_size;
109 wchar_t domains_4_res[] = L".openvpn.com\0.openvpn.net\0";
110 err = ConvertItfDnsDomains(NULL, domains_4, &size, sizeof(domains_4));
111 assert_memory_equal(domains_4, domains_4_res, size);
112 assert_int_equal(size, sizeof(domains_4_res));
113 assert_int_equal(err, NO_ERROR);
114
115 /* Remove the first domain from a three-entry list */
116 wchar_t domains_5[BUF_SIZE] = L"openvpn.com,openvpn.net,openvpn.org";
117 len = (DWORD)wcslen(domains_5) + 1;
118 size = len * glyph_size;
119 wchar_t domains_5_res[] = L".openvpn.net\0.openvpn.org\0";
120 err = ConvertItfDnsDomains(L"openvpn.com", domains_5, &size, sizeof(domains_5));
121 assert_memory_equal(domains_5, domains_5_res, size);
122 assert_int_equal(size, sizeof(domains_5_res));
123 assert_int_equal(err, NO_ERROR);
124
125 /* Remove the middle domain from a three-entry list */
126 wchar_t domains_6[BUF_SIZE] = L"openvpn.com,openvpn.net,openvpn.org";
127 len = (DWORD)wcslen(domains_6) + 1;
128 size = len * glyph_size;
129 wchar_t domains_6_res[] = L".openvpn.com\0.openvpn.org\0";
130 err = ConvertItfDnsDomains(L"openvpn.net", domains_6, &size, sizeof(domains_6));
131 assert_memory_equal(domains_6, domains_6_res, size);
132 assert_int_equal(size, sizeof(domains_6_res));
133 assert_int_equal(err, NO_ERROR);
134
135 /* Remove the last domain from a three-entry list */
136 wchar_t domains_7[BUF_SIZE] = L"openvpn.com,openvpn.net,openvpn.org";
137 len = (DWORD)wcslen(domains_7) + 1;
138 size = len * glyph_size;
139 wchar_t domains_7_res[] = L".openvpn.com\0.openvpn.net\0";
140 err = ConvertItfDnsDomains(L"openvpn.org", domains_7, &size, sizeof(domains_7));
141 assert_memory_equal(domains_7, domains_7_res, size);
142 assert_int_equal(size, sizeof(domains_7_res));
143 assert_int_equal(err, NO_ERROR);
144
145 /* Remove the last domain from a four-entry list because of size constraints*/
146 wchar_t domains_8[BUF_SIZE] = L"openvpn.com,openvpn.net,openvpn.org,am-ende-noch-eine-lange.de";
147 len = (DWORD)wcslen(domains_8) + 1;
148 size = len * glyph_size;
149 wchar_t domains_8_res[] = L".openvpn.com\0.openvpn.net\0.openvpn.org\0";
150 err = ConvertItfDnsDomains(NULL, domains_8, &size, sizeof(domains_8));
151 assert_memory_equal(domains_8, domains_8_res, size);
152 assert_int_equal(size, sizeof(domains_8_res));
153 assert_int_equal(err, ERROR_MORE_DATA);
154}
155
156static void
158{
159 WCHAR list[64];
160
161 /* append to an empty list */
162 wcscpy(list, L"");
163 assert_true(AppendSearchList(list, _countof(list), L"a.com"));
164 assert_int_equal(wcscmp(list, L"a.com"), 0);
165
166 /* append to a non-empty list */
167 wcscpy(list, L"x.com");
168 assert_true(AppendSearchList(list, _countof(list), L"a.com,b.com"));
169 assert_int_equal(wcscmp(list, L"x.com,a.com,b.com"), 0);
170
171 /* appending an empty string is a no-op */
172 wcscpy(list, L"x.com");
173 assert_true(AppendSearchList(list, _countof(list), L""));
174 assert_int_equal(wcscmp(list, L"x.com"), 0);
175
176 /* duplicates are allowed -- relied on by the multiset-aware remove */
177 wcscpy(list, L"x.com");
178 assert_true(AppendSearchList(list, _countof(list), L"x.com"));
179 assert_int_equal(wcscmp(list, L"x.com,x.com"), 0);
180
181 /* overflow: the list is left unchanged */
182 WCHAR tiny[8];
183 wcscpy(tiny, L"abc");
184 assert_false(AppendSearchList(tiny, _countof(tiny), L"long.example.com"));
185 assert_int_equal(wcscmp(tiny, L"abc"), 0);
186}
187
188static void
190{
191 WCHAR list[256];
192
193 /* substring rejection: removing "vpn.corp.local" does NOT touch
194 * "test.vpn.corp.local" -- the original wcsstr-based code did. */
195 wcscpy(list, L"test.vpn.corp.local,other.com,vpn.corp.local");
196 assert_int_equal(RemoveSearchListTokens(list, L"vpn.corp.local"), 1);
197 assert_int_equal(wcscmp(list, L"test.vpn.corp.local,other.com"), 0);
198
199 /* multiset semantics: one undo token removes one occurrence */
200 wcscpy(list, L"corp.local,corp.local");
201 assert_int_equal(RemoveSearchListTokens(list, L"corp.local"), 1);
202 assert_int_equal(wcscmp(list, L"corp.local"), 0);
203
204 /* removal targets the LAST occurrence so a pre-existing prefix is
205 * preserved: with initial list "a,b" a VPN append yields "a,b,a";
206 * removing "a" must restore "a,b" (not "b,a") so the result matches
207 * the stored initial snapshot and a full reset can be triggered */
208 wcscpy(list, L"a,b,a");
209 assert_int_equal(RemoveSearchListTokens(list, L"a"), 1);
210 assert_int_equal(wcscmp(list, L"a,b"), 0);
211
212 /* multiple removes, mixed positions */
213 wcscpy(list, L"a,b,c,d");
214 assert_int_equal(RemoveSearchListTokens(list, L"b,d"), 2);
215 assert_int_equal(wcscmp(list, L"a,c"), 0);
216
217 /* no matches: list unchanged */
218 wcscpy(list, L"a,b,c");
219 assert_int_equal(RemoveSearchListTokens(list, L"x,y"), 0);
220 assert_int_equal(wcscmp(list, L"a,b,c"), 0);
221
222 /* head removal */
223 wcscpy(list, L"a,b,c");
224 assert_int_equal(RemoveSearchListTokens(list, L"a"), 1);
225 assert_int_equal(wcscmp(list, L"b,c"), 0);
226
227 /* tail removal */
228 wcscpy(list, L"a,b,c");
229 assert_int_equal(RemoveSearchListTokens(list, L"c"), 1);
230 assert_int_equal(wcscmp(list, L"a,b"), 0);
231
232 /* remove every token */
233 wcscpy(list, L"a,b");
234 assert_int_equal(RemoveSearchListTokens(list, L"a,b"), 2);
235 assert_int_equal(wcscmp(list, L""), 0);
236
237 /* duplicate undo tokens drain multiple occurrences */
238 wcscpy(list, L"x,x,x");
239 assert_int_equal(RemoveSearchListTokens(list, L"x,x"), 2);
240 assert_int_equal(wcscmp(list, L"x"), 0);
241
242 /* asking to remove more than is present only removes what's there */
243 wcscpy(list, L"x");
244 assert_int_equal(RemoveSearchListTokens(list, L"x,x,x"), 1);
245 assert_int_equal(wcscmp(list, L""), 0);
246
247 /* empty list */
248 wcscpy(list, L"");
249 assert_int_equal(RemoveSearchListTokens(list, L"a"), 0);
250 assert_int_equal(wcscmp(list, L""), 0);
251
252 /* empty remove */
253 wcscpy(list, L"a,b");
254 assert_int_equal(RemoveSearchListTokens(list, L""), 0);
255 assert_int_equal(wcscmp(list, L"a,b"), 0);
256
257 /* stray empty tokens in the list are tolerated -- the splice only
258 * touches the token being removed (and one adjacent comma) */
259 wcscpy(list, L"a,,b");
260 assert_int_equal(RemoveSearchListTokens(list, L"b"), 1);
261 assert_int_equal(wcscmp(list, L"a,"), 0);
262}
263
264int
265wmain(void)
266{
268 const struct CMUnitTest tests[] = {
269 cmocka_unit_test(test_list_contains_domain),
270 cmocka_unit_test(test_convert_itf_dns_domains),
271 cmocka_unit_test(test_append_search_list),
272 cmocka_unit_test(test_remove_search_list_tokens),
273 };
274
275 int ret = cmocka_run_group_tests_name("openvpnserv tests", tests, NULL, NULL);
276
277 return ret;
278}
static SERVICE_STATUS status
Definition interactive.c:52
static BOOL AppendSearchList(PWSTR list, size_t list_cap, PCWSTR add)
Append a comma-separated list of domains to another comma-separated list, in place.
static size_t RemoveSearchListTokens(PWSTR list, PCWSTR remove)
Remove tokens from a comma-separated search list with multiset semantics: for each comma-separated to...
static BOOL ListContainsDomain(PCWSTR list, PCWSTR domain, size_t len)
Check if a domain is contained in a comma separated list of domains.
static LSTATUS ConvertItfDnsDomains(PCWSTR search_domains, PWSTR domains, PDWORD size, const DWORD capacity)
Convert interface specific domain suffix(es) from comma-separated string to MULTI_SZ string.
static SERVICE_STATUS_HANDLE service
Definition interactive.c:51
static void openvpn_unit_test_setup(void)
Sets up the environment for unit tests like making both stderr and stdout non-buffered to avoid messa...
Definition test_common.h:61
int wmain(void)
static void test_list_contains_domain(void **state)
static void test_convert_itf_dns_domains(void **state)
#define BUF_SIZE
static void test_remove_search_list_tokens(void **state)
BOOL ReportStatusToSCMgr(SERVICE_STATUS_HANDLE service, SERVICE_STATUS *status)
static void test_append_search_list(void **state)