OpenVPN
test_misc.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) 2021-2026 Arne Schwabe <arne@rfc2549.org>
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 version 2
12 * as published by the Free Software Foundation.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License along
20 * with this program; if not, see <https://www.gnu.org/licenses/>.
21 */
22
23#ifdef HAVE_CONFIG_H
24#include "config.h"
25#endif
26
27#include "syshead.h"
28
29#include <stdio.h>
30#include <stdlib.h>
31#include <stdarg.h>
32#include <string.h>
33#include <setjmp.h>
34#include <cmocka.h>
35
36#include "ssl_util.h"
37#include "options_util.h"
38#include "test_common.h"
39#include "list.h"
40#include "mock_msg.h"
41#include "crypto.h"
42#ifdef _WIN32
43#include "win32-util.h"
44#endif
45#include "test_schedule.h"
46
47
48static void
50{
51 struct gc_arena gc = gc_new();
52
53 const char *input =
54 "V4,dev-type tun,link-mtu 1457,tun-mtu 1400,proto UDPv4,auth SHA1,keysize 128,key-method 2,tls-server";
55
56 const char *output = options_string_compat_lzo(input, &gc);
57
58 assert_string_equal(
59 output,
60 "V4,dev-type tun,link-mtu 1458,tun-mtu 1400,proto UDPv4,auth SHA1,keysize 128,key-method 2,tls-server,comp-lzo");
61
62 /* This string is has a much too small link-mtu so we should fail on it" */
63 input =
64 "V4,dev-type tun,link-mtu 2,tun-mtu 1400,proto UDPv4,auth SHA1,keysize 128,key-method 2,tls-server";
65
66 output = options_string_compat_lzo(input, &gc);
67
68 assert_string_equal(input, output);
69
70 /* not matching at all */
71 input = "V4,dev-type tun";
72 output = options_string_compat_lzo(input, &gc);
73
74 assert_string_equal(input, output);
75
76
77 input =
78 "V4,dev-type tun,link-mtu 999,tun-mtu 1400,proto UDPv4,auth SHA1,keysize 128,key-method 2,tls-server";
79 output = options_string_compat_lzo(input, &gc);
80
81 /* 999 -> 1000, 3 to 4 chars */
82 assert_string_equal(
83 output,
84 "V4,dev-type tun,link-mtu 1000,tun-mtu 1400,proto UDPv4,auth SHA1,keysize 128,key-method 2,tls-server,comp-lzo");
85
86 gc_free(&gc);
87}
88
89static void
91{
92 struct options o;
93
94 const char *teststr = "TEMP:There are no flags here [really not]";
95
96 const char *msg = parse_auth_failed_temp(&o, teststr + strlen("TEMP"));
97 assert_string_equal(msg, "There are no flags here [really not]");
98}
99
100static void
102{
103 struct options o;
104
105 const char *teststr = "[backoff 42,advance no]";
106
107 const char *msg = parse_auth_failed_temp(&o, teststr);
108 assert_string_equal(msg, "");
109 assert_int_equal(o.server_backoff_time, 42);
110 assert_true(o.no_advance);
111}
112
113static void
115{
116 struct options o;
117
118 const char *teststr = "[advance remote,backoff 77]:go round and round";
119
120 const char *msg = parse_auth_failed_temp(&o, teststr);
121 assert_string_equal(msg, "go round and round");
122 assert_int_equal(o.server_backoff_time, 77);
123}
124
125
126struct word
127{
128 const char *word;
129 int n;
130};
131
132
133static uint64_t
134word_hash_function(const void *key, const uint8_t hash_key[HASH_KEY_LEN])
135{
136 const char *str = (const char *)key;
137 const uint32_t len = (uint32_t)strlen(str);
138 return hash_func((const uint8_t *)str, len, *(uint32_t *)(hash_key));
139}
140
141static bool
142word_compare_function(const void *key1, const void *key2)
143{
144 return strcmp((const char *)key1, (const char *)key2) == 0;
145}
146
147static struct hash_element *
149{
150 struct hash_iterator hi;
151 struct hash_element *he;
152 struct hash_element *ret = NULL;
154
155 while ((he = hash_iterator_next(&hi)))
156 {
157 if (he->value == value)
158 {
159 ret = he;
160 }
161 }
163 return ret;
164}
165
166static void
167test_list(void **state)
168{
169 /*
170 * Test the hash code by implementing a simple
171 * word frequency algorithm.
172 */
173 struct gc_arena gc = gc_new();
176
177 printf("hash_init n_buckets=%u mask=0x%08x\n", hash->n_buckets, hash->mask);
178
179 char wordfile[PATH_MAX] = { 0 };
180 openvpn_test_get_srcdir_dir(wordfile, PATH_MAX, "/../../../COPYRIGHT.GPL");
181
182 FILE *words = fopen(wordfile, "r");
183 assert_non_null(words);
184
185 int wordcount = 0;
186
187 /* parse words from file */
188 while (true)
189 {
190 char buf[256];
191 char wordbuf[256];
192
193 if (!fgets(buf, sizeof(buf), words))
194 {
195 break;
196 }
197
198 char c = 0;
199 int bi = 0, wbi = 0;
200
201 do
202 {
203 c = buf[bi++];
204 if (isalnum(c) || c == '_')
205 {
206 assert_true(wbi < (int)sizeof(wordbuf));
207 wordbuf[wbi++] = c;
208 }
209 else
210 {
211 if (wbi)
212 {
213 wordcount++;
214
215 ASSERT(wbi < (int)sizeof(wordbuf));
216 wordbuf[wbi++] = '\0';
217
218 /* word is parsed from stdin */
219
220 /* does it already exist in table? */
221 struct word *w = (struct word *)hash_lookup(hash, wordbuf);
222
223 if (w)
224 {
225 assert_string_equal(w->word, wordbuf);
226 /* yes, increment count */
227 ++w->n;
228 }
229 else
230 {
231 /* no, make a new object */
232 ALLOC_OBJ_GC(w, struct word, &gc);
233 w->word = string_alloc(wordbuf, &gc);
234 w->n = 1;
235 assert_true(hash_add(hash, w->word, w, false));
236 assert_true(hash_add(nhash, w->word,
237 (void *)((ptr_type)(random() & 0x0F) + 1), false));
238 }
239 }
240 wbi = 0;
241 }
242 } while (c);
243 }
244
245 assert_int_equal(wordcount, 2971);
246
247 /* remove some words from the table */
248 {
249 assert_true(hash_remove(hash, "DEFECTIVE"));
250 assert_false(hash_remove(hash, "false"));
251 }
252
253 /* output contents of hash table */
254 {
255 uint32_t inc = 0;
256 int count = 0;
257
258 for (uint32_t base = 0; base < hash_n_buckets(hash); base += inc)
259 {
260 struct hash_iterator hi;
261 struct hash_element *he;
262 inc = ((uint32_t)get_random() % 3) + 1;
263 hash_iterator_init_range(hash, &hi, base, base + inc);
264
265 while ((he = hash_iterator_next(&hi)))
266 {
267 struct word *w = (struct word *)he->value;
268 /*printf("%6d '%s'\n", w->n, w->word); */
269 ++count;
270 /* check a few words to match prior results */
271 if (!strcmp(w->word, "is"))
272 {
273 assert_int_equal(w->n, 49);
274 }
275 else if (!strcmp(w->word, "redistribute"))
276 {
277 assert_int_equal(w->n, 5);
278 }
279 else if (!strcmp(w->word, "circumstances"))
280 {
281 assert_int_equal(w->n, 1);
282 }
283 else if (!strcmp(w->word, "so"))
284 {
285 assert_int_equal(w->n, 8);
286 }
287 else if (!strcmp(w->word, "BECAUSE"))
288 {
289 assert_int_equal(w->n, 1);
290 }
291 }
292
294 }
295 assert_int_equal(count, hash_n_elements(hash));
296 }
297
298 /* test hash_remove_by_value function */
299 {
300 for (ptr_type i = 1; i <= 16; ++i)
301 {
302 struct hash_element *item = hash_lookup_by_value(nhash, (void *)i);
303 hash_remove_by_value(nhash, (void *)i);
304 /* check item got removed if it was present before */
305 if (item)
306 {
307 assert_null(hash_lookup_by_value(nhash, (void *)i));
308 }
309 }
310 }
311
313 hash_free(nhash);
314 gc_free(&gc);
315}
316
317static void
319{
320 assert_true(valid_integer("1234", true));
321 assert_true(valid_integer("1234", false));
322 assert_true(valid_integer("0", false));
323 assert_true(valid_integer("0", true));
324 assert_true(valid_integer("-777", false));
325 assert_false(valid_integer("-777", true));
326
327 assert_false(valid_integer("-777foo", false));
328 assert_false(valid_integer("-777foo", true));
329
330 assert_false(valid_integer("foo777", true));
331 assert_false(valid_integer("foo777", false));
332
333 /* 2**31 + 5 , just outside of signed int range */
334 assert_false(valid_integer("2147483653", true));
335 assert_false(valid_integer("2147483653", false));
336 assert_false(valid_integer("-2147483653", true));
337 assert_false(valid_integer("-2147483653", false));
338
339
340 msglvl_t msglevel = D_LOW;
341 msglvl_t saved_log_level = mock_get_debug_level();
343
344 /* check happy path */
345 assert_int_equal(positive_atoi("1234", msglevel), 1234);
346 assert_int_equal(positive_atoi("0", msglevel), 0);
347
348 assert_int_equal(atoi_warn("1234", msglevel), 1234);
349 assert_int_equal(atoi_warn("0", msglevel), 0);
350 assert_int_equal(atoi_warn("-1194", msglevel), -1194);
351
352 int parameter = 0;
353 assert_true(atoi_constrained("1234", &parameter, "test", 0, INT_MAX, msglevel));
354 assert_int_equal(parameter, 1234);
355 assert_true(atoi_constrained("0", &parameter, "test", -1, 0, msglevel));
356 assert_int_equal(parameter, 0);
357 assert_true(atoi_constrained("-1194", &parameter, "test", INT_MIN, INT_MAX, msglevel));
358 assert_int_equal(parameter, -1194);
359
360 int64_t parameter64 = 0;
361 assert_true(positive_atoll("1234", &parameter64, "test", msglevel));
362 assert_int_equal(parameter64, 1234);
363 assert_true(positive_atoll("0", &parameter64, "test", msglevel));
364 assert_int_equal(parameter64, 0);
365 assert_true(positive_atoll("2147483653", &parameter64, "test", msglevel));
366 assert_int_equal(parameter64, 2147483653);
367 /* overflow gets capped to LLONG_MAX */
368 assert_true(positive_atoll("9223372036854775810", &parameter64, "test", msglevel));
369 assert_int_equal(parameter64, 9223372036854775807);
370
372 assert_int_equal(positive_atoi("-1234", msglevel), 0);
373 assert_string_equal(mock_msg_buf, "Cannot parse argument '-1234' as non-negative integer");
374
375 /* 2**31 + 5 , just outside of signed int range */
377 assert_int_equal(positive_atoi("2147483653", msglevel), 0);
378 assert_string_equal(mock_msg_buf, "Cannot parse argument '2147483653' as non-negative integer");
379
381 assert_int_equal(atoi_warn("2147483653", msglevel), 0);
382 assert_string_equal(mock_msg_buf, "Cannot parse argument '2147483653' as integer");
383
385 parameter = -42;
386 assert_false(atoi_constrained("2147483653", &parameter, "test", 0, INT_MAX, msglevel));
387 assert_string_equal(mock_msg_buf, "test: Cannot parse '2147483653' as integer");
388 assert_int_equal(parameter, -42);
389
391 assert_int_equal(positive_atoi("foo77", msglevel), 0);
392 assert_string_equal(mock_msg_buf, "Cannot parse argument 'foo77' as non-negative integer");
393
395 assert_int_equal(positive_atoi("77foo", msglevel), 0);
396 assert_string_equal(mock_msg_buf, "Cannot parse argument '77foo' as non-negative integer");
397
399 parameter = -42;
400 assert_false(atoi_constrained("foo77", &parameter, "test", 0, INT_MAX, msglevel));
401 assert_string_equal(mock_msg_buf, "test: Cannot parse 'foo77' as integer");
402 assert_int_equal(parameter, -42);
403
405 parameter = -42;
406 assert_false(atoi_constrained("77foo", &parameter, "test", 0, INT_MAX, msglevel));
407 assert_string_equal(mock_msg_buf, "test: Cannot parse '77foo' as integer");
408 assert_int_equal(parameter, -42);
409
411 assert_int_equal(atoi_warn("foo77", msglevel), 0);
412 assert_string_equal(mock_msg_buf, "Cannot parse argument 'foo77' as integer");
413
415 assert_int_equal(atoi_warn("77foo", msglevel), 0);
416 assert_string_equal(mock_msg_buf, "Cannot parse argument '77foo' as integer");
417
418 /* special tests for _constrained */
420 parameter = -42;
421 assert_false(atoi_constrained("77", &parameter, "test", 0, 76, msglevel));
422 assert_string_equal(mock_msg_buf, "test: Must be an integer between 0 and 76, not 77");
423 assert_int_equal(parameter, -42);
424
426 parameter = -42;
427 assert_false(atoi_constrained("-77", &parameter, "test", -76, 76, msglevel));
428 assert_string_equal(mock_msg_buf, "test: Must be an integer between -76 and 76, not -77");
429 assert_int_equal(parameter, -42);
430
432 parameter = -42;
433 assert_false(atoi_constrained("-77", &parameter, "test", 0, INT_MAX, msglevel));
434 assert_string_equal(mock_msg_buf, "test: Must be an integer >= 0, not -77");
435 assert_int_equal(parameter, -42);
436
438 parameter = -42;
439 assert_false(atoi_constrained("0", &parameter, "test", 1, INT_MAX, msglevel));
440 assert_string_equal(mock_msg_buf, "test: Must be an integer >= 1, not 0");
441 assert_int_equal(parameter, -42);
442
443 mock_set_debug_level(saved_log_level);
444}
445
446#ifdef _WIN32
447static void
449{
450 /* plugin/install dir without trailing separator */
451 assert_true(win_path_in_dir(L"C:\\openvpn_plugins\\foo.dll", L"C:\\openvpn_plugins"));
452
453 /* the bug being fixed: a sibling dir sharing the prefix must NOT match */
454 assert_false(win_path_in_dir(L"C:\\openvpn_plugins_evil\\foo.dll", L"C:\\openvpn_plugins"));
455
456 /* trusted dir with trailing separator */
457 assert_true(win_path_in_dir(L"C:\\openvpn_plugins\\foo.dll", L"C:\\openvpn_plugins\\"));
458 assert_false(win_path_in_dir(L"C:\\openvpn_plugins_evil\\foo.dll", L"C:\\openvpn_plugins\\"));
459
460 /* forward slash separator in the candidate path is accepted */
461 assert_true(win_path_in_dir(L"C:\\openvpn_plugins/foo.dll", L"C:\\openvpn_plugins"));
462
463 /* comparison is case-insensitive */
464 assert_true(win_path_in_dir(L"c:\\OPENVPN_PLUGINS\\foo.dll", L"C:\\openvpn_plugins"));
465
466 /* the directory itself (no trailing component) is not "in" the directory */
467 assert_false(win_path_in_dir(L"C:\\openvpn_plugins", L"C:\\openvpn_plugins"));
468
469 /* nested subdirectories are still inside */
470 assert_true(win_path_in_dir(L"C:\\openvpn_plugins\\sub\\foo.dll", L"C:\\openvpn_plugins"));
471
472 /* an empty trusted dir never matches */
473 assert_false(win_path_in_dir(L"C:\\openvpn_plugins\\foo.dll", L""));
474}
475#endif /* _WIN32 */
476
477const struct CMUnitTest misc_tests[] = {
478#ifdef _WIN32
479 cmocka_unit_test(test_win_path_in_dir),
480#endif
481 cmocka_unit_test(test_compat_lzo_string),
482 cmocka_unit_test(test_auth_fail_temp_no_flags),
483 cmocka_unit_test(test_auth_fail_temp_flags),
484 cmocka_unit_test(test_auth_fail_temp_flags_msg),
485 cmocka_unit_test(test_list),
486 cmocka_unit_test(test_atoi_variants),
487 cmocka_unit_test(schedule_test)
488};
489
490int
491main(void)
492{
494 return cmocka_run_group_tests(misc_tests, NULL, NULL);
495}
char * string_alloc(const char *str, struct gc_arena *gc)
Duplicate a string, allocating memory under garbage collection.
Definition buffer.c:616
#define ALLOC_OBJ_GC(dptr, type, gc)
Allocate a garbage-collected object of the given type (uninitialised).
Definition buffer.h:2055
static void gc_free(struct gc_arena *a)
Free all allocations in a garbage collection arena.
Definition buffer.h:1909
static struct gc_arena gc_new(void)
Allocate and return a new, empty garbage collection arena.
Definition buffer.h:1893
unsigned long ptr_type
Definition common.h:59
int64_t get_random(void)
an analogue to the random() function, but use prng_bytes and also int64_t instead of long to avoid LL...
Definition crypto.c:1737
Data Channel Cryptography Module.
#define D_LOW
Definition errlevel.h:96
void hash_iterator_free(struct hash_iterator *hi)
Definition list.c:275
struct hash_element * hash_iterator_next(struct hash_iterator *hi)
Definition list.c:281
void hash_iterator_init(struct hash *hash, struct hash_iterator *hi)
Definition list.c:239
struct hash * hash_init(const uint32_t n_buckets, uint64_t(*hash_function)(const void *key, const uint8_t hash_key[HASH_KEY_LEN]), bool(*compare_function)(const void *key1, const void *key2))
Definition list.c:39
void hash_free(struct hash *hash)
Definition list.c:65
bool hash_add(struct hash *hash, const void *key, void *value, bool replace)
Definition list.c:142
void hash_remove_by_value(struct hash *hash, void *value)
Definition list.c:170
void hash_iterator_init_range(struct hash *hash, struct hash_iterator *hi, uint32_t start_bucket, uint32_t end_bucket)
Definition list.c:218
uint64_t hash_func(const uint8_t *k, uint32_t length, uint32_t initval)
Definition list.c:421
static bool hash_remove(struct hash *hash, const void *key)
Definition list.h:166
static void * hash_lookup(struct hash *hash, const void *key)
Definition list.h:133
#define HASH_KEY_LEN
Definition list.h:53
static uint32_t hash_n_elements(const struct hash *hash)
Definition list.h:115
static uint32_t hash_n_buckets(const struct hash *hash)
Definition list.h:121
msglvl_t mock_get_debug_level(void)
Definition mock_msg.c:55
void mock_set_debug_level(msglvl_t level)
Mock debug level defaults to 0, which gives clean(-ish) test reports.
Definition mock_msg.c:49
char mock_msg_buf[MOCK_MSG_BUF]
Definition mock_msg.c:45
#define CLEAR(x)
Definition basic.h:32
#define msg(flags,...)
Definition error.h:152
unsigned int msglvl_t
Definition error.h:77
#define ASSERT(x)
Definition error.h:219
int atoi_warn(const char *str, msglvl_t msglevel)
Converts a str to an integer if the string can be represented as an integer number.
int positive_atoi(const char *str, msglvl_t msglevel)
Converts a str to a positive number if the string represents a postive integer number.
bool positive_atoll(const char *str, int64_t *value, const char *name, msglvl_t msglevel)
Converts a str to an integer if the string can be represented as an integer number and is >= 0.
const char * parse_auth_failed_temp(struct options *o, const char *reason)
bool valid_integer(const char *str, bool positive)
Checks if the string is a valid integer by checking if it can be converted to an integer.
bool atoi_constrained(const char *str, int *value, const char *name, int min, int max, msglvl_t msglevel)
Converts a str to an integer if the string can be represented as an integer number and is between min...
const char * options_string_compat_lzo(const char *options, struct gc_arena *gc)
Takes a locally produced OCC string for TLS server mode and modifies as if the option comp-lzo was en...
Definition ssl_util.c:127
SSL utility functions.
Garbage collection arena used to keep track of dynamically allocated memory.
Definition buffer.h:127
void * value
Definition list.h:41
Definition list.h:56
uint32_t mask
Definition list.h:59
uint32_t n_buckets
Definition list.h:57
Container for bidirectional cipher and HMAC key material.
Definition crypto.h:240
Container for unidirectional cipher and HMAC key material.
Definition crypto.h:152
int server_backoff_time
Definition options.h:310
bool no_advance
Definition options.h:299
int n
Definition test_misc.c:129
const char * word
Definition test_misc.c:128
#define random
Definition syshead.h:43
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
static void openvpn_test_get_srcdir_dir(char *buf, size_t bufsize, const char *filename)
Helper function to get a file path from the unit test directory to open it or pass its path to anothe...
Definition test_common.h:82
static uint64_t word_hash_function(const void *key, const uint8_t hash_key[HASH_KEY_LEN])
Definition test_misc.c:134
static void test_win_path_in_dir(void **state)
Definition test_misc.c:448
static void test_atoi_variants(void **state)
Definition test_misc.c:318
static void test_auth_fail_temp_flags(void **state)
Definition test_misc.c:101
static void test_compat_lzo_string(void **state)
Definition test_misc.c:49
int main(void)
Definition test_misc.c:491
static void test_list(void **state)
Definition test_misc.c:167
static void test_auth_fail_temp_no_flags(void **state)
Definition test_misc.c:90
const struct CMUnitTest misc_tests[]
Definition test_misc.c:477
static bool word_compare_function(const void *key1, const void *key2)
Definition test_misc.c:142
static struct hash_element * hash_lookup_by_value(struct hash *hash, void *value)
Definition test_misc.c:148
static void test_auth_fail_temp_flags_msg(void **state)
Definition test_misc.c:114
void schedule_test(void **state)
Runs the schedule test.
struct gc_arena gc
Definition test_ssl.c:122
bool win_path_in_dir(const WCHAR *path, const WCHAR *dir)
Check whether path resides within directory dir.
Definition win32-util.c:179