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, uint32_t iv)
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, iv);
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
174 struct gc_arena gc = gc_new();
177
178 printf("hash_init n_buckets=%u mask=0x%08x\n", hash->n_buckets, hash->mask);
179
180 char wordfile[PATH_MAX] = { 0 };
181 openvpn_test_get_srcdir_dir(wordfile, PATH_MAX, "/../../../COPYRIGHT.GPL");
182
183 FILE *words = fopen(wordfile, "r");
184 assert_non_null(words);
185
186 int wordcount = 0;
187
188 /* parse words from file */
189 while (true)
190 {
191 char buf[256];
192 char wordbuf[256];
193
194 if (!fgets(buf, sizeof(buf), words))
195 {
196 break;
197 }
198
199 char c = 0;
200 int bi = 0, wbi = 0;
201
202 do
203 {
204 c = buf[bi++];
205 if (isalnum(c) || c == '_')
206 {
207 assert_true(wbi < (int)sizeof(wordbuf));
208 wordbuf[wbi++] = c;
209 }
210 else
211 {
212 if (wbi)
213 {
214 wordcount++;
215
216 ASSERT(wbi < (int)sizeof(wordbuf));
217 wordbuf[wbi++] = '\0';
218
219 /* word is parsed from stdin */
220
221 /* does it already exist in table? */
222 struct word *w = (struct word *)hash_lookup(hash, wordbuf);
223
224 if (w)
225 {
226 assert_string_equal(w->word, wordbuf);
227 /* yes, increment count */
228 ++w->n;
229 }
230 else
231 {
232 /* no, make a new object */
233 ALLOC_OBJ_GC(w, struct word, &gc);
234 w->word = string_alloc(wordbuf, &gc);
235 w->n = 1;
236 assert_true(hash_add(hash, w->word, w, false));
237 assert_true(hash_add(nhash, w->word,
238 (void *)((ptr_type)(random() & 0x0F) + 1), false));
239 }
240 }
241 wbi = 0;
242 }
243 } while (c);
244 }
245
246 assert_int_equal(wordcount, 2971);
247
248 /* remove some words from the table */
249 {
250 assert_true(hash_remove(hash, "DEFECTIVE"));
251 assert_false(hash_remove(hash, "false"));
252 }
253
254 /* output contents of hash table */
255 {
256 uint32_t inc = 0;
257 int count = 0;
258
259 for (uint32_t base = 0; base < hash_n_buckets(hash); base += inc)
260 {
261 struct hash_iterator hi;
262 struct hash_element *he;
263 inc = ((uint32_t)get_random() % 3) + 1;
264 hash_iterator_init_range(hash, &hi, base, base + inc);
265
266 while ((he = hash_iterator_next(&hi)))
267 {
268 struct word *w = (struct word *)he->value;
269 /*printf("%6d '%s'\n", w->n, w->word); */
270 ++count;
271 /* check a few words to match prior results */
272 if (!strcmp(w->word, "is"))
273 {
274 assert_int_equal(w->n, 49);
275 }
276 else if (!strcmp(w->word, "redistribute"))
277 {
278 assert_int_equal(w->n, 5);
279 }
280 else if (!strcmp(w->word, "circumstances"))
281 {
282 assert_int_equal(w->n, 1);
283 }
284 else if (!strcmp(w->word, "so"))
285 {
286 assert_int_equal(w->n, 8);
287 }
288 else if (!strcmp(w->word, "BECAUSE"))
289 {
290 assert_int_equal(w->n, 1);
291 }
292 }
293
295 }
296 assert_int_equal(count, hash_n_elements(hash));
297 }
298
299 /* test hash_remove_by_value function */
300 {
301 for (ptr_type i = 1; i <= 16; ++i)
302 {
303 struct hash_element *item = hash_lookup_by_value(nhash, (void *)i);
304 hash_remove_by_value(nhash, (void *)i);
305 /* check item got removed if it was present before */
306 if (item)
307 {
308 assert_null(hash_lookup_by_value(nhash, (void *)i));
309 }
310 }
311 }
312
314 hash_free(nhash);
315 gc_free(&gc);
316}
317
318static void
320{
321 assert_true(valid_integer("1234", true));
322 assert_true(valid_integer("1234", false));
323 assert_true(valid_integer("0", false));
324 assert_true(valid_integer("0", true));
325 assert_true(valid_integer("-777", false));
326 assert_false(valid_integer("-777", true));
327
328 assert_false(valid_integer("-777foo", false));
329 assert_false(valid_integer("-777foo", true));
330
331 assert_false(valid_integer("foo777", true));
332 assert_false(valid_integer("foo777", false));
333
334 /* 2**31 + 5 , just outside of signed int range */
335 assert_false(valid_integer("2147483653", true));
336 assert_false(valid_integer("2147483653", false));
337 assert_false(valid_integer("-2147483653", true));
338 assert_false(valid_integer("-2147483653", false));
339
340
341 msglvl_t msglevel = D_LOW;
342 msglvl_t saved_log_level = mock_get_debug_level();
344
345 /* check happy path */
346 assert_int_equal(positive_atoi("1234", msglevel), 1234);
347 assert_int_equal(positive_atoi("0", msglevel), 0);
348
349 assert_int_equal(atoi_warn("1234", msglevel), 1234);
350 assert_int_equal(atoi_warn("0", msglevel), 0);
351 assert_int_equal(atoi_warn("-1194", msglevel), -1194);
352
353 int parameter = 0;
354 assert_true(atoi_constrained("1234", &parameter, "test", 0, INT_MAX, msglevel));
355 assert_int_equal(parameter, 1234);
356 assert_true(atoi_constrained("0", &parameter, "test", -1, 0, msglevel));
357 assert_int_equal(parameter, 0);
358 assert_true(atoi_constrained("-1194", &parameter, "test", INT_MIN, INT_MAX, msglevel));
359 assert_int_equal(parameter, -1194);
360
361 int64_t parameter64 = 0;
362 assert_true(positive_atoll("1234", &parameter64, "test", msglevel));
363 assert_int_equal(parameter64, 1234);
364 assert_true(positive_atoll("0", &parameter64, "test", msglevel));
365 assert_int_equal(parameter64, 0);
366 assert_true(positive_atoll("2147483653", &parameter64, "test", msglevel));
367 assert_int_equal(parameter64, 2147483653);
368 /* overflow gets capped to LLONG_MAX */
369 assert_true(positive_atoll("9223372036854775810", &parameter64, "test", msglevel));
370 assert_int_equal(parameter64, 9223372036854775807);
371
373 assert_int_equal(positive_atoi("-1234", msglevel), 0);
374 assert_string_equal(mock_msg_buf, "Cannot parse argument '-1234' as non-negative integer");
375
376 /* 2**31 + 5 , just outside of signed int range */
378 assert_int_equal(positive_atoi("2147483653", msglevel), 0);
379 assert_string_equal(mock_msg_buf, "Cannot parse argument '2147483653' as non-negative integer");
380
382 assert_int_equal(atoi_warn("2147483653", msglevel), 0);
383 assert_string_equal(mock_msg_buf, "Cannot parse argument '2147483653' as integer");
384
386 parameter = -42;
387 assert_false(atoi_constrained("2147483653", &parameter, "test", 0, INT_MAX, msglevel));
388 assert_string_equal(mock_msg_buf, "test: Cannot parse '2147483653' as integer");
389 assert_int_equal(parameter, -42);
390
392 assert_int_equal(positive_atoi("foo77", msglevel), 0);
393 assert_string_equal(mock_msg_buf, "Cannot parse argument 'foo77' as non-negative integer");
394
396 assert_int_equal(positive_atoi("77foo", msglevel), 0);
397 assert_string_equal(mock_msg_buf, "Cannot parse argument '77foo' as non-negative integer");
398
400 parameter = -42;
401 assert_false(atoi_constrained("foo77", &parameter, "test", 0, INT_MAX, msglevel));
402 assert_string_equal(mock_msg_buf, "test: Cannot parse 'foo77' as integer");
403 assert_int_equal(parameter, -42);
404
406 parameter = -42;
407 assert_false(atoi_constrained("77foo", &parameter, "test", 0, INT_MAX, msglevel));
408 assert_string_equal(mock_msg_buf, "test: Cannot parse '77foo' as integer");
409 assert_int_equal(parameter, -42);
410
412 assert_int_equal(atoi_warn("foo77", msglevel), 0);
413 assert_string_equal(mock_msg_buf, "Cannot parse argument 'foo77' as integer");
414
416 assert_int_equal(atoi_warn("77foo", msglevel), 0);
417 assert_string_equal(mock_msg_buf, "Cannot parse argument '77foo' as integer");
418
419 /* special tests for _constrained */
421 parameter = -42;
422 assert_false(atoi_constrained("77", &parameter, "test", 0, 76, msglevel));
423 assert_string_equal(mock_msg_buf, "test: Must be an integer between 0 and 76, not 77");
424 assert_int_equal(parameter, -42);
425
427 parameter = -42;
428 assert_false(atoi_constrained("-77", &parameter, "test", -76, 76, msglevel));
429 assert_string_equal(mock_msg_buf, "test: Must be an integer between -76 and 76, not -77");
430 assert_int_equal(parameter, -42);
431
433 parameter = -42;
434 assert_false(atoi_constrained("-77", &parameter, "test", 0, INT_MAX, msglevel));
435 assert_string_equal(mock_msg_buf, "test: Must be an integer >= 0, not -77");
436 assert_int_equal(parameter, -42);
437
439 parameter = -42;
440 assert_false(atoi_constrained("0", &parameter, "test", 1, INT_MAX, msglevel));
441 assert_string_equal(mock_msg_buf, "test: Must be an integer >= 1, not 0");
442 assert_int_equal(parameter, -42);
443
444 mock_set_debug_level(saved_log_level);
445}
446
447#ifdef _WIN32
448static void
450{
451 /* plugin/install dir without trailing separator */
452 assert_true(win_path_in_dir(L"C:\\openvpn_plugins\\foo.dll", L"C:\\openvpn_plugins"));
453
454 /* the bug being fixed: a sibling dir sharing the prefix must NOT match */
455 assert_false(win_path_in_dir(L"C:\\openvpn_plugins_evil\\foo.dll", L"C:\\openvpn_plugins"));
456
457 /* trusted dir with trailing separator */
458 assert_true(win_path_in_dir(L"C:\\openvpn_plugins\\foo.dll", L"C:\\openvpn_plugins\\"));
459 assert_false(win_path_in_dir(L"C:\\openvpn_plugins_evil\\foo.dll", L"C:\\openvpn_plugins\\"));
460
461 /* forward slash separator in the candidate path is accepted */
462 assert_true(win_path_in_dir(L"C:\\openvpn_plugins/foo.dll", L"C:\\openvpn_plugins"));
463
464 /* comparison is case-insensitive */
465 assert_true(win_path_in_dir(L"c:\\OPENVPN_PLUGINS\\foo.dll", L"C:\\openvpn_plugins"));
466
467 /* the directory itself (no trailing component) is not "in" the directory */
468 assert_false(win_path_in_dir(L"C:\\openvpn_plugins", L"C:\\openvpn_plugins"));
469
470 /* nested subdirectories are still inside */
471 assert_true(win_path_in_dir(L"C:\\openvpn_plugins\\sub\\foo.dll", L"C:\\openvpn_plugins"));
472
473 /* an empty trusted dir never matches */
474 assert_false(win_path_in_dir(L"C:\\openvpn_plugins\\foo.dll", L""));
475}
476#endif /* _WIN32 */
477
478const struct CMUnitTest misc_tests[] = {
479#ifdef _WIN32
480 cmocka_unit_test(test_win_path_in_dir),
481#endif
482 cmocka_unit_test(test_compat_lzo_string),
483 cmocka_unit_test(test_auth_fail_temp_no_flags),
484 cmocka_unit_test(test_auth_fail_temp_flags),
485 cmocka_unit_test(test_auth_fail_temp_flags_msg),
486 cmocka_unit_test(test_list),
487 cmocka_unit_test(test_atoi_variants),
488 cmocka_unit_test(schedule_test)
489};
490
491int
492main(void)
493{
495 return cmocka_run_group_tests(misc_tests, NULL, NULL);
496}
char * string_alloc(const char *str, struct gc_arena *gc)
Definition buffer.c:653
#define ALLOC_OBJ_GC(dptr, type, gc)
Definition buffer.h:1152
static void gc_free(struct gc_arena *a)
Definition buffer.h:1081
static struct gc_arena gc_new(void)
Definition buffer.h:1073
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:270
struct hash_element * hash_iterator_next(struct hash_iterator *hi)
Definition list.c:276
void hash_iterator_init(struct hash *hash, struct hash_iterator *hi)
Definition list.c:234
struct hash * hash_init(const uint32_t n_buckets, const uint32_t iv, uint64_t(*hash_function)(const void *key, uint32_t iv), bool(*compare_function)(const void *key1, const void *key2))
Definition list.c:37
void hash_free(struct hash *hash)
Definition list.c:60
bool hash_add(struct hash *hash, const void *key, void *value, bool replace)
Definition list.c:137
void hash_remove_by_value(struct hash *hash, void *value)
Definition list.c:165
void hash_iterator_init_range(struct hash *hash, struct hash_iterator *hi, uint32_t start_bucket, uint32_t end_bucket)
Definition list.c:213
uint64_t hash_func(const uint8_t *k, uint32_t length, uint32_t initval)
Definition list.c:416
static bool hash_remove(struct hash *hash, const void *key)
Definition list.h:161
static void * hash_lookup(struct hash *hash, const void *key)
Definition list.h:128
static uint32_t hash_n_elements(const struct hash *hash)
Definition list.h:110
static uint32_t hash_n_buckets(const struct hash *hash)
Definition list.h:116
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:76
SSL utility functions.
Garbage collection arena used to keep track of dynamically allocated memory.
Definition buffer.h:117
void * value
Definition list.h:41
Definition list.h:53
uint32_t mask
Definition list.h:56
uint32_t n_buckets
Definition list.h:54
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 void test_win_path_in_dir(void **state)
Definition test_misc.c:449
static void test_atoi_variants(void **state)
Definition test_misc.c:319
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:492
static void test_list(void **state)
Definition test_misc.c:167
static uint64_t word_hash_function(const void *key, uint32_t iv)
Definition test_misc.c:134
static void test_auth_fail_temp_no_flags(void **state)
Definition test_misc.c:90
const struct CMUnitTest misc_tests[]
Definition test_misc.c:478
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:133
bool win_path_in_dir(const WCHAR *path, const WCHAR *dir)
Check whether path resides within directory dir.
Definition win32-util.c:179