Browse code

options: Factor out parsing code to separate options_parse.c

For easier testability. And because everything that
reduces the length of that file in a sensible manner
is a good idea.

Change-Id: I18e38862df1318740928c6cfa21dc4dcd7d44b89
Signed-off-by: Frank Lichtenheld <frank@lichtenheld.com>
Acked-by: Gert Doering <gert@greenie.muc.de>
Gerrit URL: https://gerrit.openvpn.net/c/openvpn/+/1242
Message-Id: <20251007185110.19267-1-gert@greenie.muc.de>
URL: https://sourceforge.net/p/openvpn/mailman/message/59243506/
Signed-off-by: Gert Doering <gert@greenie.muc.de>

Frank Lichtenheld authored on 2025/10/08 03:51:04
Showing 6 changed files
... ...
@@ -513,6 +513,7 @@ set(SOURCE_FILES
513 513
     src/openvpn/options.h
514 514
     src/openvpn/options_util.c
515 515
     src/openvpn/options_util.h
516
+    src/openvpn/options_parse.c
516 517
     src/openvpn/otime.c
517 518
     src/openvpn/otime.h
518 519
     src/openvpn/ovpn_dco_win.h
... ...
@@ -109,6 +109,7 @@ openvpn_SOURCES = \
109 109
 	openvpn.c openvpn.h \
110 110
 	options.c options.h \
111 111
 	options_util.c options_util.h \
112
+	options_parse.c \
112 113
 	otime.c otime.h \
113 114
 	packet_id.c packet_id.h \
114 115
 	perf.c perf.h \
... ...
@@ -23,6 +23,8 @@
23 23
 #ifndef COMMON_H
24 24
 #define COMMON_H
25 25
 
26
+#include <stdint.h>
27
+
26 28
 /*
27 29
  * Statistics counters and associated printf format.
28 30
  */
... ...
@@ -4838,7 +4838,7 @@ auth_retry_print(void)
4838 4838
 /*
4839 4839
  * Print the help message.
4840 4840
  */
4841
-static void
4841
+void
4842 4842
 usage(void)
4843 4843
 {
4844 4844
     FILE *fp = msg_fp(0);
... ...
@@ -4967,581 +4967,6 @@ atou(const char *str)
4967 4967
 }
4968 4968
 #endif
4969 4969
 
4970
-static inline bool
4971
-space(char c)
4972
-{
4973
-    return c == '\0' || isspace(c);
4974
-}
4975
-
4976
-int
4977
-parse_line(const char *line, char *p[], const int n, const char *file, const int line_num,
4978
-           msglvl_t msglevel, struct gc_arena *gc)
4979
-{
4980
-    const int STATE_INITIAL = 0;
4981
-    const int STATE_READING_QUOTED_PARM = 1;
4982
-    const int STATE_READING_UNQUOTED_PARM = 2;
4983
-    const int STATE_DONE = 3;
4984
-    const int STATE_READING_SQUOTED_PARM = 4;
4985
-
4986
-    const char *error_prefix = "";
4987
-
4988
-    int ret = 0;
4989
-    const char *c = line;
4990
-    int state = STATE_INITIAL;
4991
-    bool backslash = false;
4992
-    char in, out;
4993
-
4994
-    char parm[OPTION_PARM_SIZE];
4995
-    unsigned int parm_len = 0;
4996
-
4997
-    msglevel &= ~M_OPTERR;
4998
-
4999
-    if (msglevel & M_MSG_VIRT_OUT)
5000
-    {
5001
-        error_prefix = "ERROR: ";
5002
-    }
5003
-
5004
-    do
5005
-    {
5006
-        in = *c;
5007
-        out = 0;
5008
-
5009
-        if (!backslash && in == '\\' && state != STATE_READING_SQUOTED_PARM)
5010
-        {
5011
-            backslash = true;
5012
-        }
5013
-        else
5014
-        {
5015
-            if (state == STATE_INITIAL)
5016
-            {
5017
-                if (!space(in))
5018
-                {
5019
-                    if (in == ';' || in == '#') /* comment */
5020
-                    {
5021
-                        break;
5022
-                    }
5023
-                    if (!backslash && in == '\"')
5024
-                    {
5025
-                        state = STATE_READING_QUOTED_PARM;
5026
-                    }
5027
-                    else if (!backslash && in == '\'')
5028
-                    {
5029
-                        state = STATE_READING_SQUOTED_PARM;
5030
-                    }
5031
-                    else
5032
-                    {
5033
-                        out = in;
5034
-                        state = STATE_READING_UNQUOTED_PARM;
5035
-                    }
5036
-                }
5037
-            }
5038
-            else if (state == STATE_READING_UNQUOTED_PARM)
5039
-            {
5040
-                if (!backslash && space(in))
5041
-                {
5042
-                    state = STATE_DONE;
5043
-                }
5044
-                else
5045
-                {
5046
-                    out = in;
5047
-                }
5048
-            }
5049
-            else if (state == STATE_READING_QUOTED_PARM)
5050
-            {
5051
-                if (!backslash && in == '\"')
5052
-                {
5053
-                    state = STATE_DONE;
5054
-                }
5055
-                else
5056
-                {
5057
-                    out = in;
5058
-                }
5059
-            }
5060
-            else if (state == STATE_READING_SQUOTED_PARM)
5061
-            {
5062
-                if (in == '\'')
5063
-                {
5064
-                    state = STATE_DONE;
5065
-                }
5066
-                else
5067
-                {
5068
-                    out = in;
5069
-                }
5070
-            }
5071
-            if (state == STATE_DONE)
5072
-            {
5073
-                /* ASSERT (parm_len > 0); */
5074
-                p[ret] = gc_malloc(parm_len + 1, true, gc);
5075
-                memcpy(p[ret], parm, parm_len);
5076
-                p[ret][parm_len] = '\0';
5077
-                state = STATE_INITIAL;
5078
-                parm_len = 0;
5079
-                ++ret;
5080
-            }
5081
-
5082
-            if (backslash && out)
5083
-            {
5084
-                if (!(out == '\\' || out == '\"' || space(out)))
5085
-                {
5086
-#ifdef ENABLE_SMALL
5087
-                    msg(msglevel, "%sOptions warning: Bad backslash ('\\') usage in %s:%d",
5088
-                        error_prefix, file, line_num);
5089
-#else
5090
-                    msg(msglevel,
5091
-                        "%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
5092
-                        "\\\\static.key\"",
5093
-                        error_prefix, file, line_num);
5094
-#endif
5095
-                    return 0;
5096
-                }
5097
-            }
5098
-            backslash = false;
5099
-        }
5100
-
5101
-        /* store parameter character */
5102
-        if (out)
5103
-        {
5104
-            if (parm_len >= SIZE(parm))
5105
-            {
5106
-                parm[SIZE(parm) - 1] = 0;
5107
-                msg(msglevel, "%sOptions error: Parameter at %s:%d is too long (%d chars max): %s",
5108
-                    error_prefix, file, line_num, (int)SIZE(parm), parm);
5109
-                return 0;
5110
-            }
5111
-            parm[parm_len++] = out;
5112
-        }
5113
-
5114
-        /* avoid overflow if too many parms in one config file line */
5115
-        if (ret >= n)
5116
-        {
5117
-            break;
5118
-        }
5119
-
5120
-    } while (*c++ != '\0');
5121
-
5122
-    if (state == STATE_READING_QUOTED_PARM)
5123
-    {
5124
-        msg(msglevel, "%sOptions error: No closing quotation (\") in %s:%d", error_prefix, file,
5125
-            line_num);
5126
-        return 0;
5127
-    }
5128
-    if (state == STATE_READING_SQUOTED_PARM)
5129
-    {
5130
-        msg(msglevel, "%sOptions error: No closing single quotation (\') in %s:%d", error_prefix,
5131
-            file, line_num);
5132
-        return 0;
5133
-    }
5134
-    if (state != STATE_INITIAL)
5135
-    {
5136
-        msg(msglevel, "%sOptions error: Residual parse state (%d) in %s:%d", error_prefix, state,
5137
-            file, line_num);
5138
-        return 0;
5139
-    }
5140
-#if 0
5141
-    {
5142
-        int i;
5143
-        for (i = 0; i < ret; ++i)
5144
-        {
5145
-            msg(M_INFO|M_NOPREFIX, "%s:%d ARG[%d] '%s'", file, line_num, i, p[i]);
5146
-        }
5147
-    }
5148
-#endif
5149
-    return ret;
5150
-}
5151
-
5152
-static void
5153
-bypass_doubledash(char **p)
5154
-{
5155
-    if (strlen(*p) >= 3 && !strncmp(*p, "--", 2))
5156
-    {
5157
-        *p += 2;
5158
-    }
5159
-}
5160
-
5161
-struct in_src
5162
-{
5163
-#define IS_TYPE_FP  1
5164
-#define IS_TYPE_BUF 2
5165
-    int type;
5166
-    union
5167
-    {
5168
-        FILE *fp;
5169
-        struct buffer *multiline;
5170
-    } u;
5171
-};
5172
-
5173
-static bool
5174
-in_src_get(const struct in_src *is, char *line, const int size)
5175
-{
5176
-    if (is->type == IS_TYPE_FP)
5177
-    {
5178
-        return BOOL_CAST(fgets(line, size, is->u.fp));
5179
-    }
5180
-    else if (is->type == IS_TYPE_BUF)
5181
-    {
5182
-        bool status = buf_parse(is->u.multiline, '\n', line, size);
5183
-        if ((int)strlen(line) + 1 < size)
5184
-        {
5185
-            strcat(line, "\n");
5186
-        }
5187
-        return status;
5188
-    }
5189
-    else
5190
-    {
5191
-        ASSERT(0);
5192
-        return false;
5193
-    }
5194
-}
5195
-
5196
-static char *
5197
-read_inline_file(struct in_src *is, const char *close_tag, int *num_lines, struct gc_arena *gc)
5198
-{
5199
-    char line[OPTION_LINE_SIZE];
5200
-    struct buffer buf = alloc_buf(8 * OPTION_LINE_SIZE);
5201
-    char *ret;
5202
-    bool endtagfound = false;
5203
-
5204
-    while (in_src_get(is, line, sizeof(line)))
5205
-    {
5206
-        (*num_lines)++;
5207
-        char *line_ptr = line;
5208
-        /* Remove leading spaces */
5209
-        while (isspace(*line_ptr))
5210
-        {
5211
-            line_ptr++;
5212
-        }
5213
-        if (!strncmp(line_ptr, close_tag, strlen(close_tag)))
5214
-        {
5215
-            endtagfound = true;
5216
-            break;
5217
-        }
5218
-        if (!buf_safe(&buf, strlen(line) + 1))
5219
-        {
5220
-            /* Increase buffer size */
5221
-            struct buffer buf2 = alloc_buf(buf.capacity * 2);
5222
-            ASSERT(buf_copy(&buf2, &buf));
5223
-            buf_clear(&buf);
5224
-            free_buf(&buf);
5225
-            buf = buf2;
5226
-        }
5227
-        buf_printf(&buf, "%s", line);
5228
-    }
5229
-    if (!endtagfound)
5230
-    {
5231
-        msg(M_FATAL, "ERROR: Endtag %s missing", close_tag);
5232
-    }
5233
-    ret = string_alloc(BSTR(&buf), gc);
5234
-    buf_clear(&buf);
5235
-    free_buf(&buf);
5236
-    secure_memzero(line, sizeof(line));
5237
-    return ret;
5238
-}
5239
-
5240
-static int
5241
-check_inline_file(struct in_src *is, char *p[], struct gc_arena *gc)
5242
-{
5243
-    int num_inline_lines = 0;
5244
-
5245
-    if (p[0] && !p[1])
5246
-    {
5247
-        char *arg = p[0];
5248
-        if (arg[0] == '<' && arg[strlen(arg) - 1] == '>')
5249
-        {
5250
-            struct buffer close_tag;
5251
-
5252
-            arg[strlen(arg) - 1] = '\0';
5253
-            p[0] = string_alloc(arg + 1, gc);
5254
-            close_tag = alloc_buf(strlen(p[0]) + 4);
5255
-            buf_printf(&close_tag, "</%s>", p[0]);
5256
-            p[1] = read_inline_file(is, BSTR(&close_tag), &num_inline_lines, gc);
5257
-            p[2] = NULL;
5258
-            free_buf(&close_tag);
5259
-        }
5260
-    }
5261
-    return num_inline_lines;
5262
-}
5263
-
5264
-static int
5265
-check_inline_file_via_fp(FILE *fp, char *p[], struct gc_arena *gc)
5266
-{
5267
-    struct in_src is;
5268
-    is.type = IS_TYPE_FP;
5269
-    is.u.fp = fp;
5270
-    return check_inline_file(&is, p, gc);
5271
-}
5272
-
5273
-static int
5274
-check_inline_file_via_buf(struct buffer *multiline, char *p[], struct gc_arena *gc)
5275
-{
5276
-    struct in_src is;
5277
-    is.type = IS_TYPE_BUF;
5278
-    is.u.multiline = multiline;
5279
-    return check_inline_file(&is, p, gc);
5280
-}
5281
-
5282
-static void add_option(struct options *options, char *p[], bool is_inline, const char *file,
5283
-                       int line, const int level, const msglvl_t msglevel,
5284
-                       const unsigned int permission_mask, unsigned int *option_types_found,
5285
-                       struct env_set *es);
5286
-
5287
-static void remove_option(struct context *c, struct options *options, char *p[], bool is_inline,
5288
-                          const char *file, int line, const msglvl_t msglevel,
5289
-                          const unsigned int permission_mask, unsigned int *option_types_found,
5290
-                          struct env_set *es);
5291
-
5292
-static void update_option(struct context *c, struct options *options, char *p[], bool is_inline,
5293
-                          const char *file, int line, const int level, const msglvl_t msglevel,
5294
-                          const unsigned int permission_mask, unsigned int *option_types_found,
5295
-                          struct env_set *es, unsigned int *update_options_found);
5296
-
5297
-static void
5298
-read_config_file(struct options *options, const char *file, int level, const char *top_file,
5299
-                 const int top_line, const msglvl_t msglevel,
5300
-                 const unsigned int permission_mask, unsigned int *option_types_found,
5301
-                 struct env_set *es)
5302
-{
5303
-    const int max_recursive_levels = 10;
5304
-    FILE *fp;
5305
-    int line_num;
5306
-    char line[OPTION_LINE_SIZE + 1];
5307
-    char *p[MAX_PARMS + 1];
5308
-
5309
-    ++level;
5310
-    if (level <= max_recursive_levels)
5311
-    {
5312
-        if (streq(file, "stdin"))
5313
-        {
5314
-            fp = stdin;
5315
-        }
5316
-        else
5317
-        {
5318
-            fp = platform_fopen(file, "r");
5319
-        }
5320
-        if (fp)
5321
-        {
5322
-            line_num = 0;
5323
-            while (fgets(line, sizeof(line), fp))
5324
-            {
5325
-                int offset = 0;
5326
-                CLEAR(p);
5327
-                ++line_num;
5328
-                if (strlen(line) == OPTION_LINE_SIZE)
5329
-                {
5330
-                    msg(msglevel,
5331
-                        "In %s:%d: Maximum option line length (%d) exceeded, line starts with %s",
5332
-                        file, line_num, OPTION_LINE_SIZE, line);
5333
-                }
5334
-
5335
-                /* Ignore UTF-8 BOM at start of stream */
5336
-                if (line_num == 1 && strncmp(line, "\xEF\xBB\xBF", 3) == 0)
5337
-                {
5338
-                    offset = 3;
5339
-                }
5340
-                if (parse_line(line + offset, p, SIZE(p) - 1, file, line_num, msglevel,
5341
-                               &options->gc))
5342
-                {
5343
-                    bypass_doubledash(&p[0]);
5344
-                    int lines_inline = check_inline_file_via_fp(fp, p, &options->gc);
5345
-                    add_option(options, p, lines_inline, file, line_num, level, msglevel,
5346
-                               permission_mask, option_types_found, es);
5347
-                    line_num += lines_inline;
5348
-                }
5349
-            }
5350
-            if (fp != stdin)
5351
-            {
5352
-                fclose(fp);
5353
-            }
5354
-        }
5355
-        else
5356
-        {
5357
-            msg(msglevel, "In %s:%d: Error opening configuration file: %s", top_file, top_line,
5358
-                file);
5359
-        }
5360
-    }
5361
-    else
5362
-    {
5363
-        msg(msglevel,
5364
-            "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.",
5365
-            top_file, top_line, file);
5366
-    }
5367
-    secure_memzero(line, sizeof(line));
5368
-    CLEAR(p);
5369
-}
5370
-
5371
-static void
5372
-read_config_string(const char *prefix, struct options *options, const char *config,
5373
-                   const msglvl_t msglevel, const unsigned int permission_mask,
5374
-                   unsigned int *option_types_found, struct env_set *es)
5375
-{
5376
-    char line[OPTION_LINE_SIZE];
5377
-    struct buffer multiline;
5378
-    int line_num = 0;
5379
-
5380
-    buf_set_read(&multiline, (uint8_t *)config, strlen(config));
5381
-
5382
-    while (buf_parse(&multiline, '\n', line, sizeof(line)))
5383
-    {
5384
-        char *p[MAX_PARMS + 1];
5385
-        CLEAR(p);
5386
-        ++line_num;
5387
-        if (parse_line(line, p, SIZE(p) - 1, prefix, line_num, msglevel, &options->gc))
5388
-        {
5389
-            bypass_doubledash(&p[0]);
5390
-            int lines_inline = check_inline_file_via_buf(&multiline, p, &options->gc);
5391
-            add_option(options, p, lines_inline, prefix, line_num, 0, msglevel, permission_mask,
5392
-                       option_types_found, es);
5393
-            line_num += lines_inline;
5394
-        }
5395
-        CLEAR(p);
5396
-    }
5397
-    secure_memzero(line, sizeof(line));
5398
-}
5399
-
5400
-void
5401
-parse_argv(struct options *options, const int argc, char *argv[], const msglvl_t msglevel,
5402
-           const unsigned int permission_mask, unsigned int *option_types_found, struct env_set *es)
5403
-{
5404
-    /* usage message */
5405
-    if (argc <= 1)
5406
-    {
5407
-        usage();
5408
-    }
5409
-
5410
-    /* config filename specified only? */
5411
-    if (argc == 2 && strncmp(argv[1], "--", 2))
5412
-    {
5413
-        char *p[MAX_PARMS + 1];
5414
-        CLEAR(p);
5415
-        p[0] = "config";
5416
-        p[1] = argv[1];
5417
-        add_option(options, p, false, NULL, 0, 0, msglevel, permission_mask, option_types_found,
5418
-                   es);
5419
-    }
5420
-    else
5421
-    {
5422
-        /* parse command line */
5423
-        for (int i = 1; i < argc; ++i)
5424
-        {
5425
-            char *p[MAX_PARMS + 1];
5426
-            CLEAR(p);
5427
-            p[0] = argv[i];
5428
-            if (strncmp(p[0], "--", 2))
5429
-            {
5430
-                msg(msglevel,
5431
-                    "I'm trying to parse \"%s\" as an --option parameter but I don't see a leading '--'",
5432
-                    p[0]);
5433
-            }
5434
-            else
5435
-            {
5436
-                p[0] += 2;
5437
-            }
5438
-
5439
-            int j;
5440
-            for (j = 1; j < MAX_PARMS; ++j)
5441
-            {
5442
-                if (i + j < argc)
5443
-                {
5444
-                    char *arg = argv[i + j];
5445
-                    if (strncmp(arg, "--", 2))
5446
-                    {
5447
-                        p[j] = arg;
5448
-                    }
5449
-                    else
5450
-                    {
5451
-                        break;
5452
-                    }
5453
-                }
5454
-            }
5455
-            add_option(options, p, false, NULL, 0, 0, msglevel, permission_mask, option_types_found,
5456
-                       es);
5457
-            i += j - 1;
5458
-        }
5459
-    }
5460
-}
5461
-
5462
-bool
5463
-apply_push_options(struct context *c, struct options *options, struct buffer *buf,
5464
-                   unsigned int permission_mask, unsigned int *option_types_found,
5465
-                   struct env_set *es, bool is_update)
5466
-{
5467
-    char line[OPTION_PARM_SIZE];
5468
-    int line_num = 0;
5469
-    const char *file = "[PUSH-OPTIONS]";
5470
-    const msglvl_t msglevel = D_PUSH_ERRORS | M_OPTERR;
5471
-    unsigned int update_options_found = 0;
5472
-
5473
-    while (buf_parse(buf, ',', line, sizeof(line)))
5474
-    {
5475
-        char *p[MAX_PARMS + 1];
5476
-        CLEAR(p);
5477
-        ++line_num;
5478
-        unsigned int push_update_option_flags = 0;
5479
-        int i = 0;
5480
-
5481
-        /* skip leading spaces matching the behaviour of parse_line */
5482
-        while (isspace(line[i]))
5483
-        {
5484
-            i++;
5485
-        }
5486
-
5487
-        /* If we are not in a 'PUSH_UPDATE' we just check `apply_pull_filter()`
5488
-         * otherwise we must call `check_push_update_option_flags()` first
5489
-         */
5490
-        if ((is_update && !check_push_update_option_flags(line, &i, &push_update_option_flags))
5491
-            || !apply_pull_filter(options, &line[i]))
5492
-        {
5493
-            /* In case we are in a `PUSH_UPDATE` and `check_push_update_option_flags()`
5494
-             * or `apply_pull_filter()` fail but the option is flagged by `PUSH_OPT_OPTIONAL`,
5495
-             * instead of restarting, we just ignore the option and we process the next one
5496
-             */
5497
-            if (push_update_option_flags & PUSH_OPT_OPTIONAL)
5498
-            {
5499
-                continue; /* Ignoring this option */
5500
-            }
5501
-            return false; /* Cause push/pull error and stop push processing */
5502
-        }
5503
-
5504
-        if (parse_line(&line[i], p, SIZE(p) - 1, file, line_num, msglevel, &options->gc))
5505
-        {
5506
-            if (!is_update)
5507
-            {
5508
-                add_option(options, p, false, file, line_num, 0, msglevel, permission_mask,
5509
-                           option_types_found, es);
5510
-            }
5511
-            else if (push_update_option_flags & PUSH_OPT_TO_REMOVE)
5512
-            {
5513
-                remove_option(c, options, p, false, file, line_num, msglevel, permission_mask,
5514
-                              option_types_found, es);
5515
-            }
5516
-            else
5517
-            {
5518
-                update_option(c, options, p, false, file, line_num, 0, msglevel, permission_mask,
5519
-                              option_types_found, es, &update_options_found);
5520
-            }
5521
-        }
5522
-    }
5523
-    return true;
5524
-}
5525
-
5526
-void
5527
-options_server_import(struct options *o, const char *filename, msglvl_t msglevel,
5528
-                      unsigned int permission_mask, unsigned int *option_types_found,
5529
-                      struct env_set *es)
5530
-{
5531
-    msg(D_PUSH, "OPTIONS IMPORT: reading client specific options from: %s", filename);
5532
-    read_config_file(o, filename, 0, filename, 0, msglevel, permission_mask, option_types_found,
5533
-                     es);
5534
-}
5535
-
5536
-void
5537
-options_string_import(struct options *options, const char *config, const msglvl_t msglevel,
5538
-                      const unsigned int permission_mask, unsigned int *option_types_found,
5539
-                      struct env_set *es)
5540
-{
5541
-    read_config_string("[CONFIG-STRING]", options, config, msglevel, permission_mask,
5542
-                       option_types_found, es);
5543
-}
5544
-
5545 4970
 #define VERIFY_PERMISSION(mask)                                                               \
5546 4971
     {                                                                                         \
5547 4972
         if (!verify_permission(p[0], file, line, (mask), permission_mask, option_types_found, \
... ...
@@ -5642,27 +5067,7 @@ msglevel_forward_compatible(struct options *options, const msglvl_t msglevel)
5642 5642
         option_ptr->flags = 0;                 \
5643 5643
     }
5644 5644
 
5645
-/**
5646
- * @brief Resets options found in the PUSH_UPDATE message that are preceded by the `-` flag.
5647
- *        This function is used in push-updates to reset specified options.
5648
- *        The number of parameters `p` must always be 1. If the permission is verified,
5649
- *        all related options are erased or reset to their default values.
5650
- *        Upon successful permission verification (by VERIFY_PERMISSION()),
5651
- *        `option_types_found` is filled with the flag corresponding to the option.
5652
- *
5653
- * @param c The context structure.
5654
- * @param options A pointer to the options structure.
5655
- * @param p An array of strings containing the options and their parameters.
5656
- * @param is_inline A boolean indicating if the option is inline.
5657
- * @param file The file where the function is called.
5658
- * @param line The line number where the function is called.
5659
- * @param msglevel The message level.
5660
- * @param permission_mask The permission mask used by VERIFY_PERMISSION().
5661
- * @param option_types_found A pointer to the variable where the flags corresponding to the options
5662
- * found are stored.
5663
- * @param es The environment set structure.
5664
- */
5665
-static void
5645
+void
5666 5646
 remove_option(struct context *c, struct options *options, char *p[], bool is_inline,
5667 5647
               const char *file, int line, const msglvl_t msglevel,
5668 5648
               const unsigned int permission_mask, unsigned int *option_types_found,
... ...
@@ -5982,30 +5387,7 @@ check_dns_option(struct options *options, char *p[], const msglvl_t msglevel, bo
5982 5982
     return true;
5983 5983
 }
5984 5984
 
5985
-/**
5986
- * @brief Processes an option to update. It first checks whether it has already
5987
- *        received an option of the same type within the same update message.
5988
- *        If the option has already been received, it calls add_option().
5989
- *        Otherwise, it deletes all existing values related to that option before calling
5990
- * add_option().
5991
- *
5992
- * @param c The context structure.
5993
- * @param options A pointer to the options structure.
5994
- * @param p An array of strings containing the options and their parameters.
5995
- * @param is_inline A boolean indicating if the option is inline.
5996
- * @param file The file where the function is called.
5997
- * @param line The line number where the function is called.
5998
- * @param level The level of the option.
5999
- * @param msglevel The message level for logging.
6000
- * @param permission_mask The permission mask used by VERIFY_PERMISSION().
6001
- * @param option_types_found A pointer to the variable where the flags corresponding to the options
6002
- * found are stored.
6003
- * @param es The environment set structure.
6004
- * @param update_options_found A pointer to the variable where the flags corresponding to the update
6005
- * options found are stored, used to check if an option of the same type has already been processed
6006
- * by update_option() within the same push-update message.
6007
- */
6008
-static void
5985
+void
6009 5986
 update_option(struct context *c, struct options *options, char *p[], bool is_inline,
6010 5987
               const char *file, int line, const int level, const msglvl_t msglevel,
6011 5988
               const unsigned int permission_mask, unsigned int *option_types_found,
... ...
@@ -6190,7 +5572,7 @@ key_is_external(const struct options *options)
6190 6190
     return ret;
6191 6191
 }
6192 6192
 
6193
-static void
6193
+void
6194 6194
 add_option(struct options *options, char *p[], bool is_inline, const char *file, int line,
6195 6195
            const int level, const msglvl_t msglevel, const unsigned int permission_mask,
6196 6196
            unsigned int *option_types_found, struct env_set *es)
... ...
@@ -822,14 +822,83 @@ struct pull_filter_list
822 822
     struct pull_filter *tail;
823 823
 };
824 824
 
825
+void add_option(struct options *options, char *p[], bool is_inline, const char *file,
826
+                int line, const int level, const msglvl_t msglevel,
827
+                const unsigned int permission_mask, unsigned int *option_types_found,
828
+                struct env_set *es);
829
+
830
+/**
831
+ * @brief Resets options found in the PUSH_UPDATE message that are preceded by the `-` flag.
832
+ *        This function is used in push-updates to reset specified options.
833
+ *        The number of parameters `p` must always be 1. If the permission is verified,
834
+ *        all related options are erased or reset to their default values.
835
+ *        Upon successful permission verification (by VERIFY_PERMISSION()),
836
+ *        `option_types_found` is filled with the flag corresponding to the option.
837
+ *
838
+ * @param c The context structure.
839
+ * @param options A pointer to the options structure.
840
+ * @param p An array of strings containing the options and their parameters.
841
+ * @param is_inline A boolean indicating if the option is inline.
842
+ * @param file The file where the function is called.
843
+ * @param line The line number where the function is called.
844
+ * @param msglevel The message level.
845
+ * @param permission_mask The permission mask used by VERIFY_PERMISSION().
846
+ * @param option_types_found A pointer to the variable where the flags corresponding to the options
847
+ * found are stored.
848
+ * @param es The environment set structure.
849
+ */
850
+void remove_option(struct context *c, struct options *options, char *p[], bool is_inline,
851
+                   const char *file, int line, const msglvl_t msglevel,
852
+                   const unsigned int permission_mask, unsigned int *option_types_found,
853
+                   struct env_set *es);
854
+
855
+/**
856
+ * @brief Processes an option to update. It first checks whether it has already
857
+ *        received an option of the same type within the same update message.
858
+ *        If the option has already been received, it calls add_option().
859
+ *        Otherwise, it deletes all existing values related to that option before calling
860
+ * add_option().
861
+ *
862
+ * @param c The context structure.
863
+ * @param options A pointer to the options structure.
864
+ * @param p An array of strings containing the options and their parameters.
865
+ * @param is_inline A boolean indicating if the option is inline.
866
+ * @param file The file where the function is called.
867
+ * @param line The line number where the function is called.
868
+ * @param level The level of the option.
869
+ * @param msglevel The message level for logging.
870
+ * @param permission_mask The permission mask used by VERIFY_PERMISSION().
871
+ * @param option_types_found A pointer to the variable where the flags corresponding to the options
872
+ * found are stored.
873
+ * @param es The environment set structure.
874
+ * @param update_options_found A pointer to the variable where the flags corresponding to the update
875
+ * options found are stored, used to check if an option of the same type has already been processed
876
+ * by update_option() within the same push-update message.
877
+ */
878
+void update_option(struct context *c, struct options *options, char *p[], bool is_inline,
879
+                   const char *file, int line, const int level, const msglvl_t msglevel,
880
+                   const unsigned int permission_mask, unsigned int *option_types_found,
881
+                   struct env_set *es, unsigned int *update_options_found);
882
+
825 883
 void parse_argv(struct options *options, const int argc, char *argv[], const msglvl_t msglevel,
826 884
                 const unsigned int permission_mask, unsigned int *option_types_found,
827 885
                 struct env_set *es);
828 886
 
887
+void read_config_file(struct options *options, const char *file, int level, const char *top_file,
888
+                      const int top_line, const msglvl_t msglevel,
889
+                      const unsigned int permission_mask, unsigned int *option_types_found,
890
+                      struct env_set *es);
891
+
892
+void read_config_string(const char *prefix, struct options *options, const char *config,
893
+                        const msglvl_t msglevel, const unsigned int permission_mask,
894
+                        unsigned int *option_types_found, struct env_set *es);
895
+
829 896
 void notnull(const char *arg, const char *description);
830 897
 
831 898
 void usage_small(void);
832 899
 
900
+void usage(void);
901
+
833 902
 void show_library_versions(const unsigned int flags);
834 903
 
835 904
 #ifdef _WIN32
836 905
new file mode 100644
... ...
@@ -0,0 +1,592 @@
0
+/*
1
+ *  OpenVPN -- An application to securely tunnel IP networks
2
+ *             over a single UDP port, with support for SSL/TLS-based
3
+ *             session authentication and key exchange,
4
+ *             packet encryption, packet authentication, and
5
+ *             packet compression.
6
+ *
7
+ *  Copyright (C) 2002-2025 OpenVPN Inc <sales@openvpn.net>
8
+ *  Copyright (C) 2008-2025 David Sommerseth <dazo@eurephia.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 <string.h>
28
+
29
+#include "options.h"
30
+#include "options_util.h"
31
+#include "push.h"
32
+
33
+static void
34
+bypass_doubledash(char **p)
35
+{
36
+    if (strlen(*p) >= 3 && !strncmp(*p, "--", 2))
37
+    {
38
+        *p += 2;
39
+    }
40
+}
41
+
42
+static inline bool
43
+space(char c)
44
+{
45
+    return c == '\0' || isspace(c);
46
+}
47
+
48
+int
49
+parse_line(const char *line, char *p[], const int n, const char *file, const int line_num,
50
+           msglvl_t msglevel, struct gc_arena *gc)
51
+{
52
+    const int STATE_INITIAL = 0;
53
+    const int STATE_READING_QUOTED_PARM = 1;
54
+    const int STATE_READING_UNQUOTED_PARM = 2;
55
+    const int STATE_DONE = 3;
56
+    const int STATE_READING_SQUOTED_PARM = 4;
57
+
58
+    const char *error_prefix = "";
59
+
60
+    int ret = 0;
61
+    const char *c = line;
62
+    int state = STATE_INITIAL;
63
+    bool backslash = false;
64
+    char in, out;
65
+
66
+    char parm[OPTION_PARM_SIZE];
67
+    unsigned int parm_len = 0;
68
+
69
+    msglevel &= ~M_OPTERR;
70
+
71
+    if (msglevel & M_MSG_VIRT_OUT)
72
+    {
73
+        error_prefix = "ERROR: ";
74
+    }
75
+
76
+    do
77
+    {
78
+        in = *c;
79
+        out = 0;
80
+
81
+        if (!backslash && in == '\\' && state != STATE_READING_SQUOTED_PARM)
82
+        {
83
+            backslash = true;
84
+        }
85
+        else
86
+        {
87
+            if (state == STATE_INITIAL)
88
+            {
89
+                if (!space(in))
90
+                {
91
+                    if (in == ';' || in == '#') /* comment */
92
+                    {
93
+                        break;
94
+                    }
95
+                    if (!backslash && in == '\"')
96
+                    {
97
+                        state = STATE_READING_QUOTED_PARM;
98
+                    }
99
+                    else if (!backslash && in == '\'')
100
+                    {
101
+                        state = STATE_READING_SQUOTED_PARM;
102
+                    }
103
+                    else
104
+                    {
105
+                        out = in;
106
+                        state = STATE_READING_UNQUOTED_PARM;
107
+                    }
108
+                }
109
+            }
110
+            else if (state == STATE_READING_UNQUOTED_PARM)
111
+            {
112
+                if (!backslash && space(in))
113
+                {
114
+                    state = STATE_DONE;
115
+                }
116
+                else
117
+                {
118
+                    out = in;
119
+                }
120
+            }
121
+            else if (state == STATE_READING_QUOTED_PARM)
122
+            {
123
+                if (!backslash && in == '\"')
124
+                {
125
+                    state = STATE_DONE;
126
+                }
127
+                else
128
+                {
129
+                    out = in;
130
+                }
131
+            }
132
+            else if (state == STATE_READING_SQUOTED_PARM)
133
+            {
134
+                if (in == '\'')
135
+                {
136
+                    state = STATE_DONE;
137
+                }
138
+                else
139
+                {
140
+                    out = in;
141
+                }
142
+            }
143
+            if (state == STATE_DONE)
144
+            {
145
+                /* ASSERT (parm_len > 0); */
146
+                p[ret] = gc_malloc(parm_len + 1, true, gc);
147
+                memcpy(p[ret], parm, parm_len);
148
+                p[ret][parm_len] = '\0';
149
+                state = STATE_INITIAL;
150
+                parm_len = 0;
151
+                ++ret;
152
+            }
153
+
154
+            if (backslash && out)
155
+            {
156
+                if (!(out == '\\' || out == '\"' || space(out)))
157
+                {
158
+#ifdef ENABLE_SMALL
159
+                    msg(msglevel, "%sOptions warning: Bad backslash ('\\') usage in %s:%d",
160
+                        error_prefix, file, line_num);
161
+#else
162
+                    msg(msglevel,
163
+                        "%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
164
+                        "\\\\static.key\"",
165
+                        error_prefix, file, line_num);
166
+#endif
167
+                    return 0;
168
+                }
169
+            }
170
+            backslash = false;
171
+        }
172
+
173
+        /* store parameter character */
174
+        if (out)
175
+        {
176
+            if (parm_len >= SIZE(parm))
177
+            {
178
+                parm[SIZE(parm) - 1] = 0;
179
+                msg(msglevel, "%sOptions error: Parameter at %s:%d is too long (%d chars max): %s",
180
+                    error_prefix, file, line_num, (int)SIZE(parm), parm);
181
+                return 0;
182
+            }
183
+            parm[parm_len++] = out;
184
+        }
185
+
186
+        /* avoid overflow if too many parms in one config file line */
187
+        if (ret >= n)
188
+        {
189
+            break;
190
+        }
191
+
192
+    } while (*c++ != '\0');
193
+
194
+    if (state == STATE_READING_QUOTED_PARM)
195
+    {
196
+        msg(msglevel, "%sOptions error: No closing quotation (\") in %s:%d", error_prefix, file,
197
+            line_num);
198
+        return 0;
199
+    }
200
+    if (state == STATE_READING_SQUOTED_PARM)
201
+    {
202
+        msg(msglevel, "%sOptions error: No closing single quotation (\') in %s:%d", error_prefix,
203
+            file, line_num);
204
+        return 0;
205
+    }
206
+    if (state != STATE_INITIAL)
207
+    {
208
+        msg(msglevel, "%sOptions error: Residual parse state (%d) in %s:%d", error_prefix, state,
209
+            file, line_num);
210
+        return 0;
211
+    }
212
+#if 0
213
+    {
214
+        int i;
215
+        for (i = 0; i < ret; ++i)
216
+        {
217
+            msg(M_INFO|M_NOPREFIX, "%s:%d ARG[%d] '%s'", file, line_num, i, p[i]);
218
+        }
219
+    }
220
+#endif
221
+    return ret;
222
+}
223
+
224
+struct in_src
225
+{
226
+#define IS_TYPE_FP  1
227
+#define IS_TYPE_BUF 2
228
+    int type;
229
+    union
230
+    {
231
+        FILE *fp;
232
+        struct buffer *multiline;
233
+    } u;
234
+};
235
+
236
+static bool
237
+in_src_get(const struct in_src *is, char *line, const int size)
238
+{
239
+    if (is->type == IS_TYPE_FP)
240
+    {
241
+        return BOOL_CAST(fgets(line, size, is->u.fp));
242
+    }
243
+    else if (is->type == IS_TYPE_BUF)
244
+    {
245
+        bool status = buf_parse(is->u.multiline, '\n', line, size);
246
+        if ((int)strlen(line) + 1 < size)
247
+        {
248
+            strcat(line, "\n");
249
+        }
250
+        return status;
251
+    }
252
+    else
253
+    {
254
+        ASSERT(0);
255
+        return false;
256
+    }
257
+}
258
+
259
+static char *
260
+read_inline_file(struct in_src *is, const char *close_tag, int *num_lines, struct gc_arena *gc)
261
+{
262
+    char line[OPTION_LINE_SIZE];
263
+    struct buffer buf = alloc_buf(8 * OPTION_LINE_SIZE);
264
+    char *ret;
265
+    bool endtagfound = false;
266
+
267
+    while (in_src_get(is, line, sizeof(line)))
268
+    {
269
+        (*num_lines)++;
270
+        char *line_ptr = line;
271
+        /* Remove leading spaces */
272
+        while (isspace(*line_ptr))
273
+        {
274
+            line_ptr++;
275
+        }
276
+        if (!strncmp(line_ptr, close_tag, strlen(close_tag)))
277
+        {
278
+            endtagfound = true;
279
+            break;
280
+        }
281
+        if (!buf_safe(&buf, strlen(line) + 1))
282
+        {
283
+            /* Increase buffer size */
284
+            struct buffer buf2 = alloc_buf(buf.capacity * 2);
285
+            ASSERT(buf_copy(&buf2, &buf));
286
+            buf_clear(&buf);
287
+            free_buf(&buf);
288
+            buf = buf2;
289
+        }
290
+        buf_printf(&buf, "%s", line);
291
+    }
292
+    if (!endtagfound)
293
+    {
294
+        msg(M_FATAL, "ERROR: Endtag %s missing", close_tag);
295
+    }
296
+    ret = string_alloc(BSTR(&buf), gc);
297
+    buf_clear(&buf);
298
+    free_buf(&buf);
299
+    secure_memzero(line, sizeof(line));
300
+    return ret;
301
+}
302
+
303
+static int
304
+check_inline_file(struct in_src *is, char *p[], struct gc_arena *gc)
305
+{
306
+    int num_inline_lines = 0;
307
+
308
+    if (p[0] && !p[1])
309
+    {
310
+        char *arg = p[0];
311
+        if (arg[0] == '<' && arg[strlen(arg) - 1] == '>')
312
+        {
313
+            struct buffer close_tag;
314
+
315
+            arg[strlen(arg) - 1] = '\0';
316
+            p[0] = string_alloc(arg + 1, gc);
317
+            close_tag = alloc_buf(strlen(p[0]) + 4);
318
+            buf_printf(&close_tag, "</%s>", p[0]);
319
+            p[1] = read_inline_file(is, BSTR(&close_tag), &num_inline_lines, gc);
320
+            p[2] = NULL;
321
+            free_buf(&close_tag);
322
+        }
323
+    }
324
+    return num_inline_lines;
325
+}
326
+
327
+static int
328
+check_inline_file_via_fp(FILE *fp, char *p[], struct gc_arena *gc)
329
+{
330
+    struct in_src is;
331
+    is.type = IS_TYPE_FP;
332
+    is.u.fp = fp;
333
+    return check_inline_file(&is, p, gc);
334
+}
335
+
336
+static int
337
+check_inline_file_via_buf(struct buffer *multiline, char *p[], struct gc_arena *gc)
338
+{
339
+    struct in_src is;
340
+    is.type = IS_TYPE_BUF;
341
+    is.u.multiline = multiline;
342
+    return check_inline_file(&is, p, gc);
343
+}
344
+
345
+void
346
+read_config_file(struct options *options, const char *file, int level, const char *top_file,
347
+                 const int top_line, const msglvl_t msglevel,
348
+                 const unsigned int permission_mask, unsigned int *option_types_found,
349
+                 struct env_set *es)
350
+{
351
+    const int max_recursive_levels = 10;
352
+    FILE *fp;
353
+    int line_num;
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
+            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
+
419
+void
420
+read_config_string(const char *prefix, struct options *options, const char *config,
421
+                   const msglvl_t msglevel, const unsigned int permission_mask,
422
+                   unsigned int *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
+
448
+void
449
+parse_argv(struct options *options, const int argc, char *argv[], const msglvl_t msglevel,
450
+           const unsigned int permission_mask, unsigned int *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
+
510
+bool
511
+apply_push_options(struct context *c, struct options *options, struct buffer *buf,
512
+                   unsigned int permission_mask, unsigned int *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
+    unsigned int update_options_found = 0;
520
+
521
+    while (buf_parse(buf, ',', line, sizeof(line)))
522
+    {
523
+        char *p[MAX_PARMS + 1];
524
+        CLEAR(p);
525
+        ++line_num;
526
+        unsigned int push_update_option_flags = 0;
527
+        int i = 0;
528
+
529
+        /* skip leading spaces matching the behaviour of parse_line */
530
+        while (isspace(line[i]))
531
+        {
532
+            i++;
533
+        }
534
+
535
+        /* If we are not in a 'PUSH_UPDATE' we just check `apply_pull_filter()`
536
+         * otherwise we must call `check_push_update_option_flags()` first
537
+         */
538
+        if ((is_update && !check_push_update_option_flags(line, &i, &push_update_option_flags))
539
+            || !apply_pull_filter(options, &line[i]))
540
+        {
541
+            /* In case we are in a `PUSH_UPDATE` and `check_push_update_option_flags()`
542
+             * or `apply_pull_filter()` fail but the option is flagged by `PUSH_OPT_OPTIONAL`,
543
+             * instead of restarting, we just ignore the option and we process the next one
544
+             */
545
+            if (push_update_option_flags & PUSH_OPT_OPTIONAL)
546
+            {
547
+                continue; /* Ignoring this option */
548
+            }
549
+            return false; /* Cause push/pull error and stop push processing */
550
+        }
551
+
552
+        if (parse_line(&line[i], p, SIZE(p) - 1, file, line_num, msglevel, &options->gc))
553
+        {
554
+            if (!is_update)
555
+            {
556
+                add_option(options, p, false, file, line_num, 0, msglevel, permission_mask,
557
+                           option_types_found, es);
558
+            }
559
+            else if (push_update_option_flags & PUSH_OPT_TO_REMOVE)
560
+            {
561
+                remove_option(c, options, p, false, file, line_num, msglevel, permission_mask,
562
+                              option_types_found, es);
563
+            }
564
+            else
565
+            {
566
+                update_option(c, options, p, false, file, line_num, 0, msglevel, permission_mask,
567
+                              option_types_found, es, &update_options_found);
568
+            }
569
+        }
570
+    }
571
+    return true;
572
+}
573
+
574
+void
575
+options_server_import(struct options *o, const char *filename, msglvl_t msglevel,
576
+                      unsigned int permission_mask, unsigned int *option_types_found,
577
+                      struct env_set *es)
578
+{
579
+    msg(D_PUSH, "OPTIONS IMPORT: reading client specific options from: %s", filename);
580
+    read_config_file(o, filename, 0, filename, 0, msglevel, permission_mask, option_types_found,
581
+                     es);
582
+}
583
+
584
+void
585
+options_string_import(struct options *options, const char *config, const msglvl_t msglevel,
586
+                      const unsigned int permission_mask, unsigned int *option_types_found,
587
+                      struct env_set *es)
588
+{
589
+    read_config_string("[CONFIG-STRING]", options, config, msglevel, permission_mask,
590
+                       option_types_found, es);
591
+}