summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDaiki Ueno <ueno@gnu.org>2022-01-03 10:30:34 +0100
committerDaiki Ueno <ueno@gnu.org>2022-01-15 09:25:55 +0100
commitb8c1a61860e58e40dd57ef182e1d41a832aa52fd (patch)
tree36c1759781e369712e12a84dfae237e7972938a1 /src
parent0da805e6d3b2b148f9689b3229ddbbf3f4cedb88 (diff)
downloadgnutls-b8c1a61860e58e40dd57ef182e1d41a832aa52fd.tar.gz
src: replace autoopts/libopts with minimal config parser
This replaces configuration file parsing code previously provided by <autoopts/options.h>, with a minimal compatible implementation. Signed-off-by: Daiki Ueno <ueno@gnu.org>
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am34
-rw-r--r--src/certtool-cfg.c204
-rw-r--r--src/cfg.c464
-rw-r--r--src/cfg.h33
4 files changed, 614 insertions, 121 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index ff9c09dd04..d867b02013 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -163,9 +163,8 @@ certtool_LDADD = ../lib/libgnutls.la
certtool_LDADD += libcmd-certtool.la ../gl/libgnu.la gl/libgnu_gpl.la
noinst_LTLIBRARIES += libcmd-certtool.la
-libcmd_certtool_la_SOURCES = certtool-options.c certtool-options.h \
- certtool-cfg.h certtool-cfg.c
-libcmd_certtool_la_LIBADD = ../lib/libgnutls.la gl/libgnu_gpl.la ../gl/libgnu.la
+libcmd_certtool_la_SOURCES = certtool-options.c certtool-options.h
+libcmd_certtool_la_LIBADD = libcerttool-cfg.la ../lib/libgnutls.la gl/libgnu_gpl.la ../gl/libgnu.la
libcmd_certtool_la_LIBADD += $(COMMON_LIBS)
libcmd_certtool_la_LIBADD += $(LTLIBREADLINE) gl/libgnu_gpl.la
libcmd_certtool_la_LIBADD += $(INET_PTON_LIB) $(LIB_CLOCK_GETTIME)
@@ -179,9 +178,8 @@ danetool_LDADD += ../libdane/libgnutls-dane.la
endif
noinst_LTLIBRARIES += libcmd-danetool.la
-libcmd_danetool_la_SOURCES = danetool-options.c danetool-options.h \
- certtool-cfg.h certtool-cfg.c
-libcmd_danetool_la_LIBADD = ../lib/libgnutls.la gl/libgnu_gpl.la ../gl/libgnu.la
+libcmd_danetool_la_SOURCES = danetool-options.c danetool-options.h
+libcmd_danetool_la_LIBADD = libcerttool-cfg.la ../lib/libgnutls.la gl/libgnu_gpl.la ../gl/libgnu.la
libcmd_danetool_la_LIBADD += $(COMMON_LIBS)
libcmd_danetool_la_LIBADD += $(LTLIBREADLINE)
libcmd_danetool_la_LIBADD += $(INET_PTON_LIB) $(LIB_CLOCK_GETTIME)
@@ -198,9 +196,8 @@ p11tool_LDADD += libcmd-p11tool.la ../gl/libgnu.la gl/libgnu_gpl.la
p11tool_LDADD += $(COMMON_LIBS)
noinst_LTLIBRARIES += libcmd-p11tool.la
-libcmd_p11tool_la_SOURCES = p11tool-options.c p11tool-options.h \
- certtool-cfg.h certtool-cfg.c
-libcmd_p11tool_la_LIBADD = ../lib/libgnutls.la gl/libgnu_gpl.la ../gl/libgnu.la
+libcmd_p11tool_la_SOURCES = p11tool-options.c p11tool-options.h
+libcmd_p11tool_la_LIBADD = libcerttool-cfg.la ../lib/libgnutls.la gl/libgnu_gpl.la ../gl/libgnu.la
libcmd_p11tool_la_LIBADD += $(LTLIBREADLINE) $(INET_PTON_LIB) $(LIB_CLOCK_GETTIME)
endif # ENABLE_PKCS11
@@ -213,9 +210,8 @@ tpmtool_LDADD += libcmd-tpmtool.la ../gl/libgnu.la gl/libgnu_gpl.la
tpmtool_LDADD += $(COMMON_LIBS)
noinst_LTLIBRARIES += libcmd-tpmtool.la
-libcmd_tpmtool_la_SOURCES = tpmtool-options.c tpmtool-options.h \
- certtool-cfg.h certtool-cfg.c
-libcmd_tpmtool_la_LIBADD = ../lib/libgnutls.la gl/libgnu_gpl.la ../gl/libgnu.la
+libcmd_tpmtool_la_SOURCES = tpmtool-options.c tpmtool-options.h
+libcmd_tpmtool_la_LIBADD = libcerttool-cfg.la ../lib/libgnutls.la gl/libgnu_gpl.la ../gl/libgnu.la
libcmd_tpmtool_la_LIBADD += $(LTLIBREADLINE) $(INET_PTON_LIB) $(LIB_CLOCK_GETTIME)
endif # ENABLE_TROUSERS
@@ -226,11 +222,19 @@ systemkey_LDADD += libcmd-systemkey.la ../gl/libgnu.la gl/libgnu_gpl.la
systemkey_LDADD += $(COMMON_LIBS)
noinst_LTLIBRARIES += libcmd-systemkey.la
-libcmd_systemkey_la_SOURCES = systemkey-tool-options.c systemkey-tool-options.h \
- certtool-cfg.h certtool-cfg.c
-libcmd_systemkey_la_LIBADD = ../lib/libgnutls.la gl/libgnu_gpl.la ../gl/libgnu.la
+libcmd_systemkey_la_SOURCES = systemkey-tool-options.c systemkey-tool-options.h
+libcmd_systemkey_la_LIBADD = libcerttool-cfg.la ../lib/libgnutls.la gl/libgnu_gpl.la ../gl/libgnu.la
libcmd_systemkey_la_LIBADD += $(LTLIBREADLINE) $(INET_PTON_LIB) $(LIB_CLOCK_GETTIME)
+noinst_LTLIBRARIES += libcerttool-cfg.la
+libcerttool_cfg_la_SOURCES = certtool-cfg.h certtool-cfg.c cfg.c cfg.h
+libcerttool_cfg_la_LIBADD = ../gl/libgnu.la gl/libgnu_gpl.la
+
+noinst_PROGRAMS = dumpcfg
+dumpcfg_SOURCES = cfg.c cfg.h
+dumpcfg_CFLAGS = -DTEST=1
+dumpcfg_LDADD = ../gl/libgnu.la gl/libgnu_gpl.la
+
SUFFIXES = .stamp .json
OPTIONS_STAMP: $(srcdir)/gen-getopt.py
diff --git a/src/certtool-cfg.c b/src/certtool-cfg.c
index 8c658c8667..a8a135a4a4 100644
--- a/src/certtool-cfg.c
+++ b/src/certtool-cfg.c
@@ -34,7 +34,7 @@
#include <time.h>
#include <timespec.h>
#include <parse-datetime.h>
-#include <autoopts/options.h>
+#include "cfg.h"
#include <intprops.h>
#include <gnutls/crypto.h>
#include <libtasn1.h>
@@ -244,29 +244,29 @@ void cfg_init(void)
cfg.skip_certs = -1;
}
-#define READ_MULTI_LINE(name, s_name) \
- val = optionGetValue(pov, name); \
- if (val != NULL && val->valType == OPARG_TYPE_STRING) \
+#define READ_MULTI_LINE(k_name, s_name) \
+ val = cfg_next(pov, k_name); \
+ if (val != NULL) \
{ \
if (s_name == NULL) { \
i = 0; \
s_name = malloc(sizeof(char*)*MAX_ENTRIES); \
CHECK_MALLOC(s_name); \
do { \
- if (val && strcmp(val->pzName, name)!=0) \
+ if (val && strcmp(val->name, k_name)!=0) \
continue; \
- s_name[i] = strdup(val->v.strVal); \
+ s_name[i] = strdup(val->value); \
i++; \
if (i>=MAX_ENTRIES) \
break; \
- } while((val = optionNextValue(pov, val)) != NULL); \
+ } while((val = cfg_next(val + 1, val->name)) != NULL); \
s_name[i] = NULL; \
} \
}
-#define READ_MULTI_LINE_TOKENIZED(name, s_name) \
- val = optionGetValue(pov, name); \
- if (val != NULL && val->valType == OPARG_TYPE_STRING) \
+#define READ_MULTI_LINE_TOKENIZED(k_name, s_name) \
+ val = cfg_next(pov, k_name); \
+ if (val != NULL) \
{ \
char *str; \
char *p; \
@@ -275,12 +275,12 @@ void cfg_init(void)
s_name = malloc(sizeof(char*)*MAX_ENTRIES); \
CHECK_MALLOC(s_name); \
do { \
- if (val && strcmp(val->pzName, name)!=0) \
+ if (val && strcmp(val->name, k_name)!=0) \
continue; \
- str = strdup(val->v.strVal); \
+ str = strdup(val->value); \
CHECK_MALLOC(str); \
if ((p=strchr(str, ' ')) == NULL && (p=strchr(str, '\t')) == NULL) { \
- fprintf(stderr, "Error parsing %s\n", name); \
+ fprintf(stderr, "Error parsing %s\n", k_name); \
exit(1); \
} \
p[0] = 0; \
@@ -288,7 +288,7 @@ void cfg_init(void)
s_name[i] = strdup(str); \
while(*p==' ' || *p == '\t') p++; \
if (p[0] == 0) { \
- fprintf(stderr, "Error (2) parsing %s\n", name); \
+ fprintf(stderr, "Error (2) parsing %s\n", k_name); \
exit(1); \
} \
s_name[i+1] = strdup(p); \
@@ -296,13 +296,13 @@ void cfg_init(void)
free(str); \
if (i>=MAX_ENTRIES) \
break; \
- } while((val = optionNextValue(pov, val)) != NULL); \
+ } while((val = cfg_next(val + 1, val->name)) != NULL); \
s_name[i] = NULL; \
} \
}
#define READ_BOOLEAN(name, s_name) \
- val = optionGetValue(pov, name); \
+ val = cfg_next(pov, name); \
if (val != NULL) \
{ \
s_name = 1; \
@@ -310,13 +310,10 @@ void cfg_init(void)
/* READ_NUMERIC only returns a long */
#define READ_NUMERIC(name, s_name) \
- val = optionGetValue(pov, name); \
+ val = cfg_next(pov, name); \
if (val != NULL) \
{ \
- if (val->valType == OPARG_TYPE_NUMERIC) \
- s_name = val->v.longVal; \
- else if (val->valType == OPARG_TYPE_STRING) \
- s_name = strtol(val->v.strVal, NULL, 10); \
+ s_name = strtol(val->value, NULL, 10); \
}
#define HEX_DECODE(hex, output, output_size) \
@@ -345,17 +342,17 @@ void cfg_init(void)
}
-static int handle_option(const tOptionValue* val)
+static int handle_option(cfg_option_t val)
{
-unsigned j;
-unsigned len, cmp;
+ unsigned j;
+ unsigned len, cmp;
for (j=0;j<sizeof(available_options)/sizeof(available_options[0]);j++) {
len = strlen(available_options[j].name);
if (len > 2 && available_options[j].name[len-1] == '*')
- cmp = strncasecmp(val->pzName, available_options[j].name, len-1);
+ cmp = strncasecmp(val->name, available_options[j].name, len-1);
else
- cmp = strcasecmp(val->pzName, available_options[j].name);
+ cmp = strcasecmp(val->name, available_options[j].name);
if (cmp == 0) {
if (available_options[j].type != OPTION_MULTI_LINE &&
@@ -375,24 +372,21 @@ int template_parse(const char *template)
/* Parsing return code */
unsigned int i;
int ret;
- tOptionValue const *pov;
- const tOptionValue *val, *prev;
+ cfg_option_t pov;
+ cfg_option_t val;
char tmpstr[256];
- pov = configFileLoad(template);
+ pov = cfg_load(template);
if (pov == NULL) {
perror("configFileLoad");
fprintf(stderr, "Error loading template: %s\n", template);
exit(1);
}
- val = optionGetValue(pov, NULL);
- while (val != NULL) {
+ for (val = pov; val->name; val++) {
if (handle_option(val) == 0) {
- fprintf(stderr, "Warning: skipping unknown option '%s'\n", val->pzName);
+ fprintf(stderr, "Warning: skipping unknown option '%s'\n", val->name);
}
- prev = val;
- val = optionNextValue(pov, prev);
}
/* Option variables */
@@ -406,92 +400,90 @@ int template_parse(const char *template)
READ_MULTI_LINE("o", cfg.organization);
}
- val = optionGetValue(pov, "locality");
- if (val != NULL && val->valType == OPARG_TYPE_STRING)
- cfg.locality = strdup(val->v.strVal);
+ val = cfg_next(pov, "locality");
+ if (val != NULL)
+ cfg.locality = strdup(val->value);
- val = optionGetValue(pov, "state");
- if (val != NULL && val->valType == OPARG_TYPE_STRING)
- cfg.state = strdup(val->v.strVal);
+ val = cfg_next(pov, "state");
+ if (val != NULL)
+ cfg.state = strdup(val->value);
- val = optionGetValue(pov, "dn");
- if (val != NULL && val->valType == OPARG_TYPE_STRING)
- cfg.dn = strdup(val->v.strVal);
+ val = cfg_next(pov, "dn");
+ if (val != NULL)
+ cfg.dn = strdup(val->value);
- val = optionGetValue(pov, "cn");
- if (val != NULL && val->valType == OPARG_TYPE_STRING)
- cfg.cn = strdup(val->v.strVal);
+ val = cfg_next(pov, "cn");
+ if (val != NULL)
+ cfg.cn = strdup(val->value);
- val = optionGetValue(pov, "uid");
- if (val != NULL && val->valType == OPARG_TYPE_STRING)
- cfg.uid = strdup(val->v.strVal);
+ val = cfg_next(pov, "uid");
+ if (val != NULL)
+ cfg.uid = strdup(val->value);
- val = optionGetValue(pov, "issuer_unique_id");
- if (val != NULL && val->valType == OPARG_TYPE_STRING)
- HEX_DECODE(val->v.strVal, cfg.issuer_unique_id, cfg.issuer_unique_id_size);
+ val = cfg_next(pov, "issuer_unique_id");
+ if (val != NULL)
+ HEX_DECODE(val->value, cfg.issuer_unique_id, cfg.issuer_unique_id_size);
- val = optionGetValue(pov, "subject_unique_id");
- if (val != NULL && val->valType == OPARG_TYPE_STRING)
- HEX_DECODE(val->v.strVal, cfg.subject_unique_id, cfg.subject_unique_id_size);
+ val = cfg_next(pov, "subject_unique_id");
+ if (val != NULL)
+ HEX_DECODE(val->value, cfg.subject_unique_id, cfg.subject_unique_id_size);
- val = optionGetValue(pov, "challenge_password");
- if (val != NULL && val->valType == OPARG_TYPE_STRING)
- cfg.challenge_password = strdup(val->v.strVal);
+ val = cfg_next(pov, "challenge_password");
+ if (val != NULL)
+ cfg.challenge_password = strdup(val->value);
- val = optionGetValue(pov, "password");
- if (val != NULL && val->valType == OPARG_TYPE_STRING)
- cfg.password = strdup(val->v.strVal);
+ val = cfg_next(pov, "password");
+ if (val != NULL)
+ cfg.password = strdup(val->value);
- val = optionGetValue(pov, "pkcs9_email");
- if (val != NULL && val->valType == OPARG_TYPE_STRING)
- cfg.pkcs9_email = strdup(val->v.strVal);
+ val = cfg_next(pov, "pkcs9_email");
+ if (val != NULL)
+ cfg.pkcs9_email = strdup(val->value);
- val = optionGetValue(pov, "country");
- if (val != NULL && val->valType == OPARG_TYPE_STRING)
- cfg.country = strdup(val->v.strVal);
+ val = cfg_next(pov, "country");
+ if (val != NULL)
+ cfg.country = strdup(val->value);
- val = optionGetValue(pov, "expiration_date");
- if (val != NULL && val->valType == OPARG_TYPE_STRING)
- cfg.expiration_date = strdup(val->v.strVal);
+ val = cfg_next(pov, "expiration_date");
+ if (val != NULL)
+ cfg.expiration_date = strdup(val->value);
- val = optionGetValue(pov, "activation_date");
- if (val != NULL && val->valType == OPARG_TYPE_STRING)
- cfg.activation_date = strdup(val->v.strVal);
+ val = cfg_next(pov, "activation_date");
+ if (val != NULL)
+ cfg.activation_date = strdup(val->value);
- val = optionGetValue(pov, "crl_revocation_date");
- if (val != NULL && val->valType == OPARG_TYPE_STRING)
- cfg.revocation_date = strdup(val->v.strVal);
+ val = cfg_next(pov, "crl_revocation_date");
+ if (val != NULL)
+ cfg.revocation_date = strdup(val->value);
- val = optionGetValue(pov, "crl_this_update_date");
- if (val != NULL && val->valType == OPARG_TYPE_STRING)
- cfg.this_update_date = strdup(val->v.strVal);
+ val = cfg_next(pov, "crl_this_update_date");
+ if (val != NULL)
+ cfg.this_update_date = strdup(val->value);
- val = optionGetValue(pov, "crl_next_update_date");
- if (val != NULL && val->valType == OPARG_TYPE_STRING)
- cfg.next_update_date = strdup(val->v.strVal);
+ val = cfg_next(pov, "crl_next_update_date");
+ if (val != NULL)
+ cfg.next_update_date = strdup(val->value);
READ_NUMERIC("inhibit_anypolicy_skip_certs", cfg.skip_certs);
for (i = 0; i < MAX_POLICIES; i++) {
snprintf(tmpstr, sizeof(tmpstr), "policy%d", i + 1);
- val = optionGetValue(pov, tmpstr);
- if (val != NULL && val->valType == OPARG_TYPE_STRING)
- cfg.policy_oid[i] = strdup(val->v.strVal);
+ val = cfg_next(pov, tmpstr);
+ if (val != NULL)
+ cfg.policy_oid[i] = strdup(val->value);
if (cfg.policy_oid[i] != NULL) {
snprintf(tmpstr, sizeof(tmpstr), "policy%d_url",
i + 1);
- val = optionGetValue(pov, tmpstr);
- if (val != NULL
- && val->valType == OPARG_TYPE_STRING)
- cfg.policy_url[i] = strdup(val->v.strVal);
+ val = cfg_next(pov, tmpstr);
+ if (val != NULL)
+ cfg.policy_url[i] = strdup(val->value);
snprintf(tmpstr, sizeof(tmpstr), "policy%d_txt",
i + 1);
- val = optionGetValue(pov, tmpstr);
- if (val != NULL
- && val->valType == OPARG_TYPE_STRING) {
- cfg.policy_txt[i] = strdup(val->v.strVal);
+ val = cfg_next(pov, tmpstr);
+ if (val != NULL) {
+ cfg.policy_txt[i] = strdup(val->value);
}
}
}
@@ -523,27 +515,27 @@ int template_parse(const char *template)
READ_MULTI_LINE("crl_dist_points", cfg.crl_dist_points);
- val = optionGetValue(pov, "pkcs12_key_name");
- if (val != NULL && val->valType == OPARG_TYPE_STRING)
- cfg.pkcs12_key_name = strdup(val->v.strVal);
+ val = cfg_next(pov, "pkcs12_key_name");
+ if (val != NULL)
+ cfg.pkcs12_key_name = strdup(val->value);
- val = optionGetValue(pov, "serial");
- if (val != NULL && val->valType == OPARG_TYPE_STRING)
- SERIAL_DECODE(val->v.strVal, cfg.serial, cfg.serial_size);
+ val = cfg_next(pov, "serial");
+ if (val != NULL)
+ SERIAL_DECODE(val->value, cfg.serial, cfg.serial_size);
READ_NUMERIC("expiration_days", cfg.expiration_days);
READ_NUMERIC("crl_next_update", cfg.crl_next_update);
- val = optionGetValue(pov, "crl_number");
- if (val != NULL && val->valType == OPARG_TYPE_STRING)
- SERIAL_DECODE(val->v.strVal, cfg.crl_number, cfg.crl_number_size);
+ val = cfg_next(pov, "crl_number");
+ if (val != NULL)
+ SERIAL_DECODE(val->value, cfg.crl_number, cfg.crl_number_size);
READ_NUMERIC("path_len", cfg.path_len);
- val = optionGetValue(pov, "proxy_policy_language");
- if (val != NULL && val->valType == OPARG_TYPE_STRING)
- cfg.proxy_policy_language = strdup(val->v.strVal);
+ val = cfg_next(pov, "proxy_policy_language");
+ if (val != NULL)
+ cfg.proxy_policy_language = strdup(val->value);
READ_MULTI_LINE("ocsp_uri", cfg.ocsp_uris);
READ_MULTI_LINE("ca_issuers_uri", cfg.ca_issuers_uris);
@@ -570,7 +562,7 @@ int template_parse(const char *template)
READ_MULTI_LINE("tls_feature", cfg.tls_features);
- optionUnloadNested(pov);
+ cfg_free(pov);
return 0;
}
diff --git a/src/cfg.c b/src/cfg.c
new file mode 100644
index 0000000000..b8925f9c7c
--- /dev/null
+++ b/src/cfg.c
@@ -0,0 +1,464 @@
+/*
+ * Copyright (C) 2021 Daiki Ueno
+ *
+ * This file is part of GnuTLS.
+ *
+ * GnuTLS is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GnuTLS 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
+ * <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "cfg.h"
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "xsize.h"
+
+#define SIZEOF(x) (sizeof(x) / sizeof((x)[0]))
+
+struct options_st {
+ struct cfg_option_st *data;
+ size_t length;
+ size_t capacity;
+};
+
+struct parser_st
+{
+ FILE *fp;
+ char pushback[2];
+ size_t pushback_length;
+};
+
+static inline void
+clear_option(struct cfg_option_st *option)
+{
+ free(option->name);
+ free(option->value);
+ memset(option, 0, sizeof(*option));
+}
+
+void
+cfg_free(cfg_option_t options)
+{
+ for (size_t i = 0; options[i].name; i++) {
+ clear_option(&options[i]);
+ }
+ free(options);
+}
+
+#define HORIZONTAL_WHITESPACE "\t "
+#define WHITESPACE HORIZONTAL_WHITESPACE "\n\v\f\r\b"
+#define ALPHABETIC "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+#define DECIMAL "0123456789"
+#define NAME_FIRST_CHARS "_" ALPHABETIC
+#define VALUE_NAME_CHARS ":^-" NAME_FIRST_CHARS DECIMAL
+
+struct buffer_st {
+ char *data;
+ size_t length;
+ size_t capacity;
+};
+
+static int
+buffer_append(struct buffer_st *buffer, int c)
+{
+ size_t new_length = xsum(buffer->length, 1);
+ if (size_overflow_p(new_length)) {
+ return -EINVAL;
+ }
+ if (buffer->capacity < new_length) {
+ size_t new_capacity;
+ char *new_array;
+
+ new_capacity = xtimes(xsum(buffer->capacity, 1), 2);
+ if (size_overflow_p(new_capacity)) {
+ return -EINVAL;
+ }
+ new_array = realloc(buffer->data, new_capacity);
+ if (!new_array) {
+ return -errno;
+ }
+ buffer->capacity = new_capacity;
+ buffer->data = new_array;
+ }
+ assert(buffer->data);
+ buffer->data[buffer->length++] = c;
+ return 0;
+}
+
+static int
+parser_getc(struct parser_st *parser)
+{
+ if (parser->pushback_length > 0) {
+ return parser->pushback[--parser->pushback_length];
+ }
+ int c = getc(parser->fp);
+ return c;
+}
+
+static void
+parser_ungetc(struct parser_st *parser, int c)
+{
+ assert(parser->pushback_length < SIZEOF(parser->pushback));
+ parser->pushback[parser->pushback_length++] = c;
+}
+
+static void
+skip_comment(struct parser_st *parser)
+{
+ int c;
+
+ c = parser_getc(parser);
+ if (c == EOF) {
+ return;
+ }
+
+ if (c == '#') {
+ for (;;) {
+ c = parser_getc(parser);
+ if (c == EOF) {
+ return;
+ }
+ if (c == '\n') {
+ break;
+ }
+ }
+ }
+ parser_ungetc(parser, c);
+}
+
+static void
+skip_chars(struct parser_st *parser, const char *chars)
+{
+ int c;
+
+ for (;;) {
+ c = parser_getc(parser);
+ if (c == EOF) {
+ return;
+ }
+ if (!strchr(chars, c)) {
+ break;
+ }
+ }
+ parser_ungetc(parser, c);
+}
+
+static void
+skip_comments_and_whitespaces(struct parser_st *parser)
+{
+ int c;
+
+ for (;;) {
+ c = parser_getc(parser);
+ if (c == EOF) {
+ return;
+ }
+ parser_ungetc(parser, c);
+ if (c == '#') {
+ skip_comment(parser);
+ } else if (strchr(WHITESPACE, c)) {
+ skip_chars(parser, WHITESPACE);
+ } else {
+ break;
+ }
+ }
+}
+
+/* Read the name part of an option. Returns NULL if it fails. */
+static char *
+read_name(struct parser_st *parser)
+{
+ struct buffer_st buffer;
+ int c;
+
+ memset(&buffer, 0, sizeof(buffer));
+
+ skip_comments_and_whitespaces(parser);
+
+ c = parser_getc(parser);
+ if (c == EOF) {
+ return NULL;
+ }
+
+ if (!strchr(NAME_FIRST_CHARS, c)) {
+ parser_ungetc(parser, c);
+ return NULL;
+ }
+
+ buffer_append(&buffer, c);
+ for (;;) {
+ c = parser_getc(parser);
+ if (c == EOF) {
+ break;
+ }
+ if (!strchr(VALUE_NAME_CHARS, c)) {
+ parser_ungetc(parser, c);
+ break;
+ }
+ buffer_append(&buffer, c);
+ }
+ assert(buffer.data);
+ if (buffer.data[buffer.length - 1] == ':') {
+ buffer.data[buffer.length - 1] = '\0';
+ buffer.length--;
+ parser_ungetc(parser, ':');
+ }
+
+ /* NUL terminate */
+ buffer_append(&buffer, '\0');
+ return buffer.data;
+}
+
+static char *
+read_quoted_value(struct parser_st *parser)
+{
+ struct buffer_st buffer;
+ int c, quote_char;
+
+ memset(&buffer, 0, sizeof(buffer));
+
+ c = parser_getc(parser);
+ if (c == EOF) {
+ assert(false);
+ return NULL;
+ }
+
+ if (c == '"' || c == '\'') {
+ quote_char = c;
+ } else {
+ assert(false);
+ return NULL;
+ }
+
+ for (;;) {
+ c = parser_getc(parser);
+ if (c == EOF) {
+ break;
+ }
+ if (c == '\\') {
+ c = parser_getc(parser);
+ if (c == EOF) {
+ /* unmatched quote */
+ free(buffer.data);
+ return NULL;
+ }
+ if (c == '\n') {
+ buffer_append(&buffer, ' ');
+ } else if (c == quote_char) {
+ buffer_append(&buffer, c);
+ }
+ } else if (c == quote_char) {
+ break;
+ } else {
+ buffer_append(&buffer, c);
+ }
+ }
+
+ /* NUL terminate */
+ buffer_append(&buffer, '\0');
+ return buffer.data;
+}
+
+/* Read the value part of an option. Returns NULL if it fails. */
+static char *
+read_value(struct parser_st *parser)
+{
+ struct buffer_st buffer;
+ int c;
+
+ memset(&buffer, 0, sizeof(buffer));
+
+ skip_chars(parser, HORIZONTAL_WHITESPACE);
+
+ c = parser_getc(parser);
+ if (c == EOF) {
+ goto out;
+ }
+
+ /* skip delimiter if any, followed by horizontal whitespaces */
+ if (c == ':' || c == '=') {
+ c = parser_getc(parser);
+ if (c == EOF) {
+ goto out;
+ }
+ parser_ungetc(parser, c);
+ skip_chars(parser, HORIZONTAL_WHITESPACE);
+ c = parser_getc(parser);
+ if (c == EOF) {
+ goto out;
+ }
+ }
+
+ if (c == '\n') {
+ return strdup(""); /* empty value */
+ } else if (c == '"' || c == '\'') {
+ parser_ungetc(parser, c);
+ return read_quoted_value(parser);
+ }
+
+ buffer_append(&buffer, c);
+ for (;;) {
+ c = parser_getc(parser);
+ if (c == EOF) {
+ break;
+ }
+ if (c == '\\') {
+ c = parser_getc(parser);
+ if (c == EOF) {
+ break;
+ }
+ if (c == '\n') {
+ buffer_append(&buffer, c);
+ }
+ } else if (c == '\n') {
+ break;
+ } else {
+ buffer_append(&buffer, c);
+ }
+ }
+
+ out:
+ /* NUL terminate */
+ buffer_append(&buffer, '\0');
+ return buffer.data;
+}
+
+/* Append OPTION to OPTIONS. Take ownership of the fields of OPTION. */
+static int
+take_option(struct options_st *options, struct cfg_option_st *option)
+{
+ size_t new_length = xsum(options->length, 1);
+ if (size_overflow_p(new_length)) {
+ return -EINVAL;
+ }
+ if (options->capacity < new_length) {
+ size_t new_capacity;
+ struct cfg_option_st *new_array;
+
+ new_capacity = xtimes(xsum(options->capacity, 1), 2);
+ if (size_overflow_p(new_capacity)) {
+ return -EINVAL;
+ }
+ new_array = reallocarray(options->data, new_capacity,
+ sizeof(*option));
+ if (!new_array) {
+ return -errno;
+ }
+ options->capacity = new_capacity;
+ options->data = new_array;
+ }
+
+ assert(options->data);
+
+ options->data[options->length].name = option->name;
+ options->data[options->length].value = option->value;
+
+ options->length++;
+
+ option->name = NULL;
+ option->value = NULL;
+
+ return 0;
+}
+
+static void
+clear_options(struct options_st *options)
+{
+ for (size_t i = 0; options->length; i++) {
+ clear_option(&options->data[i]);
+ }
+}
+
+cfg_option_t
+cfg_load(const char *filename)
+{
+ struct parser_st parser;
+ struct options_st options;
+ struct cfg_option_st null_option = { NULL, NULL };
+
+ memset(&parser, 0, sizeof(parser));
+ memset(&options, 0, sizeof(options));
+
+ parser.fp = fopen(filename, "r");
+ if (!parser.fp) {
+ return NULL;
+ }
+
+ for (;;) {
+ struct cfg_option_st option;
+
+ option.name = read_name(&parser);
+ if (!option.name) {
+ break;
+ }
+
+ option.value = read_value(&parser);
+ if (!option.value) {
+ clear_option(&option);
+ goto error;
+ }
+
+ if (take_option(&options, &option) < 0) {
+ clear_option(&option);
+ goto error;
+ }
+ assert(!option.name && !option.value);
+ }
+
+ fclose(parser.fp);
+ /* NUL terminate */
+ take_option(&options, &null_option);
+ return options.data;
+
+error:
+ clear_options(&options);
+ fclose(parser.fp);
+ return NULL;
+}
+
+cfg_option_t
+cfg_next(const cfg_option_t options, const char *name)
+{
+ for (size_t i = 0; options[i].name; i++) {
+ if (strcmp(options[i].name, name) == 0) {
+ return &options[i];
+ }
+ }
+ return NULL;
+}
+
+#ifdef TEST
+int
+main(int argc, char **argv)
+{
+ cfg_option_t opts;
+
+ assert(argc == 2);
+
+ opts = cfg_load(argv[1]);
+ for (size_t i = 0; opts[i].name; i++) {
+ printf("%s: %s\n", opts[i].name, opts[i].value);
+ }
+ cfg_free(opts);
+
+ return 0;
+}
+#endif
diff --git a/src/cfg.h b/src/cfg.h
new file mode 100644
index 0000000000..20e8fc9faa
--- /dev/null
+++ b/src/cfg.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2021 Daiki Ueno
+ *
+ * This file is part of GnuTLS.
+ *
+ * GnuTLS is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GnuTLS 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
+ * <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef CFG_H_
+#define CFG_H_ 1
+
+typedef struct cfg_option_st {
+ char *name;
+ char *value;
+} *cfg_option_t;
+
+cfg_option_t cfg_load(const char *filename);
+void cfg_free(cfg_option_t options);
+cfg_option_t cfg_next(const cfg_option_t options, const char *name);
+
+#endif /* CFG_H_ */