OpenVPN
options_parse.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) 2002-2026 OpenVPN Inc <sales@openvpn.net>
9 * Copyright (C) 2008-2026 David Sommerseth <dazo@eurephia.org>
10 *
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License version 2
13 * as published by the Free Software Foundation.
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 <string.h>
29
30#include "options.h"
31#include "options_util.h"
32#include "push.h"
33
34static void
36{
37 if (strlen(*p) >= 3 && !strncmp(*p, "--", 2))
38 {
39 *p += 2;
40 }
41}
42
43static inline bool
44space(char c)
45{
46 return c == '\0' || isspace(c);
47}
48
49int
50parse_line(const char *line, char *p[], const int n, const char *file, const int line_num,
51 msglvl_t msglevel, struct gc_arena *gc)
52{
53 const int STATE_INITIAL = 0;
54 const int STATE_READING_QUOTED_PARM = 1;
55 const int STATE_READING_UNQUOTED_PARM = 2;
56 const int STATE_DONE = 3;
57 const int STATE_READING_SQUOTED_PARM = 4;
58
59 const char *error_prefix = "";
60
61 int ret = 0;
62 const char *c = line;
63 int state = STATE_INITIAL;
64 bool backslash = false;
65 char in, out;
66
67 char parm[OPTION_PARM_SIZE];
68 unsigned int parm_len = 0;
69
70 msglevel &= ~M_OPTERR;
71
72 if (msglevel & M_MSG_VIRT_OUT)
73 {
74 error_prefix = "ERROR: ";
75 }
76
77 do
78 {
79 in = *c;
80 out = 0;
81
82 if (!backslash && in == '\\' && state != STATE_READING_SQUOTED_PARM)
83 {
84 backslash = true;
85 }
86 else
87 {
88 if (state == STATE_INITIAL)
89 {
90 if (!space(in))
91 {
92 if (in == ';' || in == '#') /* comment */
93 {
94 break;
95 }
96 if (!backslash && in == '\"')
97 {
98 state = STATE_READING_QUOTED_PARM;
99 }
100 else if (!backslash && in == '\'')
101 {
102 state = STATE_READING_SQUOTED_PARM;
103 }
104 else
105 {
106 out = in;
107 state = STATE_READING_UNQUOTED_PARM;
108 }
109 }
110 }
111 else if (state == STATE_READING_UNQUOTED_PARM)
112 {
113 if (!backslash && space(in))
114 {
115 state = STATE_DONE;
116 }
117 else
118 {
119 out = in;
120 }
121 }
122 else if (state == STATE_READING_QUOTED_PARM)
123 {
124 if (!backslash && in == '\"')
125 {
126 state = STATE_DONE;
127 }
128 else
129 {
130 out = in;
131 }
132 }
133 else if (state == STATE_READING_SQUOTED_PARM)
134 {
135 if (in == '\'')
136 {
137 state = STATE_DONE;
138 }
139 else
140 {
141 out = in;
142 }
143 }
144 if (state == STATE_DONE)
145 {
146 /* ASSERT (parm_len > 0); */
147 p[ret] = gc_malloc(parm_len + 1, true, gc);
148 memcpy(p[ret], parm, parm_len);
149 p[ret][parm_len] = '\0';
150 state = STATE_INITIAL;
151 parm_len = 0;
152 ++ret;
153 }
154
155 if (backslash && out)
156 {
157 if (!(out == '\\' || out == '\"' || space(out)))
158 {
159#ifdef ENABLE_SMALL
160 msg(msglevel, "%sOptions warning: Bad backslash ('\\') usage in %s:%d",
161 error_prefix, file, line_num);
162#else
163 msg(msglevel,
164 "%sOptions warning: Bad backslash ('\\') usage in %s:%d: remember that backslashes are treated as shell-escapes and if you need to pass backslash characters as part of a Windows filename, you should use double backslashes such as \"c:\\\\" PACKAGE
165 "\\\\static.key\"",
166 error_prefix, file, line_num);
167#endif
168 return 0;
169 }
170 }
171 backslash = false;
172 }
173
174 /* store parameter character */
175 if (out)
176 {
177 if (parm_len >= SIZE(parm))
178 {
179 parm[SIZE(parm) - 1] = 0;
180 msg(msglevel, "%sOptions error: Parameter at %s:%d is too long (%d chars max): %s",
181 error_prefix, file, line_num, (int)SIZE(parm), parm);
182 return 0;
183 }
184 parm[parm_len++] = out;
185 }
186
187 /* avoid overflow if too many parms in one config file line */
188 if (ret >= n)
189 {
190 break;
191 }
192
193 } while (*c++ != '\0');
194
195 if (state == STATE_READING_QUOTED_PARM)
196 {
197 msg(msglevel, "%sOptions error: No closing quotation (\") in %s:%d", error_prefix, file,
198 line_num);
199 return 0;
200 }
201 if (state == STATE_READING_SQUOTED_PARM)
202 {
203 msg(msglevel, "%sOptions error: No closing single quotation (\') in %s:%d", error_prefix,
204 file, line_num);
205 return 0;
206 }
207 if (state != STATE_INITIAL)
208 {
209 msg(msglevel, "%sOptions error: Residual parse state (%d) in %s:%d", error_prefix, state,
210 file, line_num);
211 return 0;
212 }
213#if 0
214 {
215 int i;
216 for (i = 0; i < ret; ++i)
217 {
218 msg(M_INFO|M_NOPREFIX, "%s:%d ARG[%d] '%s'", file, line_num, i, p[i]);
219 }
220 }
221#endif
222 return ret;
223}
224
225struct in_src
226{
227#define IS_TYPE_FP 1
228#define IS_TYPE_BUF 2
229 int type;
230 union
231 {
232 FILE *fp;
234 } u;
235};
236
237static bool
238in_src_get(const struct in_src *is, char *line, const int size)
239{
240 if (is->type == IS_TYPE_FP)
241 {
242 return BOOL_CAST(fgets(line, size, is->u.fp));
243 }
244 else if (is->type == IS_TYPE_BUF)
245 {
246 bool status = buf_parse(is->u.multiline, '\n', line, size);
247 if ((int)strlen(line) + 1 < size)
248 {
249 strcat(line, "\n");
250 }
251 return status;
252 }
253 else
254 {
255 ASSERT(0);
256 return false;
257 }
258}
259
260static char *
261read_inline_file(struct in_src *is, const char *close_tag, int *num_lines, struct gc_arena *gc)
262{
263 char line[OPTION_LINE_SIZE];
264 struct buffer buf = alloc_buf(8 * OPTION_LINE_SIZE);
265 char *ret;
266 bool endtagfound = false;
267
268 while (in_src_get(is, line, sizeof(line)))
269 {
270 (*num_lines)++;
271 const char *line_ptr = line;
272 /* Remove leading spaces */
273 while (isspace(*line_ptr))
274 {
275 line_ptr++;
276 }
277 if (!strncmp(line_ptr, close_tag, strlen(close_tag)))
278 {
279 endtagfound = true;
280 break;
281 }
282 if (!buf_safe(&buf, strlen(line) + 1))
283 {
284 /* Increase buffer size */
285 struct buffer buf2 = alloc_buf(buf.capacity * 2);
286 ASSERT(buf_copy(&buf2, &buf));
287 buf_clear(&buf);
288 free_buf(&buf);
289 buf = buf2;
290 }
291 buf_printf(&buf, "%s", line);
292 }
293 if (!endtagfound)
294 {
295 msg(M_FATAL, "ERROR: Endtag %s missing", close_tag);
296 }
297 ret = string_alloc(BSTR(&buf), gc);
298 buf_clear(&buf);
299 free_buf(&buf);
300 secure_memzero(line, sizeof(line));
301 return ret;
302}
303
304static int
305check_inline_file(struct in_src *is, char *p[], struct gc_arena *gc)
306{
307 int num_inline_lines = 0;
308
309 if (p[0] && !p[1])
310 {
311 char *arg = p[0];
312 if (arg[0] == '<' && arg[strlen(arg) - 1] == '>')
313 {
314 struct buffer close_tag;
315
316 arg[strlen(arg) - 1] = '\0';
317 p[0] = string_alloc(arg + 1, gc);
318 close_tag = alloc_buf(strlen(p[0]) + 4);
319 buf_printf(&close_tag, "</%s>", p[0]);
320 p[1] = read_inline_file(is, BSTR(&close_tag), &num_inline_lines, gc);
321 p[2] = NULL;
322 free_buf(&close_tag);
323 }
324 }
325 return num_inline_lines;
326}
327
328static int
329check_inline_file_via_fp(FILE *fp, char *p[], struct gc_arena *gc)
330{
331 struct in_src is;
332 is.type = IS_TYPE_FP;
333 is.u.fp = fp;
334 return check_inline_file(&is, p, gc);
335}
336
337static int
339{
340 struct in_src is;
341 is.type = IS_TYPE_BUF;
342 is.u.multiline = multiline;
343 return check_inline_file(&is, p, gc);
344}
345
346void
347read_config_file(struct options *options, const char *file, int level, const char *top_file,
348 const int top_line, const msglvl_t msglevel,
349 const uint64_t permission_mask, uint64_t *option_types_found,
350 struct env_set *es)
351{
352 const int max_recursive_levels = 10;
353 FILE *fp;
354 char line[OPTION_LINE_SIZE + 1];
355 char *p[MAX_PARMS + 1];
356
357 ++level;
358 if (level <= max_recursive_levels)
359 {
360 if (streq(file, "stdin"))
361 {
362 fp = stdin;
363 }
364 else
365 {
366 fp = platform_fopen(file, "r");
367 }
368 if (fp)
369 {
370 int line_num = 0;
371 while (fgets(line, sizeof(line), fp))
372 {
373 int offset = 0;
374 CLEAR(p);
375 ++line_num;
376 if (strlen(line) == OPTION_LINE_SIZE)
377 {
378 msg(msglevel,
379 "In %s:%d: Maximum option line length (%d) exceeded, line starts with %s",
380 file, line_num, OPTION_LINE_SIZE, line);
381 }
382
383 /* Ignore UTF-8 BOM at start of stream */
384 if (line_num == 1 && strncmp(line, "\xEF\xBB\xBF", 3) == 0)
385 {
386 offset = 3;
387 }
388 if (parse_line(line + offset, p, SIZE(p) - 1, file, line_num, msglevel,
389 &options->gc))
390 {
391 bypass_doubledash(&p[0]);
392 int lines_inline = check_inline_file_via_fp(fp, p, &options->gc);
393 add_option(options, p, lines_inline, file, line_num, level, msglevel,
394 permission_mask, option_types_found, es);
395 line_num += lines_inline;
396 }
397 }
398 if (fp != stdin)
399 {
400 fclose(fp);
401 }
402 }
403 else
404 {
405 msg(msglevel, "In %s:%d: Error opening configuration file: %s", top_file, top_line,
406 file);
407 }
408 }
409 else
410 {
411 msg(msglevel,
412 "In %s:%d: Maximum recursive include levels exceeded in include attempt of file %s -- probably you have a configuration file that tries to include itself.",
413 top_file, top_line, file);
414 }
415 secure_memzero(line, sizeof(line));
416 CLEAR(p);
417}
418
419void
420read_config_string(const char *prefix, struct options *options, const char *config,
421 const msglvl_t msglevel, const uint64_t permission_mask,
422 uint64_t *option_types_found, struct env_set *es)
423{
424 char line[OPTION_LINE_SIZE];
425 struct buffer multiline;
426 int line_num = 0;
427
428 buf_set_read(&multiline, (uint8_t *)config, strlen(config));
429
430 while (buf_parse(&multiline, '\n', line, sizeof(line)))
431 {
432 char *p[MAX_PARMS + 1];
433 CLEAR(p);
434 ++line_num;
435 if (parse_line(line, p, SIZE(p) - 1, prefix, line_num, msglevel, &options->gc))
436 {
437 bypass_doubledash(&p[0]);
438 int lines_inline = check_inline_file_via_buf(&multiline, p, &options->gc);
439 add_option(options, p, lines_inline, prefix, line_num, 0, msglevel, permission_mask,
440 option_types_found, es);
441 line_num += lines_inline;
442 }
443 CLEAR(p);
444 }
445 secure_memzero(line, sizeof(line));
446}
447
448void
449parse_argv(struct options *options, const int argc, char *argv[], const msglvl_t msglevel,
450 const uint64_t permission_mask, uint64_t *option_types_found, struct env_set *es)
451{
452 /* usage message */
453 if (argc <= 1)
454 {
455 usage();
456 }
457
458 /* config filename specified only? */
459 if (argc == 2 && strncmp(argv[1], "--", 2))
460 {
461 char *p[MAX_PARMS + 1];
462 CLEAR(p);
463 p[0] = "config";
464 p[1] = argv[1];
465 add_option(options, p, false, NULL, 0, 0, msglevel, permission_mask, option_types_found,
466 es);
467 }
468 else
469 {
470 /* parse command line */
471 for (int i = 1; i < argc; ++i)
472 {
473 char *p[MAX_PARMS + 1];
474 CLEAR(p);
475 p[0] = argv[i];
476 if (strncmp(p[0], "--", 2))
477 {
478 msg(msglevel,
479 "I'm trying to parse \"%s\" as an --option parameter but I don't see a leading '--'",
480 p[0]);
481 }
482 else
483 {
484 p[0] += 2;
485 }
486
487 int j;
488 for (j = 1; j < MAX_PARMS; ++j)
489 {
490 if (i + j < argc)
491 {
492 char *arg = argv[i + j];
493 if (strncmp(arg, "--", 2))
494 {
495 p[j] = arg;
496 }
497 else
498 {
499 break;
500 }
501 }
502 }
503 add_option(options, p, false, NULL, 0, 0, msglevel, permission_mask, option_types_found,
504 es);
505 i += j - 1;
506 }
507 }
508}
509
510bool
511apply_push_options(struct context *c, struct options *options, struct buffer *buf,
512 uint64_t permission_mask, uint64_t *option_types_found,
513 struct env_set *es, bool is_update)
514{
515 char line[OPTION_PARM_SIZE];
516 int line_num = 0;
517 const char *file = "[PUSH-OPTIONS]";
518 const msglvl_t msglevel = D_PUSH_ERRORS | M_OPTERR;
519
520 while (buf_parse(buf, ',', line, sizeof(line)))
521 {
522 char *p[MAX_PARMS + 1];
523 CLEAR(p);
524 ++line_num;
525 uint64_t push_update_option_flags = 0;
526 int i = 0;
527
528 /* skip leading spaces matching the behaviour of parse_line */
529 while (isspace(line[i]))
530 {
531 i++;
532 }
533
534 /* If we are not in a 'PUSH_UPDATE' we just check `apply_pull_filter()`
535 * otherwise we must call `check_push_update_option_flags()` first
536 */
537 if ((is_update && !check_push_update_option_flags(line, &i, &push_update_option_flags))
538 || !apply_pull_filter(options, &line[i]))
539 {
540 /* In case we are in a `PUSH_UPDATE` and `check_push_update_option_flags()`
541 * or `apply_pull_filter()` fail but the option is flagged by `PUSH_OPT_OPTIONAL`,
542 * instead of restarting, we just ignore the option and we process the next one
543 */
544 if (push_update_option_flags & PUSH_OPT_OPTIONAL)
545 {
546 continue; /* Ignoring this option */
547 }
548 return false; /* Cause push/pull error and stop push processing */
549 }
550
551 if (parse_line(&line[i], p, SIZE(p) - 1, file, line_num, msglevel, &options->gc))
552 {
553 if (!is_update)
554 {
555 add_option(options, p, false, file, line_num, 0, msglevel, permission_mask,
556 option_types_found, es);
557 }
558 else if (push_update_option_flags & PUSH_OPT_TO_REMOVE)
559 {
560 remove_option(c, options, p, false, file, line_num, msglevel, permission_mask,
561 option_types_found, es);
562 }
563 else
564 {
565 /*
566 * Use persistent push_update_options_found from options struct to track
567 * which option types have been reset across continuation messages.
568 * This prevents routes from being reset on each continuation message.
569 */
570 update_option(c, options, p, false, file, line_num, 0, msglevel, permission_mask,
571 option_types_found, es);
572 }
573 }
574 }
575 return true;
576}
577
578void
579options_server_import(struct options *o, const char *filename, msglvl_t msglevel,
580 const uint64_t permission_mask, uint64_t *option_types_found,
581 struct env_set *es)
582{
583 msg(D_PUSH, "OPTIONS IMPORT: reading client specific options from: %s", filename);
584 read_config_file(o, filename, 0, filename, 0, msglevel, permission_mask, option_types_found,
585 es);
586}
587
588void
589options_string_import(struct options *options, const char *config, const msglvl_t msglevel,
590 const uint64_t permission_mask, uint64_t *option_types_found,
591 struct env_set *es)
592{
593 read_config_string("[CONFIG-STRING]", options, config, msglevel, permission_mask,
594 option_types_found, es);
595}
void free_buf(struct buffer *buf)
Free the memory allocated for a buffer.
Definition buffer.c:169
void buf_clear(struct buffer *buf)
Zeroise and reset a buffer.
Definition buffer.c:148
bool buf_printf(struct buffer *buf, const char *format,...)
printf-style append to a buffer with overflow check.
Definition buffer.c:226
void * gc_malloc(size_t size, bool clear, struct gc_arena *a)
Allocate memory and, optionally, zero it.
Definition buffer.c:318
struct buffer alloc_buf(size_t size)
Allocate a buffer of the given size.
Definition buffer.c:60
bool buf_parse(struct buffer *buf, const int delim, char *line, const int size)
Extract the next token from a buffer, delimited by a given character.
Definition buffer.c:765
char * string_alloc(const char *str, struct gc_arena *gc)
Duplicate a string, allocating memory under garbage collection.
Definition buffer.c:606
#define BSTR(buf)
Return the buffer content pointer cast to char *.
Definition buffer.h:157
static bool buf_copy(struct buffer *dest, const struct buffer *src)
Copy the content of one buffer to the end of another.
Definition buffer.h:1365
static bool buf_safe(const struct buffer *buf, size_t len)
Check whether len bytes can be appended to a buffer.
Definition buffer.h:1017
static void buf_set_read(struct buffer *buf, const uint8_t *data, size_t size)
Initialise a buffer with an externally provided read-only memory region.
Definition buffer.h:687
static void secure_memzero(void *data, size_t len)
Securely zeroise memory.
Definition buffer.h:769
#define D_PUSH
Definition errlevel.h:82
#define D_PUSH_ERRORS
Definition errlevel.h:66
#define M_INFO
Definition errlevel.h:54
static SERVICE_STATUS status
Definition interactive.c:52
#define BOOL_CAST(x)
Definition basic.h:26
#define CLEAR(x)
Definition basic.h:32
#define SIZE(x)
Definition basic.h:29
#define M_OPTERR
Definition error.h:101
#define M_NOPREFIX
Definition error.h:98
#define M_FATAL
Definition error.h:90
#define msg(flags,...)
Definition error.h:152
unsigned int msglvl_t
Definition error.h:77
#define ASSERT(x)
Definition error.h:221
#define M_MSG_VIRT_OUT
Definition error.h:100
void remove_option(struct context *c, struct options *options, char *p[], bool is_inline, const char *file, int line, const msglvl_t msglevel, const uint64_t permission_mask, uint64_t *option_types_found, struct env_set *es)
Resets options found in the PUSH_UPDATE message that are preceded by the - flag.
Definition options.c:3681
void update_option(struct context *c, struct options *options, char *p[], bool is_inline, const char *file, int line, const int level, const msglvl_t msglevel, const uint64_t permission_mask, uint64_t *option_types_found, struct env_set *es)
Processes an option to update.
Definition options.c:4001
void usage(void)
Definition options.c:3462
void add_option(struct options *options, char *p[], bool is_inline, const char *file, int line, const int level, const msglvl_t msglevel, const uint64_t permission_mask, uint64_t *option_types_found, struct env_set *es)
Definition options.c:4188
#define streq(x, y)
Definition options.h:727
#define OPTION_PARM_SIZE
Definition options.h:56
#define MAX_PARMS
Definition options.h:51
#define OPTION_LINE_SIZE
Definition options.h:57
static bool in_src_get(const struct in_src *is, char *line, const int size)
void options_server_import(struct options *o, const char *filename, msglvl_t msglevel, const uint64_t permission_mask, uint64_t *option_types_found, struct env_set *es)
static int check_inline_file_via_buf(struct buffer *multiline, char *p[], struct gc_arena *gc)
#define IS_TYPE_BUF
static int check_inline_file(struct in_src *is, char *p[], struct gc_arena *gc)
static bool space(char c)
static void bypass_doubledash(char **p)
void parse_argv(struct options *options, const int argc, char *argv[], const msglvl_t msglevel, const uint64_t permission_mask, uint64_t *option_types_found, struct env_set *es)
void options_string_import(struct options *options, const char *config, const msglvl_t msglevel, const uint64_t permission_mask, uint64_t *option_types_found, struct env_set *es)
int parse_line(const char *line, char *p[], const int n, const char *file, const int line_num, msglvl_t msglevel, struct gc_arena *gc)
void read_config_file(struct options *options, const char *file, int level, const char *top_file, const int top_line, const msglvl_t msglevel, const uint64_t permission_mask, uint64_t *option_types_found, struct env_set *es)
static int check_inline_file_via_fp(FILE *fp, char *p[], struct gc_arena *gc)
bool apply_push_options(struct context *c, struct options *options, struct buffer *buf, uint64_t permission_mask, uint64_t *option_types_found, struct env_set *es, bool is_update)
#define IS_TYPE_FP
void read_config_string(const char *prefix, struct options *options, const char *config, const msglvl_t msglevel, const uint64_t permission_mask, uint64_t *option_types_found, struct env_set *es)
static char * read_inline_file(struct in_src *is, const char *close_tag, int *num_lines, struct gc_arena *gc)
bool check_push_update_option_flags(char *line, int *i, uint64_t *flags)
Checks the formatting and validity of options inside push-update messages.
bool apply_pull_filter(const struct options *o, char *line)
Filter an option line by all pull filters.
FILE * platform_fopen(const char *path, const char *mode)
Definition platform.c:500
#define PUSH_OPT_TO_REMOVE
Definition push.h:41
#define PUSH_OPT_OPTIONAL
Definition push.h:42
Definition argv.h:35
Wrapper structure for dynamically allocated memory.
Definition buffer.h:71
int capacity
Size in bytes of memory allocated by malloc().
Definition buffer.h:72
Contains all state information for one tunnel.
Definition openvpn.h:471
Garbage collection arena used to keep track of dynamically allocated memory.
Definition buffer.h:127
struct buffer * multiline
FILE * fp
union in_src::@226311311145304014152164131202304134072003123370 u
struct gc_arena gc
Definition options.h:258
struct gc_arena gc
Definition test_ssl.c:122