From patchwork Tue Oct 7 18:51:04 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Gert Doering X-Patchwork-Id: 4473 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a05:7000:7d42:b0:72f:f16c:e055 with SMTP id fr2csp462992mab; Tue, 7 Oct 2025 11:51:30 -0700 (PDT) X-Forwarded-Encrypted: i=2; AJvYcCXLE/yQs1a+7oqrmSAgJEVvOC6rgLamLdp6m2sjftS4n3a76RivTZQQw1Mbz4kzblPxvwcb6jvrJAA=@openvpn.net X-Google-Smtp-Source: AGHT+IEzlN8joyG7gTex5q8OlfPT2roEpZfmc/r/RrOh3ig5Q94Sj2kRBVErVd+A5hx3+npdfI1H X-Received: by 2002:a05:6871:2b25:b0:31d:8964:b4aa with SMTP id 586e51a60fabf-3c0f51a5b4bmr400428fac.6.1759863089890; Tue, 07 Oct 2025 11:51:29 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1759863089; cv=none; d=google.com; s=arc-20240605; b=SHNFZbd1xNLobtBUKxEp56gOUTJFFxR9XbtZqmNLKLA2Aadom5oAjx1glaGhaWG+hu HxJx91J5Hd4+7N38yAlIgyE5EeaiaZscE/VQdEs7d2s55Y6r8NVn3SkOuW2K2OqGL8F6 j9RaErZEIQ8nsZe3A8xlKb7N7FwwvRoKxDzwvCL1NLdVYbVmd+HS5IWa///6MEGDuyBX 3TA/gA122msMuQp2Ap0jW55/nVgRg5cs3+bumbQlDVzJ0npQ4t+UPtHNJ0litFa/De5z MHhcgMYD3v6zpNUn2Rq5QLNqsgzBtCG1ObN9YD/8hwInky8fQa7CJY8NyQNoG7Ru1yxn A1vw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20240605; h=errors-to:content-transfer-encoding:list-subscribe:list-help :list-post:list-archive:list-unsubscribe:list-id:precedence:subject :mime-version:references:in-reply-to:message-id:date:to:from :dkim-signature:dkim-signature:dkim-signature; bh=bGH78fj8qBtmD2UDo+Zshfmr8XWMKZ5iHtLqH1efCBY=; fh=4NbAC/LsuMLI0S0hprUlLSLCiHwg6SCAifhH718Jh0Q=; b=QZ2B4tMv//CCWnGVLivf2l3okfbcdLXvlw7p7nueh1svtGqP8XUNzeAL9aDDE3r/oU w7+Wu+0jew+1NevG58+jvs4weDSNzIKBqXiTXktdwWgLQ/ZjKbtGANyBh4qobWJRVlzl TRKlHYxeJeSMONUAnvhbLHMX6ShKJ+v9Wviq56jXFgxthEq5ZVzps+XLMTBJFAx3X52O GAPHN0pvmR3KuZMzDK0ESLEikX2Rl9oG4+yzzJdxKUIf9MYPUxm2KXlLRkjnvP6hJe8V SFSGC2JClTdiBCOJj62/zChF5LfKPkX4mgyHxF75sdvHG4v3BmhYl7c0wb6sTFynrh9O a+RQ==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=J2J2sGjt; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=J++dJsJ8; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=AFH3OXSG; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=muc.de Received: from lists.sourceforge.net (lists.sourceforge.net. [216.105.38.7]) by mx.google.com with ESMTPS id 586e51a60fabf-3be8a252760si800760fac.106.2025.10.07.11.51.29 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Tue, 07 Oct 2025 11:51:29 -0700 (PDT) Received-SPF: pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) client-ip=216.105.38.7; Authentication-Results: mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=J2J2sGjt; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=J++dJsJ8; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=AFH3OXSG; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=muc.de DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.sourceforge.net; s=beta; h=Content-Transfer-Encoding:Content-Type: List-Subscribe:List-Help:List-Post:List-Archive:List-Unsubscribe:List-Id: Subject:MIME-Version:References:In-Reply-To:Message-ID:Date:To:From:Sender: Reply-To:Cc:Content-ID:Content-Description:Resent-Date:Resent-From: Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Owner; bh=bGH78fj8qBtmD2UDo+Zshfmr8XWMKZ5iHtLqH1efCBY=; b=J2J2sGjtXW4fiUysq5yifEzg1S jvWFzAoDSYtRtVs/6ZiuQDal/jsHFF9WAXRaf/BE0ILGV4jaXEiMPQPc8JsttHwSaHA9URVd0FIEd 7I3hmYjDL5pMfJ9SqUsWjXtJnbZ/tegcsU4d4Bund/fTik5TLXXQdqrlVfoaks0W0DPU=; Received: from [127.0.0.1] (helo=sfs-ml-4.v29.lw.sourceforge.com) by sfs-ml-4.v29.lw.sourceforge.com with esmtp (Exim 4.95) (envelope-from ) id 1v6CmV-0007sV-Ij; Tue, 07 Oct 2025 18:51:27 +0000 Received: from [172.30.29.66] (helo=mx.sourceforge.net) by sfs-ml-4.v29.lw.sourceforge.com with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.95) (envelope-from ) id 1v6CmU-0007sP-CW for openvpn-devel@lists.sourceforge.net; Tue, 07 Oct 2025 18:51:26 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Transfer-Encoding:MIME-Version:References: In-Reply-To:Message-ID:Date:Subject:To:From:Sender:Reply-To:Cc:Content-Type: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=q7eKao2y6bdoXJeNHqQsB1jruVWLlYLDuYePPLqy5FQ=; b=J++dJsJ8Zv0n2lefv9PaxMPCuh tTjeHSGOkb3PBOkeSa9+5zYXfUi2nTpva4wG1Sb1Y4DaXD3SLZ2hWm9AkP/9uCKFqZP2dpwmP11Hs h6d3/Uif7KV57Zl5WpMMo7BLFUpiYSGgQEPQK8XyqNWsFcSHPzzhNyBF/FwYo3Evn/rY=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To:Message-ID: Date:Subject:To:From:Sender:Reply-To:Cc:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=q7eKao2y6bdoXJeNHqQsB1jruVWLlYLDuYePPLqy5FQ=; b=AFH3OXSGwDSEKzL2r/CChwzpR9 ARaMK6mizJCqXm5JqVdBUrZfCWk+a6ofbjC8YNdNjfx/hDJXIZtOctenLjL95VExdOju8Oqs8g6Hp TC0d2Ad9WjMXYtsVhM/RKRljs4T1wJdQ8hFjR1ZMQ0wb66pSzGV1jtlmc3wYiik9tTd0=; Received: from [193.149.48.134] (helo=blue.greenie.muc.de) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.95) id 1v6CmR-0000Ru-0b for openvpn-devel@lists.sourceforge.net; Tue, 07 Oct 2025 18:51:26 +0000 Received: from blue.greenie.muc.de (localhost [127.0.0.1]) by blue.greenie.muc.de (8.18.1/8.18.1) with ESMTP id 597IpBru019287 for ; Tue, 7 Oct 2025 20:51:11 +0200 Received: (from gert@localhost) by blue.greenie.muc.de (8.18.1/8.18.1/Submit) id 597IpBEW019286 for openvpn-devel@lists.sourceforge.net; Tue, 7 Oct 2025 20:51:11 +0200 From: Gert Doering To: openvpn-devel@lists.sourceforge.net Date: Tue, 7 Oct 2025 20:51:04 +0200 Message-ID: <20251007185110.19267-1-gert@greenie.muc.de> X-Mailer: git-send-email 2.49.1 In-Reply-To: References: MIME-Version: 1.0 X-Spam-Score: 1.3 (+) X-Spam-Report: Spam detection software, running on the system "sfi-spamd-2.hosts.colo.sdot.me", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see the administrator of that system for details. Content preview: From: Frank Lichtenheld 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 Acked-by: Gert Doering Gerrit URL: https://gerrit.openvpn.net/c/open [...] Content analysis details: (1.3 points, 5.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- 1.3 RDNS_NONE Delivered to internal network by a host with no rDNS X-Headers-End: 1v6CmR-0000Ru-0b Subject: [Openvpn-devel] [PATCH v1] options: Factor out parsing code to separate options_parse.c X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox X-GMAIL-THRID: =?utf-8?q?1845350199204778987?= X-GMAIL-MSGID: =?utf-8?q?1845350199204778987?= From: Frank Lichtenheld 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 Acked-by: Gert Doering Gerrit URL: https://gerrit.openvpn.net/c/openvpn/+/1242 --- This change was reviewed on Gerrit and approved by at least one developer. I request to merge it to master. Gerrit URL: https://gerrit.openvpn.net/c/openvpn/+/1242 This mail reflects revision 1 of this Change. Acked-by according to Gerrit (reflected above): Gert Doering diff --git a/CMakeLists.txt b/CMakeLists.txt index 3f6196f..be66357 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -513,6 +513,7 @@ src/openvpn/options.h src/openvpn/options_util.c src/openvpn/options_util.h + src/openvpn/options_parse.c src/openvpn/otime.c src/openvpn/otime.h src/openvpn/ovpn_dco_win.h diff --git a/src/openvpn/Makefile.am b/src/openvpn/Makefile.am index 1247f11..e44fb2b 100644 --- a/src/openvpn/Makefile.am +++ b/src/openvpn/Makefile.am @@ -109,6 +109,7 @@ openvpn.c openvpn.h \ options.c options.h \ options_util.c options_util.h \ + options_parse.c \ otime.c otime.h \ packet_id.c packet_id.h \ perf.c perf.h \ diff --git a/src/openvpn/common.h b/src/openvpn/common.h index c0947da..7779614 100644 --- a/src/openvpn/common.h +++ b/src/openvpn/common.h @@ -23,6 +23,8 @@ #ifndef COMMON_H #define COMMON_H +#include + /* * Statistics counters and associated printf format. */ diff --git a/src/openvpn/options.c b/src/openvpn/options.c index f35738d..f2e6dec 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -4838,7 +4838,7 @@ /* * Print the help message. */ -static void +void usage(void) { FILE *fp = msg_fp(0); @@ -4967,581 +4967,6 @@ } #endif -static inline bool -space(char c) -{ - return c == '\0' || isspace(c); -} - -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) -{ - const int STATE_INITIAL = 0; - const int STATE_READING_QUOTED_PARM = 1; - const int STATE_READING_UNQUOTED_PARM = 2; - const int STATE_DONE = 3; - const int STATE_READING_SQUOTED_PARM = 4; - - const char *error_prefix = ""; - - int ret = 0; - const char *c = line; - int state = STATE_INITIAL; - bool backslash = false; - char in, out; - - char parm[OPTION_PARM_SIZE]; - unsigned int parm_len = 0; - - msglevel &= ~M_OPTERR; - - if (msglevel & M_MSG_VIRT_OUT) - { - error_prefix = "ERROR: "; - } - - do - { - in = *c; - out = 0; - - if (!backslash && in == '\\' && state != STATE_READING_SQUOTED_PARM) - { - backslash = true; - } - else - { - if (state == STATE_INITIAL) - { - if (!space(in)) - { - if (in == ';' || in == '#') /* comment */ - { - break; - } - if (!backslash && in == '\"') - { - state = STATE_READING_QUOTED_PARM; - } - else if (!backslash && in == '\'') - { - state = STATE_READING_SQUOTED_PARM; - } - else - { - out = in; - state = STATE_READING_UNQUOTED_PARM; - } - } - } - else if (state == STATE_READING_UNQUOTED_PARM) - { - if (!backslash && space(in)) - { - state = STATE_DONE; - } - else - { - out = in; - } - } - else if (state == STATE_READING_QUOTED_PARM) - { - if (!backslash && in == '\"') - { - state = STATE_DONE; - } - else - { - out = in; - } - } - else if (state == STATE_READING_SQUOTED_PARM) - { - if (in == '\'') - { - state = STATE_DONE; - } - else - { - out = in; - } - } - if (state == STATE_DONE) - { - /* ASSERT (parm_len > 0); */ - p[ret] = gc_malloc(parm_len + 1, true, gc); - memcpy(p[ret], parm, parm_len); - p[ret][parm_len] = '\0'; - state = STATE_INITIAL; - parm_len = 0; - ++ret; - } - - if (backslash && out) - { - if (!(out == '\\' || out == '\"' || space(out))) - { -#ifdef ENABLE_SMALL - msg(msglevel, "%sOptions warning: Bad backslash ('\\') usage in %s:%d", - error_prefix, file, line_num); -#else - msg(msglevel, - "%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 - "\\\\static.key\"", - error_prefix, file, line_num); -#endif - return 0; - } - } - backslash = false; - } - - /* store parameter character */ - if (out) - { - if (parm_len >= SIZE(parm)) - { - parm[SIZE(parm) - 1] = 0; - msg(msglevel, "%sOptions error: Parameter at %s:%d is too long (%d chars max): %s", - error_prefix, file, line_num, (int)SIZE(parm), parm); - return 0; - } - parm[parm_len++] = out; - } - - /* avoid overflow if too many parms in one config file line */ - if (ret >= n) - { - break; - } - - } while (*c++ != '\0'); - - if (state == STATE_READING_QUOTED_PARM) - { - msg(msglevel, "%sOptions error: No closing quotation (\") in %s:%d", error_prefix, file, - line_num); - return 0; - } - if (state == STATE_READING_SQUOTED_PARM) - { - msg(msglevel, "%sOptions error: No closing single quotation (\') in %s:%d", error_prefix, - file, line_num); - return 0; - } - if (state != STATE_INITIAL) - { - msg(msglevel, "%sOptions error: Residual parse state (%d) in %s:%d", error_prefix, state, - file, line_num); - return 0; - } -#if 0 - { - int i; - for (i = 0; i < ret; ++i) - { - msg(M_INFO|M_NOPREFIX, "%s:%d ARG[%d] '%s'", file, line_num, i, p[i]); - } - } -#endif - return ret; -} - -static void -bypass_doubledash(char **p) -{ - if (strlen(*p) >= 3 && !strncmp(*p, "--", 2)) - { - *p += 2; - } -} - -struct in_src -{ -#define IS_TYPE_FP 1 -#define IS_TYPE_BUF 2 - int type; - union - { - FILE *fp; - struct buffer *multiline; - } u; -}; - -static bool -in_src_get(const struct in_src *is, char *line, const int size) -{ - if (is->type == IS_TYPE_FP) - { - return BOOL_CAST(fgets(line, size, is->u.fp)); - } - else if (is->type == IS_TYPE_BUF) - { - bool status = buf_parse(is->u.multiline, '\n', line, size); - if ((int)strlen(line) + 1 < size) - { - strcat(line, "\n"); - } - return status; - } - else - { - ASSERT(0); - return false; - } -} - -static char * -read_inline_file(struct in_src *is, const char *close_tag, int *num_lines, struct gc_arena *gc) -{ - char line[OPTION_LINE_SIZE]; - struct buffer buf = alloc_buf(8 * OPTION_LINE_SIZE); - char *ret; - bool endtagfound = false; - - while (in_src_get(is, line, sizeof(line))) - { - (*num_lines)++; - char *line_ptr = line; - /* Remove leading spaces */ - while (isspace(*line_ptr)) - { - line_ptr++; - } - if (!strncmp(line_ptr, close_tag, strlen(close_tag))) - { - endtagfound = true; - break; - } - if (!buf_safe(&buf, strlen(line) + 1)) - { - /* Increase buffer size */ - struct buffer buf2 = alloc_buf(buf.capacity * 2); - ASSERT(buf_copy(&buf2, &buf)); - buf_clear(&buf); - free_buf(&buf); - buf = buf2; - } - buf_printf(&buf, "%s", line); - } - if (!endtagfound) - { - msg(M_FATAL, "ERROR: Endtag %s missing", close_tag); - } - ret = string_alloc(BSTR(&buf), gc); - buf_clear(&buf); - free_buf(&buf); - secure_memzero(line, sizeof(line)); - return ret; -} - -static int -check_inline_file(struct in_src *is, char *p[], struct gc_arena *gc) -{ - int num_inline_lines = 0; - - if (p[0] && !p[1]) - { - char *arg = p[0]; - if (arg[0] == '<' && arg[strlen(arg) - 1] == '>') - { - struct buffer close_tag; - - arg[strlen(arg) - 1] = '\0'; - p[0] = string_alloc(arg + 1, gc); - close_tag = alloc_buf(strlen(p[0]) + 4); - buf_printf(&close_tag, "", p[0]); - p[1] = read_inline_file(is, BSTR(&close_tag), &num_inline_lines, gc); - p[2] = NULL; - free_buf(&close_tag); - } - } - return num_inline_lines; -} - -static int -check_inline_file_via_fp(FILE *fp, char *p[], struct gc_arena *gc) -{ - struct in_src is; - is.type = IS_TYPE_FP; - is.u.fp = fp; - return check_inline_file(&is, p, gc); -} - -static int -check_inline_file_via_buf(struct buffer *multiline, char *p[], struct gc_arena *gc) -{ - struct in_src is; - is.type = IS_TYPE_BUF; - is.u.multiline = multiline; - return check_inline_file(&is, p, gc); -} - -static 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); - -static 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); - -static 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); - -static 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) -{ - const int max_recursive_levels = 10; - FILE *fp; - int line_num; - char line[OPTION_LINE_SIZE + 1]; - char *p[MAX_PARMS + 1]; - - ++level; - if (level <= max_recursive_levels) - { - if (streq(file, "stdin")) - { - fp = stdin; - } - else - { - fp = platform_fopen(file, "r"); - } - if (fp) - { - line_num = 0; - while (fgets(line, sizeof(line), fp)) - { - int offset = 0; - CLEAR(p); - ++line_num; - if (strlen(line) == OPTION_LINE_SIZE) - { - msg(msglevel, - "In %s:%d: Maximum option line length (%d) exceeded, line starts with %s", - file, line_num, OPTION_LINE_SIZE, line); - } - - /* Ignore UTF-8 BOM at start of stream */ - if (line_num == 1 && strncmp(line, "\xEF\xBB\xBF", 3) == 0) - { - offset = 3; - } - if (parse_line(line + offset, p, SIZE(p) - 1, file, line_num, msglevel, - &options->gc)) - { - bypass_doubledash(&p[0]); - int lines_inline = check_inline_file_via_fp(fp, p, &options->gc); - add_option(options, p, lines_inline, file, line_num, level, msglevel, - permission_mask, option_types_found, es); - line_num += lines_inline; - } - } - if (fp != stdin) - { - fclose(fp); - } - } - else - { - msg(msglevel, "In %s:%d: Error opening configuration file: %s", top_file, top_line, - file); - } - } - else - { - msg(msglevel, - "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.", - top_file, top_line, file); - } - secure_memzero(line, sizeof(line)); - CLEAR(p); -} - -static 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) -{ - char line[OPTION_LINE_SIZE]; - struct buffer multiline; - int line_num = 0; - - buf_set_read(&multiline, (uint8_t *)config, strlen(config)); - - while (buf_parse(&multiline, '\n', line, sizeof(line))) - { - char *p[MAX_PARMS + 1]; - CLEAR(p); - ++line_num; - if (parse_line(line, p, SIZE(p) - 1, prefix, line_num, msglevel, &options->gc)) - { - bypass_doubledash(&p[0]); - int lines_inline = check_inline_file_via_buf(&multiline, p, &options->gc); - add_option(options, p, lines_inline, prefix, line_num, 0, msglevel, permission_mask, - option_types_found, es); - line_num += lines_inline; - } - CLEAR(p); - } - secure_memzero(line, sizeof(line)); -} - -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) -{ - /* usage message */ - if (argc <= 1) - { - usage(); - } - - /* config filename specified only? */ - if (argc == 2 && strncmp(argv[1], "--", 2)) - { - char *p[MAX_PARMS + 1]; - CLEAR(p); - p[0] = "config"; - p[1] = argv[1]; - add_option(options, p, false, NULL, 0, 0, msglevel, permission_mask, option_types_found, - es); - } - else - { - /* parse command line */ - for (int i = 1; i < argc; ++i) - { - char *p[MAX_PARMS + 1]; - CLEAR(p); - p[0] = argv[i]; - if (strncmp(p[0], "--", 2)) - { - msg(msglevel, - "I'm trying to parse \"%s\" as an --option parameter but I don't see a leading '--'", - p[0]); - } - else - { - p[0] += 2; - } - - int j; - for (j = 1; j < MAX_PARMS; ++j) - { - if (i + j < argc) - { - char *arg = argv[i + j]; - if (strncmp(arg, "--", 2)) - { - p[j] = arg; - } - else - { - break; - } - } - } - add_option(options, p, false, NULL, 0, 0, msglevel, permission_mask, option_types_found, - es); - i += j - 1; - } - } -} - -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) -{ - char line[OPTION_PARM_SIZE]; - int line_num = 0; - const char *file = "[PUSH-OPTIONS]"; - const msglvl_t msglevel = D_PUSH_ERRORS | M_OPTERR; - unsigned int update_options_found = 0; - - while (buf_parse(buf, ',', line, sizeof(line))) - { - char *p[MAX_PARMS + 1]; - CLEAR(p); - ++line_num; - unsigned int push_update_option_flags = 0; - int i = 0; - - /* skip leading spaces matching the behaviour of parse_line */ - while (isspace(line[i])) - { - i++; - } - - /* If we are not in a 'PUSH_UPDATE' we just check `apply_pull_filter()` - * otherwise we must call `check_push_update_option_flags()` first - */ - if ((is_update && !check_push_update_option_flags(line, &i, &push_update_option_flags)) - || !apply_pull_filter(options, &line[i])) - { - /* In case we are in a `PUSH_UPDATE` and `check_push_update_option_flags()` - * or `apply_pull_filter()` fail but the option is flagged by `PUSH_OPT_OPTIONAL`, - * instead of restarting, we just ignore the option and we process the next one - */ - if (push_update_option_flags & PUSH_OPT_OPTIONAL) - { - continue; /* Ignoring this option */ - } - return false; /* Cause push/pull error and stop push processing */ - } - - if (parse_line(&line[i], p, SIZE(p) - 1, file, line_num, msglevel, &options->gc)) - { - if (!is_update) - { - add_option(options, p, false, file, line_num, 0, msglevel, permission_mask, - option_types_found, es); - } - else if (push_update_option_flags & PUSH_OPT_TO_REMOVE) - { - remove_option(c, options, p, false, file, line_num, msglevel, permission_mask, - option_types_found, es); - } - else - { - update_option(c, options, p, false, file, line_num, 0, msglevel, permission_mask, - option_types_found, es, &update_options_found); - } - } - } - return true; -} - -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) -{ - msg(D_PUSH, "OPTIONS IMPORT: reading client specific options from: %s", filename); - read_config_file(o, filename, 0, filename, 0, msglevel, permission_mask, option_types_found, - es); -} - -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) -{ - read_config_string("[CONFIG-STRING]", options, config, msglevel, permission_mask, - option_types_found, es); -} - #define VERIFY_PERMISSION(mask) \ { \ if (!verify_permission(p[0], file, line, (mask), permission_mask, option_types_found, \ @@ -5642,27 +5067,7 @@ option_ptr->flags = 0; \ } -/** - * @brief Resets options found in the PUSH_UPDATE message that are preceded by the `-` flag. - * This function is used in push-updates to reset specified options. - * The number of parameters `p` must always be 1. If the permission is verified, - * all related options are erased or reset to their default values. - * Upon successful permission verification (by VERIFY_PERMISSION()), - * `option_types_found` is filled with the flag corresponding to the option. - * - * @param c The context structure. - * @param options A pointer to the options structure. - * @param p An array of strings containing the options and their parameters. - * @param is_inline A boolean indicating if the option is inline. - * @param file The file where the function is called. - * @param line The line number where the function is called. - * @param msglevel The message level. - * @param permission_mask The permission mask used by VERIFY_PERMISSION(). - * @param option_types_found A pointer to the variable where the flags corresponding to the options - * found are stored. - * @param es The environment set structure. - */ -static void +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, @@ -5982,30 +5387,7 @@ return true; } -/** - * @brief Processes an option to update. It first checks whether it has already - * received an option of the same type within the same update message. - * If the option has already been received, it calls add_option(). - * Otherwise, it deletes all existing values related to that option before calling - * add_option(). - * - * @param c The context structure. - * @param options A pointer to the options structure. - * @param p An array of strings containing the options and their parameters. - * @param is_inline A boolean indicating if the option is inline. - * @param file The file where the function is called. - * @param line The line number where the function is called. - * @param level The level of the option. - * @param msglevel The message level for logging. - * @param permission_mask The permission mask used by VERIFY_PERMISSION(). - * @param option_types_found A pointer to the variable where the flags corresponding to the options - * found are stored. - * @param es The environment set structure. - * @param update_options_found A pointer to the variable where the flags corresponding to the update - * options found are stored, used to check if an option of the same type has already been processed - * by update_option() within the same push-update message. - */ -static void +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, @@ -6190,7 +5572,7 @@ return ret; } -static void +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) diff --git a/src/openvpn/options.h b/src/openvpn/options.h index b033068..f038975 100644 --- a/src/openvpn/options.h +++ b/src/openvpn/options.h @@ -822,14 +822,83 @@ struct pull_filter *tail; }; +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); + +/** + * @brief Resets options found in the PUSH_UPDATE message that are preceded by the `-` flag. + * This function is used in push-updates to reset specified options. + * The number of parameters `p` must always be 1. If the permission is verified, + * all related options are erased or reset to their default values. + * Upon successful permission verification (by VERIFY_PERMISSION()), + * `option_types_found` is filled with the flag corresponding to the option. + * + * @param c The context structure. + * @param options A pointer to the options structure. + * @param p An array of strings containing the options and their parameters. + * @param is_inline A boolean indicating if the option is inline. + * @param file The file where the function is called. + * @param line The line number where the function is called. + * @param msglevel The message level. + * @param permission_mask The permission mask used by VERIFY_PERMISSION(). + * @param option_types_found A pointer to the variable where the flags corresponding to the options + * found are stored. + * @param es The environment set structure. + */ +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); + +/** + * @brief Processes an option to update. It first checks whether it has already + * received an option of the same type within the same update message. + * If the option has already been received, it calls add_option(). + * Otherwise, it deletes all existing values related to that option before calling + * add_option(). + * + * @param c The context structure. + * @param options A pointer to the options structure. + * @param p An array of strings containing the options and their parameters. + * @param is_inline A boolean indicating if the option is inline. + * @param file The file where the function is called. + * @param line The line number where the function is called. + * @param level The level of the option. + * @param msglevel The message level for logging. + * @param permission_mask The permission mask used by VERIFY_PERMISSION(). + * @param option_types_found A pointer to the variable where the flags corresponding to the options + * found are stored. + * @param es The environment set structure. + * @param update_options_found A pointer to the variable where the flags corresponding to the update + * options found are stored, used to check if an option of the same type has already been processed + * by update_option() within the same push-update message. + */ +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); + 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); +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); + +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); + void notnull(const char *arg, const char *description); void usage_small(void); +void usage(void); + void show_library_versions(const unsigned int flags); #ifdef _WIN32 diff --git a/src/openvpn/options_parse.c b/src/openvpn/options_parse.c new file mode 100644 index 0000000..bb5b404 --- /dev/null +++ b/src/openvpn/options_parse.c @@ -0,0 +1,592 @@ +/* + * OpenVPN -- An application to securely tunnel IP networks + * over a single UDP port, with support for SSL/TLS-based + * session authentication and key exchange, + * packet encryption, packet authentication, and + * packet compression. + * + * Copyright (C) 2002-2025 OpenVPN Inc + * Copyright (C) 2008-2025 David Sommerseth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see . + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "options.h" +#include "options_util.h" +#include "push.h" + +static void +bypass_doubledash(char **p) +{ + if (strlen(*p) >= 3 && !strncmp(*p, "--", 2)) + { + *p += 2; + } +} + +static inline bool +space(char c) +{ + return c == '\0' || isspace(c); +} + +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) +{ + const int STATE_INITIAL = 0; + const int STATE_READING_QUOTED_PARM = 1; + const int STATE_READING_UNQUOTED_PARM = 2; + const int STATE_DONE = 3; + const int STATE_READING_SQUOTED_PARM = 4; + + const char *error_prefix = ""; + + int ret = 0; + const char *c = line; + int state = STATE_INITIAL; + bool backslash = false; + char in, out; + + char parm[OPTION_PARM_SIZE]; + unsigned int parm_len = 0; + + msglevel &= ~M_OPTERR; + + if (msglevel & M_MSG_VIRT_OUT) + { + error_prefix = "ERROR: "; + } + + do + { + in = *c; + out = 0; + + if (!backslash && in == '\\' && state != STATE_READING_SQUOTED_PARM) + { + backslash = true; + } + else + { + if (state == STATE_INITIAL) + { + if (!space(in)) + { + if (in == ';' || in == '#') /* comment */ + { + break; + } + if (!backslash && in == '\"') + { + state = STATE_READING_QUOTED_PARM; + } + else if (!backslash && in == '\'') + { + state = STATE_READING_SQUOTED_PARM; + } + else + { + out = in; + state = STATE_READING_UNQUOTED_PARM; + } + } + } + else if (state == STATE_READING_UNQUOTED_PARM) + { + if (!backslash && space(in)) + { + state = STATE_DONE; + } + else + { + out = in; + } + } + else if (state == STATE_READING_QUOTED_PARM) + { + if (!backslash && in == '\"') + { + state = STATE_DONE; + } + else + { + out = in; + } + } + else if (state == STATE_READING_SQUOTED_PARM) + { + if (in == '\'') + { + state = STATE_DONE; + } + else + { + out = in; + } + } + if (state == STATE_DONE) + { + /* ASSERT (parm_len > 0); */ + p[ret] = gc_malloc(parm_len + 1, true, gc); + memcpy(p[ret], parm, parm_len); + p[ret][parm_len] = '\0'; + state = STATE_INITIAL; + parm_len = 0; + ++ret; + } + + if (backslash && out) + { + if (!(out == '\\' || out == '\"' || space(out))) + { +#ifdef ENABLE_SMALL + msg(msglevel, "%sOptions warning: Bad backslash ('\\') usage in %s:%d", + error_prefix, file, line_num); +#else + msg(msglevel, + "%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 + "\\\\static.key\"", + error_prefix, file, line_num); +#endif + return 0; + } + } + backslash = false; + } + + /* store parameter character */ + if (out) + { + if (parm_len >= SIZE(parm)) + { + parm[SIZE(parm) - 1] = 0; + msg(msglevel, "%sOptions error: Parameter at %s:%d is too long (%d chars max): %s", + error_prefix, file, line_num, (int)SIZE(parm), parm); + return 0; + } + parm[parm_len++] = out; + } + + /* avoid overflow if too many parms in one config file line */ + if (ret >= n) + { + break; + } + + } while (*c++ != '\0'); + + if (state == STATE_READING_QUOTED_PARM) + { + msg(msglevel, "%sOptions error: No closing quotation (\") in %s:%d", error_prefix, file, + line_num); + return 0; + } + if (state == STATE_READING_SQUOTED_PARM) + { + msg(msglevel, "%sOptions error: No closing single quotation (\') in %s:%d", error_prefix, + file, line_num); + return 0; + } + if (state != STATE_INITIAL) + { + msg(msglevel, "%sOptions error: Residual parse state (%d) in %s:%d", error_prefix, state, + file, line_num); + return 0; + } +#if 0 + { + int i; + for (i = 0; i < ret; ++i) + { + msg(M_INFO|M_NOPREFIX, "%s:%d ARG[%d] '%s'", file, line_num, i, p[i]); + } + } +#endif + return ret; +} + +struct in_src +{ +#define IS_TYPE_FP 1 +#define IS_TYPE_BUF 2 + int type; + union + { + FILE *fp; + struct buffer *multiline; + } u; +}; + +static bool +in_src_get(const struct in_src *is, char *line, const int size) +{ + if (is->type == IS_TYPE_FP) + { + return BOOL_CAST(fgets(line, size, is->u.fp)); + } + else if (is->type == IS_TYPE_BUF) + { + bool status = buf_parse(is->u.multiline, '\n', line, size); + if ((int)strlen(line) + 1 < size) + { + strcat(line, "\n"); + } + return status; + } + else + { + ASSERT(0); + return false; + } +} + +static char * +read_inline_file(struct in_src *is, const char *close_tag, int *num_lines, struct gc_arena *gc) +{ + char line[OPTION_LINE_SIZE]; + struct buffer buf = alloc_buf(8 * OPTION_LINE_SIZE); + char *ret; + bool endtagfound = false; + + while (in_src_get(is, line, sizeof(line))) + { + (*num_lines)++; + char *line_ptr = line; + /* Remove leading spaces */ + while (isspace(*line_ptr)) + { + line_ptr++; + } + if (!strncmp(line_ptr, close_tag, strlen(close_tag))) + { + endtagfound = true; + break; + } + if (!buf_safe(&buf, strlen(line) + 1)) + { + /* Increase buffer size */ + struct buffer buf2 = alloc_buf(buf.capacity * 2); + ASSERT(buf_copy(&buf2, &buf)); + buf_clear(&buf); + free_buf(&buf); + buf = buf2; + } + buf_printf(&buf, "%s", line); + } + if (!endtagfound) + { + msg(M_FATAL, "ERROR: Endtag %s missing", close_tag); + } + ret = string_alloc(BSTR(&buf), gc); + buf_clear(&buf); + free_buf(&buf); + secure_memzero(line, sizeof(line)); + return ret; +} + +static int +check_inline_file(struct in_src *is, char *p[], struct gc_arena *gc) +{ + int num_inline_lines = 0; + + if (p[0] && !p[1]) + { + char *arg = p[0]; + if (arg[0] == '<' && arg[strlen(arg) - 1] == '>') + { + struct buffer close_tag; + + arg[strlen(arg) - 1] = '\0'; + p[0] = string_alloc(arg + 1, gc); + close_tag = alloc_buf(strlen(p[0]) + 4); + buf_printf(&close_tag, "", p[0]); + p[1] = read_inline_file(is, BSTR(&close_tag), &num_inline_lines, gc); + p[2] = NULL; + free_buf(&close_tag); + } + } + return num_inline_lines; +} + +static int +check_inline_file_via_fp(FILE *fp, char *p[], struct gc_arena *gc) +{ + struct in_src is; + is.type = IS_TYPE_FP; + is.u.fp = fp; + return check_inline_file(&is, p, gc); +} + +static int +check_inline_file_via_buf(struct buffer *multiline, char *p[], struct gc_arena *gc) +{ + struct in_src is; + is.type = IS_TYPE_BUF; + is.u.multiline = multiline; + return check_inline_file(&is, p, 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 unsigned int permission_mask, unsigned int *option_types_found, + struct env_set *es) +{ + const int max_recursive_levels = 10; + FILE *fp; + int line_num; + char line[OPTION_LINE_SIZE + 1]; + char *p[MAX_PARMS + 1]; + + ++level; + if (level <= max_recursive_levels) + { + if (streq(file, "stdin")) + { + fp = stdin; + } + else + { + fp = platform_fopen(file, "r"); + } + if (fp) + { + line_num = 0; + while (fgets(line, sizeof(line), fp)) + { + int offset = 0; + CLEAR(p); + ++line_num; + if (strlen(line) == OPTION_LINE_SIZE) + { + msg(msglevel, + "In %s:%d: Maximum option line length (%d) exceeded, line starts with %s", + file, line_num, OPTION_LINE_SIZE, line); + } + + /* Ignore UTF-8 BOM at start of stream */ + if (line_num == 1 && strncmp(line, "\xEF\xBB\xBF", 3) == 0) + { + offset = 3; + } + if (parse_line(line + offset, p, SIZE(p) - 1, file, line_num, msglevel, + &options->gc)) + { + bypass_doubledash(&p[0]); + int lines_inline = check_inline_file_via_fp(fp, p, &options->gc); + add_option(options, p, lines_inline, file, line_num, level, msglevel, + permission_mask, option_types_found, es); + line_num += lines_inline; + } + } + if (fp != stdin) + { + fclose(fp); + } + } + else + { + msg(msglevel, "In %s:%d: Error opening configuration file: %s", top_file, top_line, + file); + } + } + else + { + msg(msglevel, + "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.", + top_file, top_line, file); + } + secure_memzero(line, sizeof(line)); + CLEAR(p); +} + +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) +{ + char line[OPTION_LINE_SIZE]; + struct buffer multiline; + int line_num = 0; + + buf_set_read(&multiline, (uint8_t *)config, strlen(config)); + + while (buf_parse(&multiline, '\n', line, sizeof(line))) + { + char *p[MAX_PARMS + 1]; + CLEAR(p); + ++line_num; + if (parse_line(line, p, SIZE(p) - 1, prefix, line_num, msglevel, &options->gc)) + { + bypass_doubledash(&p[0]); + int lines_inline = check_inline_file_via_buf(&multiline, p, &options->gc); + add_option(options, p, lines_inline, prefix, line_num, 0, msglevel, permission_mask, + option_types_found, es); + line_num += lines_inline; + } + CLEAR(p); + } + secure_memzero(line, sizeof(line)); +} + +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) +{ + /* usage message */ + if (argc <= 1) + { + usage(); + } + + /* config filename specified only? */ + if (argc == 2 && strncmp(argv[1], "--", 2)) + { + char *p[MAX_PARMS + 1]; + CLEAR(p); + p[0] = "config"; + p[1] = argv[1]; + add_option(options, p, false, NULL, 0, 0, msglevel, permission_mask, option_types_found, + es); + } + else + { + /* parse command line */ + for (int i = 1; i < argc; ++i) + { + char *p[MAX_PARMS + 1]; + CLEAR(p); + p[0] = argv[i]; + if (strncmp(p[0], "--", 2)) + { + msg(msglevel, + "I'm trying to parse \"%s\" as an --option parameter but I don't see a leading '--'", + p[0]); + } + else + { + p[0] += 2; + } + + int j; + for (j = 1; j < MAX_PARMS; ++j) + { + if (i + j < argc) + { + char *arg = argv[i + j]; + if (strncmp(arg, "--", 2)) + { + p[j] = arg; + } + else + { + break; + } + } + } + add_option(options, p, false, NULL, 0, 0, msglevel, permission_mask, option_types_found, + es); + i += j - 1; + } + } +} + +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) +{ + char line[OPTION_PARM_SIZE]; + int line_num = 0; + const char *file = "[PUSH-OPTIONS]"; + const msglvl_t msglevel = D_PUSH_ERRORS | M_OPTERR; + unsigned int update_options_found = 0; + + while (buf_parse(buf, ',', line, sizeof(line))) + { + char *p[MAX_PARMS + 1]; + CLEAR(p); + ++line_num; + unsigned int push_update_option_flags = 0; + int i = 0; + + /* skip leading spaces matching the behaviour of parse_line */ + while (isspace(line[i])) + { + i++; + } + + /* If we are not in a 'PUSH_UPDATE' we just check `apply_pull_filter()` + * otherwise we must call `check_push_update_option_flags()` first + */ + if ((is_update && !check_push_update_option_flags(line, &i, &push_update_option_flags)) + || !apply_pull_filter(options, &line[i])) + { + /* In case we are in a `PUSH_UPDATE` and `check_push_update_option_flags()` + * or `apply_pull_filter()` fail but the option is flagged by `PUSH_OPT_OPTIONAL`, + * instead of restarting, we just ignore the option and we process the next one + */ + if (push_update_option_flags & PUSH_OPT_OPTIONAL) + { + continue; /* Ignoring this option */ + } + return false; /* Cause push/pull error and stop push processing */ + } + + if (parse_line(&line[i], p, SIZE(p) - 1, file, line_num, msglevel, &options->gc)) + { + if (!is_update) + { + add_option(options, p, false, file, line_num, 0, msglevel, permission_mask, + option_types_found, es); + } + else if (push_update_option_flags & PUSH_OPT_TO_REMOVE) + { + remove_option(c, options, p, false, file, line_num, msglevel, permission_mask, + option_types_found, es); + } + else + { + update_option(c, options, p, false, file, line_num, 0, msglevel, permission_mask, + option_types_found, es, &update_options_found); + } + } + } + return true; +} + +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) +{ + msg(D_PUSH, "OPTIONS IMPORT: reading client specific options from: %s", filename); + read_config_file(o, filename, 0, filename, 0, msglevel, permission_mask, option_types_found, + es); +} + +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) +{ + read_config_string("[CONFIG-STRING]", options, config, msglevel, permission_mask, + option_types_found, es); +}