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-2025 OpenVPN Inc <sales@openvpn.net>
9 * Copyright (C) 2008-2025 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{
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 char *line_ptr = line;
272 /* Remove leading spaces */
273 while (isspace(*line_ptr))
274 {
275 line_ptr++;
276 }
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]);
321 p[2] = NULL;
323 }
324 }
325 return num_inline_lines;
326}
327
328static int
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 unsigned int permission_mask, unsigned int *option_types_found,
350 struct env_set *es)
351{
352 const int max_recursive_levels = 10;
353 FILE *fp;
354 int line_num;
355 char line[OPTION_LINE_SIZE + 1];
356 char *p[MAX_PARMS + 1];
357
358 ++level;
359 if (level <= max_recursive_levels)
360 {
361 if (streq(file, "stdin"))
362 {
363 fp = stdin;
364 }
365 else
366 {
367 fp = platform_fopen(file, "r");
368 }
369 if (fp)
370 {
371 line_num = 0;
372 while (fgets(line, sizeof(line), fp))
373 {
374 int offset = 0;
375 CLEAR(p);
376 ++line_num;
377 if (strlen(line) == OPTION_LINE_SIZE)
378 {
379 msg(msglevel,
380 "In %s:%d: Maximum option line length (%d) exceeded, line starts with %s",
381 file, line_num, OPTION_LINE_SIZE, line);
382 }
383
384 /* Ignore UTF-8 BOM at start of stream */
385 if (line_num == 1 && strncmp(line, "\xEF\xBB\xBF", 3) == 0)
386 {
387 offset = 3;
388 }
389 if (parse_line(line + offset, p, SIZE(p) - 1, file, line_num, msglevel,
390 &options->gc))
391 {
392 bypass_doubledash(&p[0]);
393 int lines_inline = check_inline_file_via_fp(fp, p, &options->gc);
394 add_option(options, p, lines_inline, file, line_num, level, msglevel,
395 permission_mask, option_types_found, es);
396 line_num += lines_inline;
397 }
398 }
399 if (fp != stdin)
400 {
401 fclose(fp);
402 }
403 }
404 else
405 {
406 msg(msglevel, "In %s:%d: Error opening configuration file: %s", top_file, top_line,
407 file);
408 }
409 }
410 else
411 {
412 msg(msglevel,
413 "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.",
414 top_file, top_line, file);
415 }
416 secure_memzero(line, sizeof(line));
417 CLEAR(p);
418}
419
420void
421read_config_string(const char *prefix, struct options *options, const char *config,
422 const msglvl_t msglevel, const unsigned int permission_mask,
423 unsigned int *option_types_found, struct env_set *es)
424{
425 char line[OPTION_LINE_SIZE];
426 struct buffer multiline;
427 int line_num = 0;
428
429 buf_set_read(&multiline, (uint8_t *)config, strlen(config));
430
431 while (buf_parse(&multiline, '\n', line, sizeof(line)))
432 {
433 char *p[MAX_PARMS + 1];
434 CLEAR(p);
435 ++line_num;
436 if (parse_line(line, p, SIZE(p) - 1, prefix, line_num, msglevel, &options->gc))
437 {
438 bypass_doubledash(&p[0]);
439 int lines_inline = check_inline_file_via_buf(&multiline, p, &options->gc);
440 add_option(options, p, lines_inline, prefix, line_num, 0, msglevel, permission_mask,
441 option_types_found, es);
443 }
444 CLEAR(p);
445 }
446 secure_memzero(line, sizeof(line));
447}
448
449void
450parse_argv(struct options *options, const int argc, char *argv[], const msglvl_t msglevel,
451 const unsigned int permission_mask, unsigned int *option_types_found, struct env_set *es)
452{
453 /* usage message */
454 if (argc <= 1)
455 {
456 usage();
457 }
458
459 /* config filename specified only? */
460 if (argc == 2 && strncmp(argv[1], "--", 2))
461 {
462 char *p[MAX_PARMS + 1];
463 CLEAR(p);
464 p[0] = "config";
465 p[1] = argv[1];
466 add_option(options, p, false, NULL, 0, 0, msglevel, permission_mask, option_types_found,
467 es);
468 }
469 else
470 {
471 /* parse command line */
472 for (int i = 1; i < argc; ++i)
473 {
474 char *p[MAX_PARMS + 1];
475 CLEAR(p);
476 p[0] = argv[i];
477 if (strncmp(p[0], "--", 2))
478 {
479 msg(msglevel,
480 "I'm trying to parse \"%s\" as an --option parameter but I don't see a leading '--'",
481 p[0]);
482 }
483 else
484 {
485 p[0] += 2;
486 }
487
488 int j;
489 for (j = 1; j < MAX_PARMS; ++j)
490 {
491 if (i + j < argc)
492 {
493 char *arg = argv[i + j];
494 if (strncmp(arg, "--", 2))
495 {
496 p[j] = arg;
497 }
498 else
499 {
500 break;
501 }
502 }
503 }
504 add_option(options, p, false, NULL, 0, 0, msglevel, permission_mask, option_types_found,
505 es);
506 i += j - 1;
507 }
508 }
509}
510
511bool
512apply_push_options(struct context *c, struct options *options, struct buffer *buf,
513 unsigned int permission_mask, unsigned int *option_types_found,
514 struct env_set *es, bool is_update)
515{
517 int line_num = 0;
518 const char *file = "[PUSH-OPTIONS]";
519 const msglvl_t msglevel = D_PUSH_ERRORS | M_OPTERR;
520 unsigned int update_options_found = 0;
521
522 while (buf_parse(buf, ',', line, sizeof(line)))
523 {
524 char *p[MAX_PARMS + 1];
525 CLEAR(p);
526 ++line_num;
527 unsigned int push_update_option_flags = 0;
528 int i = 0;
529
530 /* skip leading spaces matching the behaviour of parse_line */
531 while (isspace(line[i]))
532 {
533 i++;
534 }
535
536 /* If we are not in a 'PUSH_UPDATE' we just check `apply_pull_filter()`
537 * otherwise we must call `check_push_update_option_flags()` first
538 */
541 {
542 /* In case we are in a `PUSH_UPDATE` and `check_push_update_option_flags()`
543 * or `apply_pull_filter()` fail but the option is flagged by `PUSH_OPT_OPTIONAL`,
544 * instead of restarting, we just ignore the option and we process the next one
545 */
547 {
548 continue; /* Ignoring this option */
549 }
550 return false; /* Cause push/pull error and stop push processing */
551 }
552
553 if (parse_line(&line[i], p, SIZE(p) - 1, file, line_num, msglevel, &options->gc))
554 {
555 if (!is_update)
556 {
557 add_option(options, p, false, file, line_num, 0, msglevel, permission_mask,
558 option_types_found, es);
559 }
561 {
562 remove_option(c, options, p, false, file, line_num, msglevel, permission_mask,
563 option_types_found, es);
564 }
565 else
566 {
567 update_option(c, options, p, false, file, line_num, 0, msglevel, permission_mask,
568 option_types_found, es, &update_options_found);
569 }
570 }
571 }
572 return true;
573}
574
575void
576options_server_import(struct options *o, const char *filename, msglvl_t msglevel,
577 unsigned int permission_mask, unsigned int *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 unsigned int permission_mask, unsigned int *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)
Definition buffer.c:184
void buf_clear(struct buffer *buf)
Definition buffer.c:163
bool buf_printf(struct buffer *buf, const char *format,...)
Definition buffer.c:241
void * gc_malloc(size_t size, bool clear, struct gc_arena *a)
Definition buffer.c:336
struct buffer alloc_buf(size_t size)
Definition buffer.c:63
bool buf_parse(struct buffer *buf, const int delim, char *line, const int size)
Definition buffer.c:825
char * string_alloc(const char *str, struct gc_arena *gc)
Definition buffer.c:649
#define BSTR(buf)
Definition buffer.h:128
static bool buf_copy(struct buffer *dest, const struct buffer *src)
Definition buffer.h:704
static bool buf_safe(const struct buffer *buf, size_t len)
Definition buffer.h:518
static void buf_set_read(struct buffer *buf, const uint8_t *data, size_t size)
Definition buffer.h:348
static void secure_memzero(void *data, size_t len)
Securely zeroise memory.
Definition buffer.h:414
#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:51
#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:219
#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 unsigned int permission_mask, unsigned int *option_types_found, struct env_set *es)
Resets options found in the PUSH_UPDATE message that are preceded by the - flag.
Definition options.c:5077
void add_option(struct options *options, char *p[], bool is_inline, const char *file, int line, const int level, const msglvl_t msglevel, const unsigned int permission_mask, unsigned int *option_types_found, struct env_set *es)
Definition options.c:5582
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 unsigned int permission_mask, unsigned int *option_types_found, struct env_set *es, unsigned int *update_options_found)
Processes an option to update.
Definition options.c:5397
void usage(void)
Definition options.c:4848
#define streq(x, y)
Definition options.h:730
#define OPTION_PARM_SIZE
Definition options.h:56
#define MAX_PARMS
Definition options.h:51
#define OPTION_LINE_SIZE
Definition options.h:57
void parse_argv(struct options *options, const int argc, char *argv[], const msglvl_t msglevel, const unsigned int permission_mask, unsigned int *option_types_found, struct env_set *es)
static bool in_src_get(const struct in_src *is, char *line, const int size)
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 unsigned int permission_mask, unsigned int *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)
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 options_string_import(struct options *options, const char *config, const msglvl_t msglevel, const unsigned int permission_mask, unsigned int *option_types_found, struct env_set *es)
void options_server_import(struct options *o, const char *filename, msglvl_t msglevel, unsigned int permission_mask, unsigned int *option_types_found, struct env_set *es)
bool apply_push_options(struct context *c, struct options *options, struct buffer *buf, unsigned int permission_mask, unsigned int *option_types_found, struct env_set *es, bool is_update)
static int check_inline_file_via_fp(FILE *fp, char *p[], struct gc_arena *gc)
#define IS_TYPE_FP
void read_config_string(const char *prefix, struct options *options, const char *config, const msglvl_t msglevel, const unsigned int permission_mask, unsigned int *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, unsigned int *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:60
int capacity
Size in bytes of memory allocated by malloc().
Definition buffer.h:61
int len
Length in bytes of the actual content within the allocated memory.
Definition buffer.h:65
Contains all state information for one tunnel.
Definition openvpn.h:474
Garbage collection arena used to keep track of dynamically allocated memory.
Definition buffer.h:116
struct buffer * multiline
union in_src::@9 u
FILE * fp
struct gc_arena gc
Definition options.h:256
struct env_set * es
struct gc_arena gc
Definition test_ssl.c:131