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 strlcat(line, "\n", size);
248 return status;
249 }
250 else
251 {
252 ASSERT(0);
253 return false;
254 }
255}
256
257static char *
258read_inline_file(const struct in_src *is, const char *close_tag, int *num_lines, struct gc_arena *gc)
259{
261 struct buffer buf = alloc_buf(8 * OPTION_LINE_SIZE);
262 char *ret;
263 bool endtagfound = false;
264
265 while (in_src_get(is, line, sizeof(line)))
266 {
267 (*num_lines)++;
268 const char *line_ptr = line;
269 /* Remove leading spaces */
270 while (isspace(*line_ptr))
271 {
272 line_ptr++;
273 }
275 {
276 endtagfound = true;
277 break;
278 }
279 if (!buf_safe(&buf, strlen(line) + 1))
280 {
281 /* Increase buffer size */
282 struct buffer buf2 = alloc_buf(buf.capacity * 2);
283 ASSERT(buf_copy(&buf2, &buf));
284 buf_clear(&buf);
285 free_buf(&buf);
286 buf = buf2;
287 }
288 buf_printf(&buf, "%s", line);
289 }
290 if (!endtagfound)
291 {
292 msg(M_FATAL, "ERROR: Endtag %s missing", close_tag);
293 }
294 ret = string_alloc(BSTR(&buf), gc);
295 buf_clear(&buf);
296 free_buf(&buf);
297 secure_memzero(line, sizeof(line));
298 return ret;
299}
300
301static int
302check_inline_file(const struct in_src *is, char *p[], struct gc_arena *gc)
303{
304 int num_inline_lines = 0;
305
306 if (p[0] && !p[1])
307 {
308 char *arg = p[0];
309 if (arg[0] == '<' && arg[strlen(arg) - 1] == '>')
310 {
311 struct buffer close_tag;
312
313 arg[strlen(arg) - 1] = '\0';
314 p[0] = string_alloc(arg + 1, gc);
315 close_tag = alloc_buf(strlen(p[0]) + 4);
316 buf_printf(&close_tag, "</%s>", p[0]);
318 p[2] = NULL;
320 }
321 }
322 return num_inline_lines;
323}
324
325static int
327{
328 struct in_src is;
329 is.type = IS_TYPE_FP;
330 is.u.fp = fp;
331 return check_inline_file(&is, p, gc);
332}
333
334static int
336{
337 struct in_src is;
338 is.type = IS_TYPE_BUF;
339 is.u.multiline = multiline;
340 return check_inline_file(&is, p, gc);
341}
342
343void
344read_config_file(struct options *options, const char *file, int level, const char *top_file,
345 const int top_line, const msglvl_t msglevel,
346 const uint64_t permission_mask, uint64_t *option_types_found,
347 struct env_set *es)
348{
349 const int max_recursive_levels = 10;
350 FILE *fp;
351 char line[OPTION_LINE_SIZE + 1];
352 char *p[MAX_PARMS + 1];
353
354 ++level;
355 if (level <= max_recursive_levels)
356 {
357 if (streq(file, "stdin"))
358 {
359 fp = stdin;
360 }
361 else
362 {
363 fp = platform_fopen(file, "r");
364 }
365 if (fp)
366 {
367 int line_num = 0;
368 while (fgets(line, sizeof(line), fp))
369 {
370 int offset = 0;
371 CLEAR(p);
372 ++line_num;
373 if (strlen(line) == OPTION_LINE_SIZE)
374 {
375 msg(msglevel,
376 "In %s:%d: Maximum option line length (%d) exceeded, line starts with %s",
377 file, line_num, OPTION_LINE_SIZE, line);
378 }
379
380 /* Ignore UTF-8 BOM at start of stream */
381 if (line_num == 1 && strncmp(line, "\xEF\xBB\xBF", 3) == 0)
382 {
383 offset = 3;
384 }
385 if (parse_line(line + offset, p, SIZE(p) - 1, file, line_num, msglevel,
386 &options->gc))
387 {
388 bypass_doubledash(&p[0]);
389 int lines_inline = check_inline_file_via_fp(fp, p, &options->gc);
390 add_option(options, p, lines_inline, file, line_num, level, msglevel,
391 permission_mask, option_types_found, es);
392 line_num += lines_inline;
393 }
394 }
395 if (fp != stdin)
396 {
397 fclose(fp);
398 }
399 }
400 else
401 {
402 msg(msglevel, "In %s:%d: Error opening configuration file: %s", top_file, top_line,
403 file);
404 }
405 }
406 else
407 {
408 msg(msglevel,
409 "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.",
410 top_file, top_line, file);
411 }
412 secure_memzero(line, sizeof(line));
413 CLEAR(p);
414}
415
416void
417read_config_string(const char *prefix, struct options *options, const char *config,
418 const msglvl_t msglevel, const uint64_t permission_mask,
419 uint64_t *option_types_found, struct env_set *es)
420{
421 char line[OPTION_LINE_SIZE];
422 struct buffer multiline;
423 int line_num = 0;
424
425 buf_set_read(&multiline, (uint8_t *)config, strlen(config));
426
427 while (buf_parse(&multiline, '\n', line, sizeof(line)))
428 {
429 char *p[MAX_PARMS + 1];
430 CLEAR(p);
431 ++line_num;
432 if (parse_line(line, p, SIZE(p) - 1, prefix, line_num, msglevel, &options->gc))
433 {
434 bypass_doubledash(&p[0]);
435 int lines_inline = check_inline_file_via_buf(&multiline, p, &options->gc);
436 add_option(options, p, lines_inline, prefix, line_num, 0, msglevel, permission_mask,
437 option_types_found, es);
439 }
440 CLEAR(p);
441 }
442 secure_memzero(line, sizeof(line));
443}
444
445void
446parse_argv(struct options *options, const int argc, char *argv[], const msglvl_t msglevel,
447 const uint64_t permission_mask, uint64_t *option_types_found, struct env_set *es)
448{
449 /* usage message */
450 if (argc <= 1)
451 {
452 usage();
453 }
454
455 /* config filename specified only? */
456 if (argc == 2 && strncmp(argv[1], "--", 2))
457 {
458 char *p[MAX_PARMS + 1];
459 CLEAR(p);
460 p[0] = "config";
461 p[1] = argv[1];
462 add_option(options, p, false, NULL, 0, 0, msglevel, permission_mask, option_types_found,
463 es);
464 }
465 else
466 {
467 /* parse command line */
468 for (int i = 1; i < argc; ++i)
469 {
470 char *p[MAX_PARMS + 1];
471 CLEAR(p);
472 p[0] = argv[i];
473 if (strncmp(p[0], "--", 2))
474 {
475 msg(msglevel,
476 "I'm trying to parse \"%s\" as an --option parameter but I don't see a leading '--'",
477 p[0]);
478 }
479 else
480 {
481 p[0] += 2;
482 }
483
484 int j;
485 for (j = 1; j < MAX_PARMS; ++j)
486 {
487 if (i + j < argc)
488 {
489 char *arg = argv[i + j];
490 if (strncmp(arg, "--", 2))
491 {
492 p[j] = arg;
493 }
494 else
495 {
496 break;
497 }
498 }
499 }
500 add_option(options, p, false, NULL, 0, 0, msglevel, permission_mask, option_types_found,
501 es);
502 i += j - 1;
503 }
504 }
505}
506
507bool
508apply_push_options(struct context *c, struct options *options, struct buffer *buf,
509 uint64_t permission_mask, uint64_t *option_types_found,
510 struct env_set *es, bool is_update)
511{
513 int line_num = 0;
514 const char *file = "[PUSH-OPTIONS]";
515 const msglvl_t msglevel = D_PUSH_ERRORS | M_OPTERR;
516
517 while (buf_parse(buf, ',', line, sizeof(line)))
518 {
519 char *p[MAX_PARMS + 1];
520 CLEAR(p);
521 ++line_num;
523 int i = 0;
524
525 /* skip leading spaces matching the behaviour of parse_line */
526 while (isspace(line[i]))
527 {
528 i++;
529 }
530
531 /* If we are not in a 'PUSH_UPDATE' we just check `apply_pull_filter()`
532 * otherwise we must call `check_push_update_option_flags()` first
533 */
536 {
537 /* In case we are in a `PUSH_UPDATE` and `check_push_update_option_flags()`
538 * or `apply_pull_filter()` fail but the option is flagged by `PUSH_OPT_OPTIONAL`,
539 * instead of restarting, we just ignore the option and we process the next one
540 */
542 {
543 continue; /* Ignoring this option */
544 }
545 return false; /* Cause push/pull error and stop push processing */
546 }
547
548 if (parse_line(&line[i], p, SIZE(p) - 1, file, line_num, msglevel, &options->gc))
549 {
550 if (!is_update)
551 {
552 add_option(options, p, false, file, line_num, 0, msglevel, permission_mask,
553 option_types_found, es);
554 }
556 {
557 remove_option(c, options, p, false, file, line_num, msglevel, permission_mask,
558 option_types_found, es);
559 }
560 else
561 {
562 /*
563 * Use persistent push_update_options_found from options struct to track
564 * which option types have been reset across continuation messages.
565 * This prevents routes from being reset on each continuation message.
566 */
567 update_option(c, options, p, false, file, line_num, 0, msglevel, permission_mask,
568 option_types_found, es);
569 }
570 }
571 }
572 return true;
573}
574
575void
576options_server_import(struct options *o, const char *filename, msglvl_t msglevel,
577 const uint64_t permission_mask, uint64_t *option_types_found,
578 struct env_set *es)
579{
580 msg(D_PUSH, "OPTIONS IMPORT: reading client specific options from: %s", filename);
581 read_config_file(o, filename, 0, filename, 0, msglevel, permission_mask, option_types_found,
582 es);
583}
584
585void
586options_string_import(struct options *options, const char *config, const msglvl_t msglevel,
587 const uint64_t permission_mask, uint64_t *option_types_found,
588 struct env_set *es)
589{
590 read_config_string("[CONFIG-STRING]", options, config, msglevel, permission_mask,
591 option_types_found, es);
592}
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:313
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:757
char * string_alloc(const char *str, struct gc_arena *gc)
Duplicate a string, allocating memory under garbage collection.
Definition buffer.c:601
#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:1342
#define streq(x, y)
helper to work around the semantics of the strcmp() return value
Definition buffer.h:1813
static bool buf_safe(const struct buffer *buf, size_t len)
Check whether len bytes can be appended to a buffer.
Definition buffer.h:994
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:748
size_t strlcat(char *dest, const char *src, size_t maxlen)
#define PACKAGE
Definition config.h:486
#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:3658
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:3978
void usage(void)
Definition options.c:3439
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:4165
#define OPTION_PARM_SIZE
Definition options.h:56
#define MAX_PARMS
Definition options.h:51
#define OPTION_LINE_SIZE
Definition options.h:57
static char * read_inline_file(const struct in_src *is, const char *close_tag, int *num_lines, struct gc_arena *gc)
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 bool space(char c)
static void bypass_doubledash(char **p)
static int check_inline_file(const struct in_src *is, char *p[], struct gc_arena *gc)
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)
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
int len
Length in bytes of the actual content within the allocated memory.
Definition buffer.h:76
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
union in_src::@9 u
FILE * fp
struct gc_arena gc
Definition options.h:258
struct gc_arena gc
Definition test_ssl.c:138