From 58500c8e530cc9b0807afbe8b068fe7b00db0131 Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Wed, 23 Sep 2020 11:19:29 -0400 Subject: [PATCH] Initial config parser --- Makefile | 2 +- config.ini | 13 +++++ config.sh | 24 ++++++-- configure | 1 + include/config.h | 34 ++++++++++++ src/config.c | 141 +++++++++++++++++++++++++++++++++++++++++++++++ src/ini.c | 4 +- src/main.c | 39 ++++++++++++- 8 files changed, 248 insertions(+), 10 deletions(-) create mode 100644 config.ini create mode 100644 include/config.h create mode 100644 src/config.c diff --git a/Makefile b/Makefile index 89d8887..7d37afd 100644 --- a/Makefile +++ b/Makefile @@ -33,6 +33,6 @@ distclean: clean install: all mkdir -p $(BINDIR) - install -Dm755 gmnisrv $(BINDIR)/gmnisrv + install -Dm755 gmnisrv $(DESTDIR)$(BINDIR)/gmnisrv .PHONY: clean distclean docs install diff --git a/config.ini b/config.ini new file mode 100644 index 0000000..b5c6052 --- /dev/null +++ b/config.ini @@ -0,0 +1,13 @@ +# Space-separated list of hosts +listen=0.0.0.0:1965 [::]:1965 + +[:tls] +# Path to store certificates on disk +store=/var/lib/gemini/certs + +# Details for new certificates +organization=gmnisrv user +email=jsmith@example.org + +[localhost] +root=/var/www diff --git a/config.sh b/config.sh index 5f15de3..e1dfa97 100644 --- a/config.sh +++ b/config.sh @@ -13,6 +13,19 @@ do case "$arg" in --prefix=*) PREFIX=${arg#*=} + if [ "$PREFIX" = "/usr" ] + then + SYSCONFDIR=/etc + fi + ;; + --bindir=*) + BINDIR=${arg#*=} + ;; + --sysconfdir=*) + SYSCONFDIR=${arg#*=} + ;; + --mandir=*) + MANDIR=${arg#*=} ;; esac done @@ -123,15 +136,18 @@ run_configure() { LIBS=$LIBS PREFIX=${PREFIX:-/usr/local} OUTDIR=${outdir} - _INSTDIR=\$(DESTDIR)\$(PREFIX) - BINDIR?=${BINDIR:-\$(_INSTDIR)/bin} - LIBDIR?=${LIBDIR:-\$(_INSTDIR)/lib} - MANDIR?=${MANDIR:-\$(_INSTDIR)/share/man} + BINDIR?=${BINDIR:-\$(PREFIX)/bin} + SYSCONFDIR?=${SYSCONFDIR:-\$(PREFIX)/etc} + LIBDIR?=${LIBDIR:-\$(PREFIX)/lib} + MANDIR?=${MANDIR:-\$(PREFIX)/share/man} + VARLIBDIR?=${MANDIR:-\$(PREFIX)/var/lib} CACHE=\$(OUTDIR)/cache CFLAGS=${CFLAGS} CFLAGS+=-Iinclude -I\$(OUTDIR) CFLAGS+=-DPREFIX='"\$(PREFIX)"' CFLAGS+=-DLIBDIR='"\$(LIBDIR)"' + CFLAGS+=-DVARLIBDIR='"\$(VARLIBDIR)"' + CFLAGS+=-DSYSCONFDIR='"\$(SYSCONFDIR)"' all: ${all} EOF diff --git a/configure b/configure index 324a58d..30c4cbb 100755 --- a/configure +++ b/configure @@ -4,6 +4,7 @@ eval ". $srcdir/config.sh" gmnisrv() { genrules gmnisrv \ + src/config.c \ src/escape.c \ src/ini.c \ src/main.c \ diff --git a/include/config.h b/include/config.h new file mode 100644 index 0000000..0c3d085 --- /dev/null +++ b/include/config.h @@ -0,0 +1,34 @@ +#ifndef GMNISRV_CONFIG +#define GMNISRV_CONFIG +#include + +struct gmnisrv_tls { + char *store; + char *organization; + char *email; +}; + +struct gmnisrv_host { + char *hostname; + char *root; + struct gmnisrv_host *next; +}; + +struct gmnisrv_bind { + int family; + char addr[sizeof(struct in6_addr)]; + struct gmnisrv_bind *next; +}; + +struct gmnisrv_config { + struct gmnisrv_tls tls; + struct gmnisrv_host *hosts; + struct gmnisrv_bind *binds; +}; + +int load_config(struct gmnisrv_config *conf, const char *path); + +struct gmnisrv_host *gmnisrv_config_get_host( + struct gmnisrv_config *conf, const char *hostname); + +#endif diff --git a/src/config.c b/src/config.c new file mode 100644 index 0000000..847af6e --- /dev/null +++ b/src/config.c @@ -0,0 +1,141 @@ +#include +#include +#include +#include +#include "config.h" +#include "ini.h" + +struct gmnisrv_host * +gmnisrv_config_get_host(struct gmnisrv_config *conf, const char *hostname) +{ + struct gmnisrv_host *host = conf->hosts; + while (host) { + if (strcmp(host->hostname, hostname) == 0) { + return host; + } + host = host->next; + } + return NULL; +} + +static int +parse_listen(struct gmnisrv_config *conf, const char *value) +{ + // TODO + (void)conf; + (void)value; + return 1; +} + +static int +conf_ini_handler(void *user, const char *section, + const char *name, const char *value) +{ + struct gmnisrv_config *conf = (struct gmnisrv_config *)user; + struct { + char *section; + char *name; + char **value; + } strvars[] = { + { ":tls", "store", &conf->tls.store }, + { ":tls", "organization", &conf->tls.organization }, + { ":tls", "email", &conf->tls.email }, + }; + struct { + char *section; + char *name; + int (*fn)(struct gmnisrv_config *conf, const char *value); + } fnvars[] = { + { "", "listen", &parse_listen, } + }; + + for (size_t i = 0; i < sizeof(strvars) / sizeof(strvars[0]); ++i) { + if (strcmp(strvars[i].section, section) != 0) { + continue; + } + if (strcmp(strvars[i].name, name) != 0) { + continue; + } + *strvars[i].value = strdup(value); + return 1; + } + + for (size_t i = 0; i < sizeof(fnvars) / sizeof(fnvars[0]); ++i) { + if (strcmp(fnvars[i].section, section) != 0) { + continue; + } + if (strcmp(fnvars[i].name, name) != 0) { + continue; + } + return fnvars[i].fn(conf, value); + } + + if (section[0] == '\0' || section[0] == ':') { + fprintf(stderr, "Unknown config option [%s]%s\n", section, name); + return 0; + } + + struct gmnisrv_host *host = gmnisrv_config_get_host(conf, section); + if (!host) { + host = calloc(1, sizeof(struct gmnisrv_host)); + host->hostname = strdup(section); + host->next = conf->hosts; + conf->hosts = host; + } + + struct { + char *name; + char **value; + } host_strvars[] = { + { "root", &host->root }, + }; + + for (size_t i = 0; i < sizeof(host_strvars) / sizeof(host_strvars[0]); ++i) { + if (strcmp(host_strvars[i].name, name) != 0) { + continue; + } + *host_strvars[i].value = strdup(value); + return 1; + } + + fprintf(stderr, "Unknown config option [%s]%s\n", section, name); + return 0; +} + +static int +validate_config(struct gmnisrv_config *conf) +{ + if (!conf->binds) { + fprintf(stderr, "Error: config missing listen directive\n"); + return 1; + } + if (!conf->hosts) { + fprintf(stderr, "Error: config defines no hosts\n"); + return 1; + } + if (!conf->tls.store) { + fprintf(stderr, "Error: config defines no certificate store\n"); + return 1; + } + return 0; +} + +int +load_config(struct gmnisrv_config *conf, const char *path) +{ + FILE *f = fopen(path, "r"); + if (!f) { + fprintf(stderr, "Error opening %s: %s\n", + path, strerror(errno)); + return 1; + } + + int n = ini_parse_file(f, conf_ini_handler, conf); + fclose(f); + if (n != 0) { + fprintf(stderr, "at %s:%d\n", path, n); + return 1; + } + + return validate_config(conf); +} diff --git a/src/ini.c b/src/ini.c index 236684e..88ff0d1 100644 --- a/src/ini.c +++ b/src/ini.c @@ -20,8 +20,8 @@ https://github.com/benhoyt/inih #include #endif -#define MAX_SECTION 50 -#define MAX_NAME 50 +#define MAX_SECTION 512 +#define MAX_NAME 512 /* Strip whitespace chars off end of given string, in place. Return s. */ static char* rstrip(char* s) diff --git a/src/main.c b/src/main.c index b581f42..b865b01 100644 --- a/src/main.c +++ b/src/main.c @@ -1,10 +1,43 @@ +#include #include +#include "config.h" + +static void +usage(const char *argv_0) +{ + fprintf(stderr, "Usage: %s [-C path]\n", argv_0); +} int main(int argc, char **argv) { - printf("Hello world!\n"); - (void)argc; - (void)argv; + struct gmnisrv_config conf = {0}; + + char *confpath = SYSCONFDIR "/gmnisrv.ini"; + int c; + while ((c = getopt(argc, argv, "C:h")) != -1) { + switch (c) { + case 'C': + confpath = optarg; + break; + case 'h': + usage(argv[0]); + return 0; + default: + fprintf(stderr, "Unknown flag %c\n", c); + usage(argv[0]); + return 1; + } + } + if (optind < argc) { + usage(argv[0]); + return 1; + } + + int r = load_config(&conf, confpath); + if (r != 0) { + return r; + } + return 0; }