diff options
author | Daiki Ueno <ueno@gnu.org> | 2022-01-03 10:30:34 +0100 |
---|---|---|
committer | Daiki Ueno <ueno@gnu.org> | 2022-01-15 09:25:55 +0100 |
commit | b8c1a61860e58e40dd57ef182e1d41a832aa52fd (patch) | |
tree | 36c1759781e369712e12a84dfae237e7972938a1 /src | |
parent | 0da805e6d3b2b148f9689b3229ddbbf3f4cedb88 (diff) | |
download | gnutls-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.am | 34 | ||||
-rw-r--r-- | src/certtool-cfg.c | 204 | ||||
-rw-r--r-- | src/cfg.c | 464 | ||||
-rw-r--r-- | src/cfg.h | 33 |
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_ */ |