/*
 * Copyright (C) 2013-2023 Nikos Mavrogiannopoulos
 * Copyright (C) 2014, 2015 Red Hat, Inc.
 *
 * This program 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 2 of the License, or
 * (at your option) any later version.
 *
 * 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 <http://www.gnu.org/licenses/>.
 */

#include <config.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>
#include <grp.h>
#include <fcntl.h>
#include <limits.h>
#include <common.h>
#include <ip-util.h>
#include <ctype.h>
#include <auth/pam.h>
#include <acct/pam.h>
#include <auth/radius.h>
#include <acct/radius.h>
#include <auth/plain.h>
#include <auth/gssapi.h>
#include <auth/openidconnect.h>
#include <auth/common.h>
#include <sec-mod-sup-config.h>
#include <sec-mod-acct.h>
#include "inih/ini.h"

#include <sys/types.h>
#include <sys/socket.h>
#include <dirent.h>
#include <netdb.h>
#include <assert.h>

#include <vpn.h>
#include <main.h>
#include <tlslib.h>
#include <occtl/ctl.h>
#include <gnutls/crypto.h>
#include "common-config.h"

#include <getopt.h>
#include <snapshot.h>

#define OLD_DEFAULT_CFG_FILE "/etc/ocserv.conf"
#define DEFAULT_CFG_FILE "/etc/ocserv/ocserv.conf"

static void print_version(void);

static char pid_file[_POSIX_PATH_MAX] = "";
static char cfg_file[_POSIX_PATH_MAX] = DEFAULT_CFG_FILE;

static void archive_cfg(struct list_head *head);
static void clear_cfg(struct list_head *head);
static void check_cfg(vhost_cfg_st *vhost, vhost_cfg_st *defvhost,
		      unsigned int silent);

#define ERRSTR "error: "
#define WARNSTR "warning: "
#define NOTESTR "note: "

#define READ_MULTI_LINE(varname, num)                                       \
	{                                                                   \
		if (_add_multi_line_val(pool, &varname, &num, value) < 0) { \
			fprintf(stderr, ERRSTR "memory\n");                 \
			exit(EXIT_FAILURE);                                 \
		}                                                           \
	}

#define READ_MULTI_BRACKET_LINE(varname, varname2, num)                        \
	{                                                                      \
		if (varname == NULL || varname2 == NULL) {                     \
			num = 0;                                               \
			varname = talloc_size(pool,                            \
					      sizeof(char *) *                 \
						      DEFAULT_CONFIG_ENTRIES); \
			varname2 = talloc_size(                                \
				pool,                                          \
				sizeof(char *) * DEFAULT_CONFIG_ENTRIES);      \
			if (varname == NULL || varname2 == NULL) {             \
				fprintf(stderr, ERRSTR "memory\n");            \
				exit(EXIT_FAILURE);                            \
			}                                                      \
		}                                                              \
		if (num < DEFAULT_CONFIG_ENTRIES) {                            \
			char *xp;                                              \
			varname[num] = talloc_strdup(pool, value);             \
			xp = strchr(varname[num], '[');                        \
			if (xp != NULL)                                        \
				*xp = 0;                                       \
			varname2[num] = get_brackets_string1(pool, value);     \
			num++;                                                 \
			varname[num] = NULL;                                   \
			varname2[num] = NULL;                                  \
		}                                                              \
	}

#define PREAD_STRING(pool, varname)                         \
	{                                                   \
		unsigned int len = strlen(value);           \
		while (len > 0 && isspace(value[len - 1]))  \
			len--;                              \
		varname = talloc_strndup(pool, value, len); \
	}

#define READ_STRING(varname) PREAD_STRING(pool, varname)

#define READ_STATIC_STRING(varname)                       \
	{                                                 \
		strlcpy(varname, value, sizeof(varname)); \
	}

#define READ_TF(varname)                              \
	{                                             \
		if (strcasecmp(value, "true") == 0 || \
		    strcasecmp(value, "yes") == 0)    \
			varname = 1;                  \
		else                                  \
			varname = 0;                  \
	}

#define READ_NUMERIC(varname)                      \
	{                                          \
		varname = strtol(value, NULL, 10); \
	}

#define READ_PRIO_TOS(varname)                             \
	{                                                  \
		if (strncmp(value, "0x", 2) == 0) {        \
			varname = strtol(value, NULL, 16); \
			varname = TOS_PACK(varname);       \
		} else {                                   \
			varname = strtol(value, NULL, 10); \
			varname++;                         \
		}                                          \
	}

struct snapshot_t *config_snapshot;

char **pam_auth_group_list;
char **gssapi_auth_group_list;
char **plain_auth_group_list;
unsigned int pam_auth_group_list_size;
unsigned int gssapi_auth_group_list_size;
unsigned int plain_auth_group_list_size;

/* Parses the string ::1/prefix, to return prefix
 * and modify the string to contain the network only.
 */
unsigned int extract_prefix(char *network)
{
	char *p;
	unsigned int prefix;

	if (network == NULL)
		return 0;

	p = strchr(network, '/');

	if (p == NULL)
		return 0;

	prefix = atoi(p + 1);
	*p = 0;

	return prefix;
}

typedef struct auth_types_st {
	const char *name;
	unsigned int name_size;
	const struct auth_mod_st *mod;
	unsigned int type;
	void *(*get_brackets_string)(void *pool, struct perm_cfg_st *config,
				     const char *);
} auth_types_st;

#define NAME(x) (x), (sizeof(x) - 1)
static auth_types_st avail_auth_types[] = {

#ifdef HAVE_PAM
	{ NAME("pam"), &pam_auth_funcs, AUTH_TYPE_PAM,
	  pam_get_brackets_string },
#endif
#ifdef HAVE_GSSAPI
	{ NAME("gssapi"), &gssapi_auth_funcs, AUTH_TYPE_GSSAPI,
	  gssapi_get_brackets_string },
#endif
#ifdef HAVE_RADIUS
	{ NAME("radius"), &radius_auth_funcs, AUTH_TYPE_RADIUS,
	  radius_get_brackets_string },
#endif
	{ NAME("plain"), &plain_auth_funcs, AUTH_TYPE_PLAIN,
	  plain_get_brackets_string },
	{ NAME("certificate"), NULL, AUTH_TYPE_CERTIFICATE, NULL },
#ifdef SUPPORT_OIDC_AUTH
	{ NAME("oidc"), &oidc_auth_funcs, AUTH_TYPE_OIDC,
	  oidc_get_brackets_string },
#endif
};

static void check_for_duplicate_password_auth(struct perm_cfg_st *config,
					      const char *vhostname,
					      unsigned int type)
{
	unsigned int i;

	if (type & AUTH_TYPE_USERNAME_PASS) {
		for (i = 0; i < MAX_AUTH_METHODS; i++) {
			if (config->auth[i].enabled == 0)
				break;
			if (config->auth[i].type & AUTH_TYPE_USERNAME_PASS) {
				fprintf(stderr,
					ERRSTR
					"%s: you cannot mix multiple password authentication methods\n",
					vhostname);
				exit(EXIT_FAILURE);
			}
		}
	}
}

static void figure_auth_funcs(void *pool, const char *vhostname,
			      struct perm_cfg_st *config, char **auth,
			      unsigned int auth_size, unsigned int primary,
			      unsigned int is_worker)
{
	unsigned int j, i;
	unsigned int found;

	if (auth == NULL)
		return;

	if (vhostname[0] == 0)
		vhostname = "vhost:default";

	if (primary != 0) {
		/* Set the primary authentication methods */
		for (j = 0; j < auth_size; j++) {
			found = 0;
			for (i = 0; i < ARRAY_SIZE(avail_auth_types); i++) {
				if (strncasecmp(
					    auth[j], avail_auth_types[i].name,
					    avail_auth_types[i].name_size) ==
				    0) {
					if (avail_auth_types[i]
						    .get_brackets_string)
						config->auth[0].additional =
							avail_auth_types[i].get_brackets_string(
								pool, config,
								auth[j] +
									avail_auth_types[i]
										.name_size);

					if (config->auth[0].amod != NULL &&
					    avail_auth_types[i].mod != NULL) {
						fprintf(stderr,
							ERRSTR
							"%s: you cannot mix multiple authentication methods of %s type\n",
							vhostname, auth[j]);
						exit(EXIT_FAILURE);
					}

					if (config->auth[0].amod == NULL)
						config->auth[0].amod =
							avail_auth_types[i].mod;
					config->auth[0].type |=
						avail_auth_types[i].type;
					if (config->auth[0].name == NULL) {
						config->auth[0]
							.name = talloc_strdup(
							pool,
							avail_auth_types[i]
								.name);
					} else {
						char *tmp;

						tmp = talloc_asprintf(
							pool, "%s+%s",
							config->auth[0].name,
							avail_auth_types[i]
								.name);
						talloc_free(
							config->auth[0].name);
						config->auth[0].name = tmp;
					}
					config->auth[0].enabled = 1;
					config->auth_methods = 1;
					found = 1;
					break;
				}
			}

			if (found == 0) {
				fprintf(stderr,
					ERRSTR
					"%s: unknown or unsupported auth method: %s\n",
					vhostname, auth[j]);
				exit(EXIT_FAILURE);
			}
			talloc_free(auth[j]);
		}

		if (!is_worker)
			fprintf(stderr,
				NOTESTR
				"%s: setting '%s' as primary authentication method\n",
				vhostname, config->auth[0].name);
	} else {
		unsigned int x = config->auth_methods;
		/* Append authentication methods (alternative options) */
		for (j = 0; j < auth_size; j++) {
			found = 0;
			for (i = 0; i < ARRAY_SIZE(avail_auth_types); i++) {
				if (strncasecmp(
					    auth[j], avail_auth_types[i].name,
					    avail_auth_types[i].name_size) ==
				    0) {
					if (avail_auth_types[i]
						    .get_brackets_string)
						config->auth[x].additional =
							avail_auth_types[i].get_brackets_string(
								pool, config,
								auth[j] +
									avail_auth_types[i]
										.name_size);

					config->auth[x].name = talloc_strdup(
						pool, avail_auth_types[i].name);
					if (!is_worker)
						fprintf(stderr,
							NOTESTR
							"%s: enabling '%s' as authentication method\n",
							vhostname,
							avail_auth_types[i]
								.name);

					check_for_duplicate_password_auth(
						config, vhostname,
						avail_auth_types[i].type);
					config->auth[x].amod =
						avail_auth_types[i].mod;
					config->auth[x].type |=
						avail_auth_types[i].type;
					config->auth[x].enabled = 1;
					found = 1;
					x++;
					if (x >= MAX_AUTH_METHODS) {
						fprintf(stderr,
							ERRSTR
							"%s: you cannot enable more than %d authentication methods\n",
							vhostname, x);
						exit(EXIT_FAILURE);
					}
					break;
				}
			}

			if (found == 0) {
				fprintf(stderr,
					ERRSTR
					"%s: unknown or unsupported auth method: %s\n",
					vhostname, auth[j]);
				exit(EXIT_FAILURE);
			}
			talloc_free(auth[j]);
		}
		config->auth_methods = x;
	}
	talloc_free(auth);
}

typedef struct acct_types_st {
	const char *name;
	unsigned int name_size;
	const struct acct_mod_st *mod;
	void *(*get_brackets_string)(void *pool, struct perm_cfg_st *config,
				     const char *);
} acct_types_st;

static acct_types_st avail_acct_types[] = {

#ifdef HAVE_RADIUS
	{ NAME("radius"), &radius_acct_funcs, radius_get_brackets_string },
#endif
#ifdef HAVE_PAM
	{ NAME("pam"), &pam_acct_funcs, NULL },
#endif
};

static void figure_acct_funcs(void *pool, const char *vhostname,
			      struct perm_cfg_st *config, const char *acct,
			      unsigned int is_worker)
{
	unsigned int i;
	unsigned int found = 0;

	if (acct == NULL)
		return;

	/* Set the accounting method */
	for (i = 0; i < ARRAY_SIZE(avail_acct_types); i++) {
		if (strncasecmp(acct, avail_acct_types[i].name,
				avail_acct_types[i].name_size) == 0) {
			if (avail_acct_types[i].mod == NULL)
				continue;

			if (avail_acct_types[i].get_brackets_string)
				config->acct.additional =
					avail_acct_types[i].get_brackets_string(
						pool, config,
						acct + avail_acct_types[i]
								.name_size);

			if ((avail_acct_types[i].mod->auth_types &
			     config->auth[0].type) == 0) {
				fprintf(stderr,
					ERRSTR
					"%s: you cannot mix the '%s' accounting method with the '%s' authentication method\n",
					vhostname, acct, config->auth[0].name);
				exit(EXIT_FAILURE);
			}

			config->acct.amod = avail_acct_types[i].mod;
			config->acct.name = avail_acct_types[i].name;
			found = 1;
			break;
		}
	}

	if (found == 0) {
		fprintf(stderr,
			ERRSTR
			"%s: unknown or unsupported accounting method: %s\n",
			vhostname, acct);
		exit(EXIT_FAILURE);
	}

	if (!is_worker)
		fprintf(stderr, NOTESTR "%ssetting '%s' as accounting method\n",
			vhostname, config->acct.name);
}

#ifdef HAVE_GSSAPI
static void parse_kkdcp(struct cfg_st *config, char **urlfw,
			unsigned int urlfw_size)
{
	unsigned int i, j;
	char *path, *server, *port, *realm;
	struct addrinfo hints, *res;
	int ret;
	struct kkdcp_st *kkdcp;
	struct kkdcp_realm_st *kkdcp_realm;

	config->kkdcp = talloc_zero_size(config, urlfw_size * sizeof(kkdcp_st));
	if (config->kkdcp == NULL) {
		fprintf(stderr, ERRSTR "memory\n");
		exit(EXIT_FAILURE);
	}

	config->kkdcp_size = 0;

	for (i = 0; i < urlfw_size; i++) {
		memset(&hints, 0, sizeof(hints));

		parse_kkdcp_string(urlfw[i], &hints.ai_socktype, &port, &server,
				   &path, &realm);

		ret = getaddrinfo(server, port, &hints, &res);
		if (ret != 0) {
			fprintf(stderr, ERRSTR "getaddrinfo(%s) failed: %s\n",
				server, gai_strerror(ret));
			exit(EXIT_FAILURE);
		}

		kkdcp = NULL;
		/* check if the path is already added */
		for (j = 0; j < config->kkdcp_size; j++) {
			if (strcmp(path, config->kkdcp[j].url) == 0) {
				kkdcp = &config->kkdcp[j];
			}
		}

		if (kkdcp == NULL) {
			kkdcp = &config->kkdcp[i];
			kkdcp->url = talloc_strdup(config->kkdcp, path);
			config->kkdcp_size++;
		}

		if (kkdcp->realms_size >= MAX_KRB_REALMS) {
			fprintf(stderr,
				ERRSTR
				"reached maximum number (%d) of realms per URL\n",
				MAX_KRB_REALMS);
			exit(EXIT_FAILURE);
		}

		kkdcp_realm = &kkdcp->realms[kkdcp->realms_size];

		memcpy(&kkdcp_realm->addr, res->ai_addr, res->ai_addrlen);
		kkdcp_realm->addr_len = res->ai_addrlen;
		kkdcp_realm->ai_family = res->ai_family;
		kkdcp_realm->ai_socktype = res->ai_socktype;
		kkdcp_realm->ai_protocol = res->ai_protocol;

		kkdcp_realm->realm = talloc_strdup(config->kkdcp, realm);

		freeaddrinfo(res);
		kkdcp->realms_size++;
	}
}
#endif

struct iroute_ctx {
	struct cfg_st *config;
	const char *file;
};

char *sanitize_config_value(void *pool, const char *value)
{
	ssize_t len = strlen(value);
	unsigned int i = 0;

	while (isspace(value[len - 1]) || value[len - 1] == '"')
		len--;

	while (isspace(value[i]) || value[i] == '"') {
		i++;
		len--;
	}

	if (len < 0)
		return NULL;

	return talloc_strndup(pool, &value[i], len);
}

static int iroutes_handler(void *_ctx, const char *section, const char *name,
			   const char *_value)
{
	struct iroute_ctx *ctx = _ctx;
	int ret;
	char *value;

	if (section != NULL && section[0] != 0) {
		fprintf(stderr, WARNSTR "skipping unknown section '%s'\n",
			section);
		return 1;
	}

	if (strcmp(name, "iroute") != 0)
		return 1;

	value = sanitize_config_value(ctx->config, _value);
	if (value == NULL)
		return 1;

	ret = _add_multi_line_val(ctx->config, &ctx->config->known_iroutes,
				  &ctx->config->known_iroutes_size, value);
	if (ret < 0) {
		fprintf(stderr, ERRSTR "cannot load iroute from %s\n",
			ctx->file);
	}

	talloc_free(value);
	return 1;
}

static void append_iroutes_from_file(struct cfg_st *config, const char *file)
{
	struct iroute_ctx ctx;
	int ret;
	unsigned int j;

	ctx.file = file;
	ctx.config = config;

	ret = ini_parse(file, iroutes_handler, &ctx);
	if (ret != 0)
		return;

	for (j = 0; j < config->known_iroutes_size; j++) {
		if (ip_route_sanity_check(config->known_iroutes,
					  &config->known_iroutes[j]) != 0)
			exit(EXIT_FAILURE);
	}
}

static void load_iroutes(struct cfg_st *config)
{
	DIR *dir;
	struct dirent *r;
	int ret;
	char path[_POSIX_PATH_MAX];

	if (config->per_user_dir == NULL)
		return;

	dir = opendir(config->per_user_dir);
	if (dir != NULL) {
		for (r = readdir(dir); r != NULL; r = readdir(dir)) {
			if (r->d_type == DT_REG) {
				ret = snprintf(path, sizeof(path), "%s/%s",
					       config->per_user_dir, r->d_name);
				if (ret != (int)strlen(path)) {
					fprintf(stderr,
						NOTESTR
						"path name too long and truncated: %s\n",
						path);
				}
				append_iroutes_from_file(config, path);
			}
		}
		closedir(dir);
	}
}

static void apply_default_conf(vhost_cfg_st *vhost, unsigned int reload)
{
	/* set config (no-zero) default vals
	 */
	if (!reload) { /* perm config defaults */
		tls_vhost_init(vhost);
		vhost->perm_config.stats_reset_time =
			24 * 60 * 60 * 7; /* weekly */
		vhost->perm_config.log_level = DEFAULT_LOG_LEVEL;
	}

	vhost->perm_config.config->mobile_idle_timeout = (unsigned int)-1;
#ifdef ENABLE_COMPRESSION
	vhost->perm_config.config->no_compress_limit =
		DEFAULT_NO_COMPRESS_LIMIT;
#endif
	vhost->perm_config.config->rekey_time = 24 * 60 * 60;
	vhost->perm_config.config->cookie_timeout =
		DEFAULT_COOKIE_RECON_TIMEOUT;
	vhost->perm_config.config->auth_timeout = DEFAULT_AUTH_TIMEOUT_SECS;
	vhost->perm_config.config->ban_time = DEFAULT_BAN_TIME;
	vhost->perm_config.config->ban_reset_time = DEFAULT_BAN_RESET_TIME;
	vhost->perm_config.config->max_ban_score = DEFAULT_MAX_BAN_SCORE;
	vhost->perm_config.config->ban_points_wrong_password =
		DEFAULT_PASSWORD_POINTS;
	vhost->perm_config.config->ban_points_connect = DEFAULT_CONNECT_POINTS;
	vhost->perm_config.config->ban_points_kkdcp = DEFAULT_KKDCP_POINTS;
	vhost->perm_config.config->dpd = DEFAULT_DPD_TIME;
	vhost->perm_config.config->network.ipv6_subnet_prefix = 128;
	vhost->perm_config.config->dtls_legacy = 1;
	vhost->perm_config.config->dtls_psk = 1;
	vhost->perm_config.config->predictable_ips = 1;
	vhost->perm_config.config->use_utmp = 1;
	vhost->perm_config.config->keepalive = 3600;
	vhost->perm_config.config->dpd = 60;
}

static void cfg_new(struct vhost_cfg_st *vhost, unsigned int reload)
{
	vhost->perm_config.config = talloc_zero(vhost->pool, struct cfg_st);
	if (vhost->perm_config.config == NULL)
		exit(EXIT_FAILURE);

	vhost->perm_config.config->usage_count =
		talloc_zero(vhost->perm_config.config, int);
	if (vhost->perm_config.config->usage_count == NULL) {
		fprintf(stderr, ERRSTR "memory\n");
		exit(EXIT_FAILURE);
	}

	apply_default_conf(vhost, reload);
}

static vhost_cfg_st *vhost_add(void *pool, struct list_head *head,
			       const char *name, unsigned int reload)
{
	vhost_cfg_st *vhost;

	vhost = talloc_zero(pool, struct vhost_cfg_st);
	if (vhost == NULL)
		exit(EXIT_FAILURE);
	vhost->pool = vhost;

	cfg_new(vhost, reload);

	if (name) {
		vhost->name = talloc_strdup(vhost, name);
		if (vhost->name == NULL) {
			fprintf(stderr, ERRSTR "memory\n");
			exit(EXIT_FAILURE);
		}
	}

	vhost->perm_config.sup_config_type = SUP_CONFIG_FILE;
	list_head_init(&vhost->perm_config.attic);

	list_add(head, &vhost->list);

	return vhost;
}

struct ini_ctx_st {
	struct list_head *head;
	unsigned int reload;
	unsigned int is_worker;
	const char *file;
	void *pool;
};

#define WARN_ON_VHOST_ONLY(vname, oname)                                      \
	({                                                                    \
		int rval;                                                     \
		if (vname) {                                                  \
			fprintf(stderr,                                       \
				WARNSTR "%s is ignored on %s virtual host\n", \
				oname, vname);                                \
			rval = 1;                                             \
		} else {                                                      \
			rval = 0;                                             \
		}                                                             \
		rval;                                                         \
	})

#define WARN_ON_VHOST(vname, oname, member)                                   \
	({                                                                    \
		int rval;                                                     \
		if (vname) {                                                  \
			fprintf(stderr,                                       \
				WARNSTR "%s is ignored on %s virtual host\n", \
				oname, vname);                                \
			memcpy(&config->member,                               \
			       &defvhost->perm_config.config->member,         \
			       sizeof(config->member));                       \
			rval = 1;                                             \
		} else {                                                      \
			rval = 0;                                             \
		}                                                             \
		rval;                                                         \
	})

#define PWARN_ON_VHOST(vname, oname, member)                                  \
	({                                                                    \
		int rval;                                                     \
		if (vname) {                                                  \
			fprintf(stderr,                                       \
				WARNSTR "%s is ignored on %s virtual host\n", \
				oname, vname);                                \
			vhost->perm_config.member =                           \
				defvhost->perm_config.member;                 \
			rval = 1;                                             \
		} else {                                                      \
			rval = 0;                                             \
		}                                                             \
		rval;                                                         \
	})

#define PWARN_ON_VHOST_STRDUP(vname, oname, member)                           \
	({                                                                    \
		int rval;                                                     \
		if (vname) {                                                  \
			fprintf(stderr,                                       \
				WARNSTR "%s is ignored on %s virtual host\n", \
				oname, vname);                                \
			vhost->perm_config.member = talloc_strdup(            \
				pool, defvhost->perm_config.member);          \
			rval = 1;                                             \
		} else {                                                      \
			rval = 0;                                             \
		}                                                             \
		rval;                                                         \
	})

static char *idna_map(void *pool, const char *name, unsigned int size)
{
#if GNUTLS_VERSION_NUMBER > 0x030508
	int ret;
	gnutls_datum_t out;

	ret = gnutls_idna_map(name, size, &out, 0);
	if (ret < 0) {
		goto fallback;
	}

	return talloc_strdup(pool, (char *)out.data);

fallback:
#endif
	return talloc_strndup(pool, name, size);
}

static char *sanitize_name(void *pool, const char *p)
{
	size_t len;
	/* cleanup spaces before and after */
	while (isspace(*p))
		p++;

	len = strlen(p);
	if (len > 0) {
		while (isspace(p[len - 1]))
			len--;
	}

	return idna_map(pool, p, len);
}

static int cfg_ini_handler(void *_ctx, const char *section, const char *name,
			   const char *_value)
{
	struct ini_ctx_st *ctx = _ctx;
	vhost_cfg_st *vhost, *vtmp = NULL, *defvhost;
	unsigned int use_dbus;
	struct cfg_st *config;
	void *pool;
	unsigned int reload = ctx->reload;
	unsigned int is_worker = ctx->is_worker;
	int ret;
	unsigned int stage1_found = 1;
	unsigned int force_cert_auth;
	unsigned int prefix = 0;
	unsigned int prefix4 = 0;
	unsigned int found_vhost;
	char *value;

	defvhost = vhost = default_vhost(ctx->head);

	assert(defvhost != NULL);

	if (section != NULL && section[0] != 0) {
		char *vname;

		if (strncmp(section, "vhost:", 6) != 0) {
			if (reload == 0 && is_worker == 0)
				fprintf(stderr,
					WARNSTR
					"skipping unknown section '%s'\n",
					section);
			return 1;
		}

		vname = sanitize_name(ctx->pool, section + 6);
		if (vname == NULL || vname[0] == 0) {
			fprintf(stderr,
				ERRSTR "virtual host name is illegal '%s'\n",
				section + 6);
			return 0;
		}

		/* virtual host */
		found_vhost = 0;
		list_for_each(ctx->head, vtmp, list)
		{
			if (vtmp->name && strcmp(vtmp->name, vname) == 0) {
				vhost = vtmp;
				found_vhost = 1;
				break;
			}
		}

		if (strcasecmp(section + 6, vname) != 0) {
			if (reload == 0 && is_worker == 0)
				fprintf(stderr,
					NOTESTR
					"virtual host name '%s' was canonicalized to '%s'\n",
					section + 6, vname);
		}

		if (!found_vhost) {
			/* add */
			if (reload == 0 && is_worker == 0)
				fprintf(stderr,
					NOTESTR "adding virtual host: %s\n",
					vname);
			vhost = vhost_add(ctx->pool, ctx->head, vname, reload);
		}
		talloc_free(vname);
	}

	value = sanitize_config_value(vhost->pool, _value);
	if (value == NULL)
		return 1;

	/* read persistent configuration */
	if (vhost->auth_init == 0) {
		pool = vhost;

		if (strcmp(name, "auth") == 0) {
			READ_MULTI_LINE(vhost->auth, vhost->auth_size);
		} else if (strcmp(name, "enable-auth") == 0) {
			READ_MULTI_LINE(vhost->eauth, vhost->eauth_size);
		} else if (strcmp(name, "acct") == 0) {
			vhost->acct = talloc_strdup(pool, value);
		} else if (strcmp(name, "listen-host") == 0) {
			PREAD_STRING(pool, vhost->perm_config.listen_host);
		} else if (strcmp(name, "udp-listen-host") == 0) {
			PREAD_STRING(pool, vhost->perm_config.udp_listen_host);
		} else if (strcmp(name, "listen-clear-file") == 0) {
			fprintf(stderr, ERRSTR
				"the 'listen-clear-file' option was removed in ocserv 1.1.2\n");
			return 0;
		} else if (strcmp(name, "listen-netns") == 0) {
			vhost->perm_config.listen_netns_name =
				talloc_strdup(pool, value);
		} else if (strcmp(name, "tcp-port") == 0) {
			if (!PWARN_ON_VHOST(vhost->name, "tcp-port", port))
				READ_NUMERIC(vhost->perm_config.port);
		} else if (strcmp(name, "udp-port") == 0) {
			if (!PWARN_ON_VHOST(vhost->name, "udp-port", udp_port))
				READ_NUMERIC(vhost->perm_config.udp_port);
		} else if (strcmp(name, "run-as-user") == 0) {
			if (!PWARN_ON_VHOST(vhost->name, "run-as-user", uid)) {
				const struct passwd *pwd = getpwnam(value);

				if (pwd == NULL) {
					fprintf(stderr,
						ERRSTR "unknown user: %s\n",
						value);
					return 0;
				}
				vhost->perm_config.uid = pwd->pw_uid;
			}
		} else if (strcmp(name, "run-as-group") == 0) {
			if (!PWARN_ON_VHOST(vhost->name, "run-as-group", gid)) {
				const struct group *grp = getgrnam(value);

				if (grp == NULL) {
					fprintf(stderr,
						ERRSTR "unknown group: %s\n",
						value);
					return 0;
				}
				vhost->perm_config.gid = grp->gr_gid;
			}
		} else if (strcmp(name, "server-cert") == 0) {
			READ_MULTI_LINE(vhost->perm_config.cert,
					vhost->perm_config.cert_size);
		} else if (strcmp(name, "server-key") == 0) {
			READ_MULTI_LINE(vhost->perm_config.key,
					vhost->perm_config.key_size);
		} else if (strcmp(name, "debug-no-secmod-stats") == 0) {
			READ_TF(vhost->perm_config.debug_no_secmod_stats);
		} else if (strcmp(name, "dh-params") == 0) {
			READ_STRING(vhost->perm_config.dh_params_file);
		} else if (strcmp(name, "pin-file") == 0) {
			READ_STRING(vhost->perm_config.pin_file);
		} else if (strcmp(name, "srk-pin-file") == 0) {
			READ_STRING(vhost->perm_config.srk_pin_file);
		} else if (strcmp(name, "ca-cert") == 0) {
			READ_STRING(vhost->perm_config.ca);
#if !defined(OCSERV_WORKER_PROCESS)
		} else if (strcmp(name, "key-pin") == 0) {
			READ_STRING(vhost->perm_config.key_pin);
		} else if (strcmp(name, "srk-pin") == 0) {
			READ_STRING(vhost->perm_config.srk_pin);
#endif
		} else if (strcmp(name, "socket-file") == 0) {
			if (!PWARN_ON_VHOST_STRDUP(vhost->name, "socket-file",
						   socket_file_prefix))
				PREAD_STRING(
					pool,
					vhost->perm_config.socket_file_prefix);
		} else if (strcmp(name, "occtl-socket-file") == 0) {
			if (!PWARN_ON_VHOST_STRDUP(vhost->name,
						   "occtl-socket-file",
						   occtl_socket_file))
				PREAD_STRING(
					pool,
					vhost->perm_config.occtl_socket_file);
		} else if (strcmp(name, "chroot-dir") == 0) {
			if (!PWARN_ON_VHOST_STRDUP(vhost->name, "chroot-dir",
						   chroot_dir))
				PREAD_STRING(pool,
					     vhost->perm_config.chroot_dir);
		} else if (strcmp(name, "server-stats-reset-time") == 0) {
			/* cannot be modified as it would require sec-mod to
			 * re-read configuration too */
			if (!PWARN_ON_VHOST(vhost->name,
					    "server-stats-reset-time",
					    stats_reset_time))
				READ_NUMERIC(
					vhost->perm_config.stats_reset_time);
		} else if (strcmp(name, "pid-file") == 0) {
			if (pid_file[0] == 0) {
				READ_STATIC_STRING(pid_file);
			} else if (reload == 0 && !ctx->is_worker)
				fprintf(stderr, NOTESTR
					"skipping 'pid-file' config option\n");
		} else if (strcmp(name, "sec-mod-scale") == 0) {
			if (!PWARN_ON_VHOST(vhost->name, "sec-mod-scale",
					    sec_mod_scale))
				READ_NUMERIC(vhost->perm_config.sec_mod_scale);
		} else if (strcmp(name, "log-level") == 0) {
			READ_NUMERIC(vhost->perm_config.log_level);
			global_log_prio = vhost->perm_config.log_level;
		} else {
			stage1_found = 0;
		}
		if (stage1_found)
			goto exit;
	}

	/* read the rest of the (non-permanent) configuration */
	pool = vhost->perm_config.config;
	config = vhost->perm_config.config;

	/* When adding allocated data, remember to modify
	 * reload_cfg_file();
	 */
	if (strcmp(name, "listen-host-is-dyndns") == 0) {
		READ_TF(config->is_dyndns);
	} else if (strcmp(name, "listen-proxy-proto") == 0) {
		if (!WARN_ON_VHOST(vhost->name, "listen-proxy-proto",
				   listen_proxy_proto))
			READ_TF(config->listen_proxy_proto);
	} else if (strcmp(name, "append-routes") == 0) {
		READ_TF(config->append_routes);
#ifdef HAVE_GSSAPI
	} else if (strcmp(name, "kkdcp") == 0) {
		READ_MULTI_LINE(vhost->urlfw, vhost->urlfw_size);
#endif
	} else if (strcmp(name, "tunnel-all-dns") == 0) {
		READ_TF(config->tunnel_all_dns);
	} else if (strcmp(name, "keepalive") == 0) {
		READ_NUMERIC(config->keepalive);
	} else if (strcmp(name, "switch-to-tcp-timeout") == 0) {
		READ_NUMERIC(config->switch_to_tcp_timeout);
	} else if (strcmp(name, "dpd") == 0) {
		READ_NUMERIC(config->dpd);
	} else if (strcmp(name, "mobile-dpd") == 0) {
		READ_NUMERIC(config->mobile_dpd);
	} else if (strcmp(name, "rate-limit-ms") == 0) {
		if (!WARN_ON_VHOST(vhost->name, "rate-limit-ms", rate_limit_ms))
			READ_NUMERIC(config->rate_limit_ms);
	} else if (strcmp(name, "server-drain-ms") == 0) {
		if (!WARN_ON_VHOST(vhost->name, "server-drain-ms",
				   server_drain_ms))
			READ_NUMERIC(config->server_drain_ms);
	} else if (strcmp(name, "ocsp-response") == 0) {
		READ_STRING(config->ocsp_response);
#ifdef ANYCONNECT_CLIENT_COMPAT
	} else if (strcmp(name, "user-profile") == 0) {
		READ_STRING(config->xml_config_file);
#endif
	} else if (strcmp(name, "client-bypass-protocol") == 0) {
		READ_TF(config->client_bypass_protocol);
	} else if (strcmp(name, "default-domain") == 0) {
		READ_STRING(config->default_domain);
	} else if (strcmp(name, "crl") == 0) {
		READ_STRING(config->crl);
	} else if (strcmp(name, "cert-user-oid") == 0) {
		READ_STRING(config->cert_user_oid);
	} else if (strcmp(name, "cert-group-oid") == 0) {
		READ_STRING(config->cert_group_oid);
	} else if (strcmp(name, "connect-script") == 0) {
		if (!WARN_ON_VHOST(vhost->name, "connect-script",
				   connect_script))
			READ_STRING(config->connect_script);
	} else if (strcmp(name, "host-update-script") == 0) {
		if (!WARN_ON_VHOST(vhost->name, "host-update-script",
				   host_update_script))
			READ_STRING(config->host_update_script);
	} else if (strcmp(name, "disconnect-script") == 0) {
		if (!WARN_ON_VHOST(vhost->name, "disconnect-script",
				   disconnect_script))
			READ_STRING(config->disconnect_script);
	} else if (strcmp(name, "session-control") == 0) {
		fprintf(stderr,
			WARNSTR "the option 'session-control' is deprecated\n");
	} else if (strcmp(name, "banner") == 0) {
		READ_STRING(config->banner);
	} else if (strcmp(name, "pre-login-banner") == 0) {
		READ_STRING(config->pre_login_banner);
	} else if (strcmp(name, "dtls-legacy") == 0) {
		READ_TF(config->dtls_legacy);
	} else if (strcmp(name, "cisco-client-compat") == 0) {
		READ_TF(config->cisco_client_compat);
	} else if (strcmp(name, "always-require-cert") == 0) {
		READ_TF(force_cert_auth);
		if (force_cert_auth == 0) {
			fprintf(stderr, NOTESTR
				"'always-require-cert' was replaced by 'cisco-client-compat'\n");
			config->cisco_client_compat = 1;
		}
	} else if (strcmp(name, "cisco-svc-client-compat") == 0) {
		READ_TF(config->cisco_svc_client_compat);
	} else if (strcmp(name, "dtls-psk") == 0) {
		if (!WARN_ON_VHOST(vhost->name, "dtls-psk", dtls_psk))
			READ_TF(config->dtls_psk);
	} else if (strcmp(name, "match-tls-dtls-ciphers") == 0) {
		READ_TF(config->match_dtls_and_tls);
#ifdef ENABLE_COMPRESSION
	} else if (strcmp(name, "compression") == 0) {
		READ_TF(config->enable_compression);
	} else if (strcmp(name, "compression-algo-priority") == 0) {
		if (!WARN_ON_VHOST_ONLY(vhost->name,
					"compression-algo-priority")) {
#if defined(OCSERV_WORKER_PROCESS)
			if (switch_comp_priority(pool, value) == 0) {
				fprintf(stderr,
					WARNSTR
					"invalid compression modstring %s\n",
					value);
			}
#endif
		}
	} else if (strcmp(name, "no-compress-limit") == 0) {
		READ_NUMERIC(config->no_compress_limit);
#endif
	} else if (strcmp(name, "use-seccomp") == 0) {
		READ_TF(config->isolate);
		if (config->isolate)
			fprintf(stderr, NOTESTR
				"'use-seccomp' was replaced by 'isolate-workers'\n");
	} else if (strcmp(name, "isolate-workers") == 0) {
		if (!WARN_ON_VHOST(vhost->name, "isolate-workers", isolate))
			READ_TF(config->isolate);
	} else if (strcmp(name, "predictable-ips") == 0) {
		READ_TF(config->predictable_ips);
	} else if (strcmp(name, "use-utmp") == 0) {
		if (!WARN_ON_VHOST(vhost->name, "use-utmp", use_utmp))
			READ_TF(config->use_utmp);
	} else if (strcmp(name, "use-dbus") == 0) {
		READ_TF(use_dbus);
		if (use_dbus != 0) {
			fprintf(stderr, NOTESTR
				"'use-dbus' was replaced by 'use-occtl'\n");
			config->use_occtl = use_dbus;
		}
	} else if (strcmp(name, "use-occtl") == 0) {
		if (!WARN_ON_VHOST(vhost->name, "use-occtl", use_occtl))
			READ_TF(config->use_occtl);
	} else if (strcmp(name, "try-mtu-discovery") == 0) {
		if (!WARN_ON_VHOST(vhost->name, "try-mtu-discovery", try_mtu))
			READ_TF(config->try_mtu);
	} else if (strcmp(name, "ping-leases") == 0) {
		if (!WARN_ON_VHOST(vhost->name, "ping_leases", ping_leases))
			READ_TF(config->ping_leases);
	} else if (strcmp(name, "restrict-user-to-routes") == 0) {
		READ_TF(config->restrict_user_to_routes);
	} else if (strcmp(name, "restrict-user-to-ports") == 0) {
		ret = cfg_parse_ports(pool, &config->fw_ports,
				      &config->n_fw_ports, value);
		if (ret < 0) {
			fprintf(stderr,
				ERRSTR "cannot parse restrict-user-to-ports\n");
			return 0;
		}
	} else if (strcmp(name, "tls-priorities") == 0) {
		READ_STRING(config->priorities);
	} else if (strcmp(name, "mtu") == 0) {
		READ_NUMERIC(config->default_mtu);
	} else if (strcmp(name, "net-priority") == 0) {
		READ_PRIO_TOS(config->net_priority);
	} else if (strcmp(name, "output-buffer") == 0) {
		READ_NUMERIC(config->output_buffer);
	} else if (strcmp(name, "rx-data-per-sec") == 0) {
		READ_NUMERIC(config->rx_per_sec);
		config->rx_per_sec /= 1000; /* in kb */
	} else if (strcmp(name, "tx-data-per-sec") == 0) {
		READ_NUMERIC(config->tx_per_sec);
		config->tx_per_sec /= 1000; /* in kb */
	} else if (strcmp(name, "deny-roaming") == 0) {
		READ_TF(config->deny_roaming);
	} else if (strcmp(name, "stats-report-time") == 0) {
		READ_NUMERIC(config->stats_report_time);
	} else if (strcmp(name, "rekey-time") == 0) {
		READ_NUMERIC(config->rekey_time);
	} else if (strcmp(name, "rekey-method") == 0) {
		if (strcmp(value, "ssl") == 0)
			config->rekey_method = REKEY_METHOD_SSL;
		else if (strcmp(value, "new-tunnel") == 0)
			config->rekey_method = REKEY_METHOD_NEW_TUNNEL;
		else {
			fprintf(stderr, ERRSTR "unknown rekey method '%s'\n",
				value);
			return 0;
		}
	} else if (strcmp(name, "cookie-timeout") == 0) {
		READ_NUMERIC(config->cookie_timeout);
	} else if (strcmp(name, "persistent-cookies") == 0) {
		READ_TF(config->persistent_cookies);
	} else if (strcmp(name, "session-timeout") == 0) {
		READ_NUMERIC(config->session_timeout);
	} else if (strcmp(name, "auth-timeout") == 0) {
		if (!WARN_ON_VHOST(vhost->name, "auth-timeout", auth_timeout))
			READ_NUMERIC(config->auth_timeout);
	} else if (strcmp(name, "idle-timeout") == 0) {
		READ_NUMERIC(config->idle_timeout);
	} else if (strcmp(name, "mobile-idle-timeout") == 0) {
		READ_NUMERIC(config->mobile_idle_timeout);
	} else if (strcmp(name, "max-clients") == 0) {
		if (!WARN_ON_VHOST(vhost->name, "max-clients", max_clients))
			READ_NUMERIC(config->max_clients);
	} else if (strcmp(name, "min-reauth-time") == 0) {
		READ_NUMERIC(config->ban_time);
		fprintf(stderr, NOTESTR
			"'min-reauth-time' was replaced by 'ban-time'\n");
	} else if (strcmp(name, "ban-time") == 0) {
		if (!WARN_ON_VHOST(vhost->name, "ban-time", ban_time))
			READ_NUMERIC(config->ban_time);
	} else if (strcmp(name, "ban-reset-time") == 0) {
		if (!WARN_ON_VHOST(vhost->name, "ban-reset-time",
				   ban_reset_time))
			READ_NUMERIC(config->ban_reset_time);
	} else if (strcmp(name, "max-ban-score") == 0) {
		if (!WARN_ON_VHOST(vhost->name, "max-ban-score", max_ban_score))
			READ_NUMERIC(config->max_ban_score);
	} else if (strcmp(name, "ban-points-wrong-password") == 0) {
		if (!WARN_ON_VHOST(vhost->name, "ban-points-wrong-password",
				   ban_points_wrong_password))
			READ_NUMERIC(config->ban_points_wrong_password);
	} else if (strcmp(name, "ban-points-connection") == 0) {
		if (!WARN_ON_VHOST(vhost->name, "ban-points-connection",
				   ban_points_connect))
			READ_NUMERIC(config->ban_points_connect);
	} else if (strcmp(name, "ban-points-kkdcp") == 0) {
		if (!WARN_ON_VHOST(vhost->name, "ban-points-kkdcp",
				   ban_points_kkdcp))
			READ_NUMERIC(config->ban_points_kkdcp);
	} else if (strcmp(name, "max-same-clients") == 0) {
		READ_NUMERIC(config->max_same_clients);
	} else if (strcmp(name, "device") == 0) {
		if (!WARN_ON_VHOST(vhost->name, "device", network.name))
			READ_STATIC_STRING(config->network.name);
	} else if (strcmp(name, "cgroup") == 0) {
		READ_STRING(config->cgroup);
	} else if (strcmp(name, "proxy-url") == 0) {
		READ_STRING(config->proxy_url);
	} else if (strcmp(name, "ipv4-network") == 0) {
		READ_STRING(config->network.ipv4);
		prefix4 = extract_prefix(config->network.ipv4);
		if (prefix4 != 0) {
			config->network.ipv4_netmask =
				ipv4_prefix_to_strmask(config, prefix4);
		}
	} else if (strcmp(name, "ipv4-netmask") == 0) {
		READ_STRING(config->network.ipv4_netmask);
	} else if (strcmp(name, "ipv6-network") == 0) {
		READ_STRING(config->network.ipv6);
		prefix = extract_prefix(config->network.ipv6);
		if (prefix)
			config->network.ipv6_prefix = prefix;
	} else if (strcmp(name, "ipv6-prefix") == 0) {
		READ_NUMERIC(config->network.ipv6_prefix);

		if (valid_ipv6_prefix(config->network.ipv6_prefix) == 0) {
			fprintf(stderr, ERRSTR "invalid IPv6 prefix: %u\n",
				prefix);
			return 0;
		}
	} else if (strcmp(name, "ipv6-subnet-prefix") == 0) {
		/* read subnet prefix */
		READ_NUMERIC(prefix);
		if (prefix > 0) {
			config->network.ipv6_subnet_prefix = prefix;

			if (valid_ipv6_prefix(prefix) == 0) {
				fprintf(stderr,
					ERRSTR
					"invalid IPv6 subnet prefix: %u\n",
					prefix);
				return 0;
			}
		}
	} else if (strcmp(name, "custom-header") == 0) {
		READ_MULTI_LINE(config->custom_header,
				config->custom_header_size);
	} else if (strcmp(name, "split-dns") == 0) {
		READ_MULTI_LINE(config->split_dns, config->split_dns_size);
	} else if (strcmp(name, "included-http-headers") == 0) {
		// Don't use sanitized input since http header values can contain optional trailing blanks and double quotes
		if (_add_multi_line_val(pool, &(config->included_http_headers),
					&(config->included_http_headers_size),
					_value) < 0) {
			fprintf(stderr, ERRSTR "memory\n");
			exit(EXIT_FAILURE);
		}
	} else if (strcmp(name, "route") == 0) {
		READ_MULTI_LINE(config->network.routes,
				config->network.routes_size);
	} else if (strcmp(name, "no-route") == 0) {
		READ_MULTI_LINE(config->network.no_routes,
				config->network.no_routes_size);
	} else if (strcmp(name, "default-select-group") == 0) {
		READ_STRING(config->default_select_group);
	} else if (strcmp(name, "select-group-by-url") == 0) {
		READ_TF(config->select_group_by_url);
	} else if (strcmp(name, "auto-select-group") == 0) {
		READ_TF(config->auto_select_group);
	} else if (strcmp(name, "select-group") == 0) {
		READ_MULTI_BRACKET_LINE(config->group_list,
					config->friendly_group_list,
					config->group_list_size);
	} else if (strcmp(name, "dns") == 0) {
		READ_MULTI_LINE(config->network.dns, config->network.dns_size);
	} else if (strcmp(name, "ipv4-dns") == 0) {
		READ_MULTI_LINE(config->network.dns, config->network.dns_size);
	} else if (strcmp(name, "ipv6-dns") == 0) {
		READ_MULTI_LINE(config->network.dns, config->network.dns_size);
	} else if (strcmp(name, "nbns") == 0) {
		READ_MULTI_LINE(config->network.nbns,
				config->network.nbns_size);
	} else if (strcmp(name, "ipv4-nbns") == 0) {
		READ_MULTI_LINE(config->network.nbns,
				config->network.nbns_size);
	} else if (strcmp(name, "ipv6-nbns") == 0) {
		READ_MULTI_LINE(config->network.nbns,
				config->network.nbns_size);
	} else if (strcmp(name, "route-add-cmd") == 0) {
		if (!WARN_ON_VHOST(vhost->name, "route-add-cmd", route_add_cmd))
			READ_STRING(config->route_add_cmd);
	} else if (strcmp(name, "route-del-cmd") == 0) {
		if (!WARN_ON_VHOST(vhost->name, "route-del-cmd", route_del_cmd))
			READ_STRING(config->route_del_cmd);
	} else if (strcmp(name, "config-per-user") == 0) {
		READ_STRING(config->per_user_dir);
	} else if (strcmp(name, "config-per-group") == 0) {
		READ_STRING(config->per_group_dir);
	} else if (strcmp(name, "expose-iroutes") == 0) {
		READ_TF(vhost->expose_iroutes);
	} else if (strcmp(name, "default-user-config") == 0) {
		READ_STRING(config->default_user_conf);
	} else if (strcmp(name, "default-group-config") == 0) {
		READ_STRING(config->default_group_conf);
	} else if (strcmp(name, "camouflage") == 0) {
		READ_TF(config->camouflage);
	} else if (strcmp(name, "camouflage_secret") == 0) {
		READ_STRING(config->camouflage_secret);
	} else if (strcmp(name, "camouflage_realm") == 0) {
		READ_STRING(config->camouflage_realm);
	} else {
		if (reload == 0)
			fprintf(stderr,
				WARNSTR "skipping unknown option '%s'\n", name);
	}

exit:
	talloc_free(value);
	return 1;
}

enum {
	CFG_FLAG_RELOAD = (1 << 0),
	CFG_FLAG_SECMOD = (1 << 1),
	CFG_FLAG_WORKER = (1 << 2)
};

static void replace_file_with_snapshot(char **file_name)
{
	char *snapshot_file_name;

	if (*file_name == NULL) {
		return;
	}

	if (snapshot_lookup_filename(config_snapshot, *file_name,
				     &snapshot_file_name) < 0) {
		fprintf(stderr, ERRSTR "cannot find snapshot for file %s\n",
			*file_name);
		exit(EXIT_FAILURE);
	}

	talloc_free(*file_name);
	*file_name = snapshot_file_name;
}

#define CONFIG_ERROR(filename, err)                                            \
	{                                                                      \
		if (err > 0)                                                   \
			fprintf(stderr,                                        \
				ERRSTR "config file error in line %d\n", err); \
		else                                                           \
			fprintf(stderr, ERRSTR "cannot load config file %s\n", \
				filename);                                     \
	}

static void parse_cfg_file(void *pool, const char *file, struct list_head *head,
			   unsigned int flags)
{
	int ret, silent = 0;
	struct cfg_st *config;
	struct ini_ctx_st ctx;
	vhost_cfg_st *vhost = NULL;
	vhost_cfg_st *defvhost;

	memset(&ctx, 0, sizeof(ctx));
	ctx.file = file;
	ctx.reload = (flags & CFG_FLAG_RELOAD) ? 1 : 0;
	ctx.is_worker = (flags & CFG_FLAG_WORKER) ? 1 : 0;
	ctx.head = head;

#if defined(PROC_FS_SUPPORTED)
	// Worker always reads from snapshot
	if ((flags & CFG_FLAG_WORKER) == CFG_FLAG_WORKER) {
		char *snapshot_file = NULL;

		if ((snapshot_lookup_filename(config_snapshot, file,
					      &snapshot_file) < 0) &&
		    (snapshot_lookup_filename(config_snapshot,
					      OLD_DEFAULT_CFG_FILE,
					      &snapshot_file) < 0)) {
			fprintf(stderr,
				ERRSTR "snapshot_lookup failed for file %s\n",
				file);
			exit(EXIT_FAILURE);
		}

		ret = ini_parse(snapshot_file, cfg_ini_handler, &ctx);
		if (ret != 0) {
			CONFIG_ERROR(file, ret);
			exit(EXIT_FAILURE);
		}
		talloc_free(snapshot_file);

		// Walk the config, replacing filename with the snapshot equivalent
		list_for_each(head, vhost, list)
		{
			size_t index;

			replace_file_with_snapshot(
				&vhost->perm_config.dh_params_file);
			replace_file_with_snapshot(
				&vhost->perm_config.config->ocsp_response);
			for (index = 0; index < vhost->perm_config.cert_size;
			     index++) {
				replace_file_with_snapshot(
					&vhost->perm_config.cert[index]);
			}
		}
	} else {
		const char *local_cfg_file = file;

		if (local_cfg_file == NULL) {
			fprintf(stderr, ERRSTR "no config file!\n");
			exit(EXIT_FAILURE);
		}

		/* parse configuration
		*/
		ret = ini_parse(local_cfg_file, cfg_ini_handler, &ctx);
		if (ret < 0 && strcmp(file, DEFAULT_CFG_FILE) == 0) {
			local_cfg_file = OLD_DEFAULT_CFG_FILE;
			ret = ini_parse(local_cfg_file, cfg_ini_handler, &ctx);
		}

		if (ret != 0) {
			CONFIG_ERROR(local_cfg_file, ret);
			exit(EXIT_FAILURE);
		}

		ret = snapshot_create(config_snapshot, local_cfg_file);
		if (ret < 0) {
			fprintf(stderr,
				ERRSTR "cannot snapshot config file %s\n",
				local_cfg_file);
			exit(EXIT_FAILURE);
		}
		list_for_each(head, vhost, list)
		{
			size_t index;

			snapshot_create(config_snapshot,
					vhost->perm_config.dh_params_file);
			snapshot_create(
				config_snapshot,
				vhost->perm_config.config->ocsp_response);
			for (index = 0; index < vhost->perm_config.cert_size;
			     index++) {
				snapshot_create(config_snapshot,
						vhost->perm_config.cert[index]);
			}
		}
	}
#else
	const char *local_cfg_file = file;

	if (local_cfg_file == NULL) {
		fprintf(stderr, ERRSTR "no config file!\n");
		exit(EXIT_FAILURE);
	}

	/* parse configuration
	*/
	ret = ini_parse(local_cfg_file, cfg_ini_handler, &ctx);
	if (ret < 0 && file != NULL && strcmp(file, DEFAULT_CFG_FILE) == 0) {
		local_cfg_file = OLD_DEFAULT_CFG_FILE;
		ret = ini_parse(local_cfg_file, cfg_ini_handler, &ctx);
	}

	if (ret != 0) {
		CONFIG_ERROR(local_cfg_file, ret);
		exit(EXIT_FAILURE);
	}
#endif

	/* apply configuration not yet applied.
	 * We start from the last, which is the default server (firstly
	 * added).
	 */
	list_for_each_rev(head, vhost, list)
	{
		config = vhost->perm_config.config;

		if (vhost->auth_init == 0) {
			if (vhost->auth_size == 0) {
				fprintf(stderr,
					ERRSTR
					"%sthe 'auth' configuration option was not specified!\n",
					PREFIX_VHOST(vhost));
				exit(EXIT_FAILURE);
			}

			figure_auth_funcs(vhost, PREFIX_VHOST(vhost),
					  &vhost->perm_config, vhost->auth,
					  vhost->auth_size, 1, ctx.is_worker);
			figure_auth_funcs(vhost, PREFIX_VHOST(vhost),
					  &vhost->perm_config, vhost->eauth,
					  vhost->eauth_size, 0, ctx.is_worker);

			figure_acct_funcs(vhost, PREFIX_VHOST(vhost),
					  &vhost->perm_config, vhost->acct,
					  ctx.is_worker);

			vhost->auth_init = 1;
		}

		if (config->auto_select_group != 0 &&
		    vhost->perm_config.auth[0].amod != NULL &&
		    vhost->perm_config.auth[0].amod->group_list != NULL) {
			vhost->perm_config.auth[0].amod->group_list(
				config, vhost->perm_config.auth[0].additional,
				&config->group_list, &config->group_list_size);
			switch (vhost->perm_config.auth[0].amod->type) {
			case AUTH_TYPE_PAM | AUTH_TYPE_USERNAME_PASS:
				pam_auth_group_list = config->group_list;
				pam_auth_group_list_size =
					config->group_list_size;
				break;
			case AUTH_TYPE_GSSAPI:
				gssapi_auth_group_list = config->group_list;
				gssapi_auth_group_list_size =
					config->group_list_size;
				break;
			case AUTH_TYPE_PLAIN | AUTH_TYPE_USERNAME_PASS:
				plain_auth_group_list = config->group_list;
				plain_auth_group_list_size =
					config->group_list_size;
				break;
			}
		}

		if (vhost->expose_iroutes != 0) {
			load_iroutes(config);
		}

		if (vhost->name)
			defvhost = default_vhost(head);
		else
			defvhost = NULL;

		/* this check copies mandatory fields from default vhost if needed */
		if (ctx.reload || ctx.is_worker)
			silent = 1;
		check_cfg(vhost, defvhost, silent);

		/* the following are only useful in main process */
		if (!(flags & CFG_FLAG_SECMOD)) {
			tls_load_files(NULL, vhost, silent);
			tls_load_prio(NULL, vhost);
			tls_reload_crl(NULL, vhost, 1);
		}

#ifdef HAVE_GSSAPI
		if (vhost->urlfw_size > 0) {
			parse_kkdcp(config, vhost->urlfw, vhost->urlfw_size);
			talloc_free(vhost->urlfw);
			vhost->urlfw = NULL;
		}
#endif
		if (!ctx.is_worker)
			fprintf(stderr,
				NOTESTR
				"%ssetting '%s' as supplemental config option\n",
				PREFIX_VHOST(vhost),
				sup_config_name(
					vhost->perm_config.sup_config_type));
	}
}

/* sanity checks on config */
static void check_cfg(vhost_cfg_st *vhost, vhost_cfg_st *defvhost,
		      unsigned int silent)
{
	unsigned int j, i;
	struct cfg_st *config;

	assert(vhost->name == NULL || defvhost != NULL);

	config = vhost->perm_config.config;

	if (vhost->perm_config.auth[0].enabled == 0) {
		fprintf(stderr,
			ERRSTR "%sno authentication method was specified!\n",
			PREFIX_VHOST(vhost));
		exit(EXIT_FAILURE);
	}

	if (vhost->perm_config.socket_file_prefix == NULL) {
		if (vhost->name) {
			vhost->perm_config.socket_file_prefix = talloc_strdup(
				vhost,
				defvhost->perm_config.socket_file_prefix);
		} else {
			/* The 'socket-file' is not mandatory on main server */
			fprintf(stderr,
				ERRSTR
				"%sthe 'socket-file' configuration option must be specified!\n",
				PREFIX_VHOST(vhost));
			exit(EXIT_FAILURE);
		}
	}

	if (vhost->perm_config.port == 0) {
		if (defvhost) {
			vhost->perm_config.port = defvhost->perm_config.port;
		} else {
			fprintf(stderr,
				ERRSTR "%sthe tcp-port option is mandatory!\n",
				PREFIX_VHOST(vhost));
			exit(EXIT_FAILURE);
		}
	}

	if (vhost->perm_config.udp_port == 0 && defvhost)
		vhost->perm_config.udp_port = defvhost->perm_config.udp_port;

	if (vhost->perm_config.cert_size == 0 ||
	    vhost->perm_config.key_size == 0) {
		fprintf(stderr,
			ERRSTR
			"%sthe 'server-cert' and 'server-key' configuration options must be specified!\n",
			PREFIX_VHOST(vhost));
		exit(EXIT_FAILURE);
	}

	if (config->network.ipv4 == NULL && config->network.ipv6 == NULL) {
		fprintf(stderr,
			ERRSTR
			"%sno ipv4-network or ipv6-network options set.\n",
			PREFIX_VHOST(vhost));
		exit(EXIT_FAILURE);
	}

	if (config->network.ipv4 != NULL &&
	    config->network.ipv4_netmask == NULL) {
		fprintf(stderr, ERRSTR "%sno mask found for IPv4 network.\n",
			PREFIX_VHOST(vhost));
		exit(EXIT_FAILURE);
	}

	if (config->network.ipv6 != NULL && config->network.ipv6_prefix == 0) {
		fprintf(stderr, ERRSTR "%sno prefix found for IPv6 network.\n",
			PREFIX_VHOST(vhost));
		exit(EXIT_FAILURE);
	}

	if (config->banner && strlen(config->banner) > MAX_BANNER_SIZE) {
		fprintf(stderr, ERRSTR "%sbanner size is too long\n",
			PREFIX_VHOST(vhost));
		exit(EXIT_FAILURE);
	}

	if (vhost->perm_config.cert_size != vhost->perm_config.key_size) {
		fprintf(stderr,
			ERRSTR
			"%sthe specified number of keys doesn't match the certificates\n",
			PREFIX_VHOST(vhost));
		exit(EXIT_FAILURE);
	}

	if ((vhost->perm_config.auth[0].type & AUTH_TYPE_CERTIFICATE) &&
	    vhost->perm_config.auth_methods == 1) {
		if (config->cisco_client_compat == 0)
			config->cert_req = GNUTLS_CERT_REQUIRE;
		else
			config->cert_req = GNUTLS_CERT_REQUEST;
	} else {
		unsigned int i;

		for (i = 0; i < vhost->perm_config.auth_methods; i++) {
			if (vhost->perm_config.auth[i].type &
			    AUTH_TYPE_CERTIFICATE) {
				config->cert_req = GNUTLS_CERT_REQUEST;
				break;
			}
		}
	}

	if (config->cert_req != 0 && config->cert_user_oid == NULL) {
		fprintf(stderr,
			ERRSTR
			"%sa certificate is requested by the option 'cert-user-oid' is not set\n",
			PREFIX_VHOST(vhost));
		exit(EXIT_FAILURE);
	}

	if (config->cert_req != 0 && config->cert_user_oid != NULL) {
		if (!isdigit(config->cert_user_oid[0]) &&
		    strcmp(config->cert_user_oid, "SAN(rfc822name)") != 0) {
			fprintf(stderr,
				ERRSTR
				"%sthe option 'cert-user-oid' has a unsupported value\n",
				PREFIX_VHOST(vhost));
			exit(EXIT_FAILURE);
		}
	}

#ifdef ANYCONNECT_CLIENT_COMPAT
	if (vhost->perm_config.cert && vhost->perm_config.cert_hash == NULL) {
		vhost->perm_config.cert_hash = calc_sha1_hash(
			vhost->pool, vhost->perm_config.cert[0], 1);
	}

	if (config->xml_config_file) {
		config->xml_config_hash =
			calc_sha1_hash(vhost->pool, config->xml_config_file, 0);
		if (config->xml_config_hash == NULL &&
		    vhost->perm_config.chroot_dir != NULL) {
			char path[_POSIX_PATH_MAX];

			snprintf(path, sizeof(path), "%s/%s",
				 vhost->perm_config.chroot_dir,
				 config->xml_config_file);
			config->xml_config_hash =
				calc_sha1_hash(vhost->pool, path, 0);

			if (config->xml_config_hash == NULL) {
				fprintf(stderr,
					ERRSTR "%scannot open file '%s'\n",
					PREFIX_VHOST(vhost), path);
				exit(EXIT_FAILURE);
			}
		}
		if (config->xml_config_hash == NULL) {
			fprintf(stderr, ERRSTR "%scannot open file '%s'\n",
				PREFIX_VHOST(vhost), config->xml_config_file);
			exit(EXIT_FAILURE);
		}
	}
#endif

	if (config->cisco_svc_client_compat) {
		if (!config->dtls_legacy && !silent) {
			fprintf(stderr,
				NOTESTR
				"%sthe cisco-svc-client-compat option implies dtls-legacy = true; enabling\n",
				PREFIX_VHOST(vhost));
		}
		config->dtls_legacy = 1;

		/* The client will only connect to port 443 */
		if (vhost->perm_config.udp_port != 0 &&
		    vhost->perm_config.udp_port != 443) {
			fprintf(stderr,
				ERRSTR
				"%s cisco-svc-client-compat option requires udp-port = 443\n",
				PREFIX_VHOST(vhost));
			exit(EXIT_FAILURE);
		}
	}

	if (config->priorities == NULL) {
		char *tmp = "";
		/* on vhosts assign the main host priorities. We furthermore disable TLS1.3 on Cisco clients
		 * due to issue #318. */

		if (config->cisco_client_compat) {
			tmp = ":-VERS-TLS1.3";
		}

		if (defvhost) {
			config->priorities = talloc_asprintf(
				config, "%s%s",
				defvhost->perm_config.config->priorities, tmp);
		} else {
			config->priorities = talloc_asprintf(
				config, "%s%s",
				"NORMAL:%SERVER_PRECEDENCE:%COMPAT", tmp);
		}
	}

	if (vhost->perm_config.occtl_socket_file == NULL)
		vhost->perm_config.occtl_socket_file =
			talloc_strdup(vhost, OCCTL_UNIX_SOCKET);

	if (config->network.ipv6_prefix &&
	    config->network.ipv6_prefix >= config->network.ipv6_subnet_prefix) {
		fprintf(stderr,
			ERRSTR
			"%sthe subnet prefix (%u) cannot be smaller or equal to network's (%u)\n",
			PREFIX_VHOST(vhost), config->network.ipv6_subnet_prefix,
			config->network.ipv6_prefix);
		exit(EXIT_FAILURE);
	}

	if (config->network.name[0] == 0) {
		if (!vhost->name) {
			fprintf(stderr,
				ERRSTR
				"%sthe 'device' configuration option must be specified!\n",
				PREFIX_VHOST(vhost));
			exit(EXIT_FAILURE);
		} else {
			strlcpy(config->network.name,
				defvhost->perm_config.config->network.name,
				sizeof(config->network.name));
		}
	}

	if (config->mobile_dpd == 0)
		config->mobile_dpd = config->dpd;

	if (config->cisco_client_compat) {
		if (!config->dtls_legacy && !silent) {
			fprintf(stderr,
				NOTESTR
				"%sthe cisco-client-compat option implies dtls-legacy = true; enabling\n",
				PREFIX_VHOST(vhost));
		}
		config->dtls_legacy = 1;

		if (!config->select_group_by_url && !silent) {
			fprintf(stderr,
				NOTESTR
				"%sthe cisco-client-compat option implies select-group-by-url = true; enabling\n",
				PREFIX_VHOST(vhost));
		}
		config->select_group_by_url = 1;
	}

	if (config->match_dtls_and_tls) {
		if (config->dtls_legacy) {
			fprintf(stderr,
				ERRSTR
				"%s'match-tls-dtls-ciphers' cannot be applied when 'dtls-legacy' or 'cisco-client-compat' is on\n",
				PREFIX_VHOST(vhost));
			exit(EXIT_FAILURE);
		}
	}

	if (config->mobile_idle_timeout == (unsigned int)-1)
		config->mobile_idle_timeout = config->idle_timeout;

#ifdef ENABLE_COMPRESSION
	if (config->no_compress_limit < MIN_NO_COMPRESS_LIMIT)
		config->no_compress_limit = MIN_NO_COMPRESS_LIMIT;
#endif

	/* use tcp listen host by default */
	if (vhost->perm_config.udp_listen_host == NULL) {
		vhost->perm_config.udp_listen_host =
			vhost->perm_config.listen_host;
	}

#if !defined(HAVE_LIBSECCOMP)
	if (config->isolate != 0 && !silent) {
		fprintf(stderr,
			ERRSTR
			"%s'isolate-workers' is set to true, but not compiled with seccomp or Linux namespaces support\n",
			PREFIX_VHOST(vhost));
	}
#endif

	for (j = 0; j < config->network.routes_size; j++) {
		if (ip_route_sanity_check(config->network.routes,
					  &config->network.routes[j]) != 0)
			exit(EXIT_FAILURE);

		if (strcmp(config->network.routes[j], "0.0.0.0/0") == 0 ||
		    strcmp(config->network.routes[j], "default") == 0) {
			/* set default route */
			for (i = 0; i < j; i++)
				talloc_free(config->network.routes[i]);
			config->network.routes_size = 0;
			break;
		}
	}

	for (j = 0; j < config->network.no_routes_size; j++) {
		if (ip_route_sanity_check(config->network.no_routes,
					  &config->network.no_routes[j]) != 0)
			exit(EXIT_FAILURE);
	}

	for (j = 0; j < config->network.dns_size; j++) {
		if (strcmp(config->network.dns[j], "local") == 0) {
			fprintf(stderr,
				ERRSTR
				"%sthe 'local' DNS keyword is no longer supported.\n",
				PREFIX_VHOST(vhost));
			exit(EXIT_FAILURE);
		}
	}

	if (config->per_user_dir || config->per_group_dir) {
		if (vhost->perm_config.sup_config_type != SUP_CONFIG_FILE) {
			fprintf(stderr,
				ERRSTR
				"%sspecified config-per-user or config-per-group but supplemental config is '%s'\n",
				PREFIX_VHOST(vhost),
				sup_config_name(
					vhost->perm_config.sup_config_type));
			exit(EXIT_FAILURE);
		}
	}
}

#define OPT_NO_CHDIR 1
static const struct option long_options[] = {
	{ "debug", 1, 0, 'd' },	     { "log-stderr", 0, 0, 'e' },
	{ "syslog", 0, 0, 's' },     { "config", 1, 0, 'c' },
	{ "pid-file", 1, 0, 'p' },   { "test-config", 0, 0, 't' },
	{ "foreground", 0, 0, 'f' }, { "no-chdir", 0, 0, OPT_NO_CHDIR },
	{ "help", 0, 0, 'h' },	     { "traceable", 0, 0, 'x' },
	{ "version", 0, 0, 'v' },    { NULL, 0, 0, 0 }
};

static void usage(void)
{
	fprintf(stderr, PACKAGE " - " PACKAGE_NAME "\n");
	fprintf(stderr,
		"Usage:  ocserv [ -<flag> [<val>] | --<name>[{=| }<val>] ]...\n\n");

	fprintf(stderr,
		"   -f, --foreground           Do not fork into background\n");
	fprintf(stderr,
		"   -d, --debug=num            Enable verbose network debugging information\n");
	fprintf(stderr,
		"				- it must be in the range:\n");
	fprintf(stderr, "				  0 to 9\n");
	fprintf(stderr,
		"   -c, --config=file          Configuration file for the server\n");
	fprintf(stderr, "				- file must exist\n");
	fprintf(stderr,
		"   -t, --test-config          Test the provided configuration file\n");
	fprintf(stderr,
		"       --no-chdir             Do not perform a chdir on daemonize\n");
	fprintf(stderr,
		"   -p, --pid-file=file        Specify pid file for the server\n");
	fprintf(stderr,
		"   -v, --version              output version information and exit\n");
	fprintf(stderr,
		"   -x, --traceable            Allow processes tracing\n");
	fprintf(stderr, "               - use for debugging purposes only\n");
	fprintf(stderr, "   -e, --log-stderr           Log to stderr\n");
	fprintf(stderr,
		"   -s, --syslog               Log to syslog (default)\n");
	fprintf(stderr,
		"   -h, --help                 Display extended usage information and exit\n\n");

	fprintf(stderr, PACKAGE_NAME " (" PACKAGE
				     ") is a VPN server compatible with the\n");
	fprintf(stderr,
		"OpenConnect VPN client.  It follows the TLS and DTLS-based AnyConnect VPN\n");
	fprintf(stderr, "protocol which is used by several CISCO routers.\n\n");

	fprintf(stderr, "Please file bug reports at:  " PACKAGE_BUGREPORT "\n");
}

int cmd_parser(void *pool, int argc, char **argv, struct list_head *head,
	       bool worker)
{
	unsigned int test_only = 0;
	unsigned int debug_asked = 0;
	int c;
	vhost_cfg_st *vhost;

	vhost = vhost_add(pool, head, NULL, 0);
	assert(vhost != NULL);

	while (1) {
		c = getopt_long(argc, argv, "d:c:p:ftvxh", long_options, NULL);
		if (c == -1)
			break;

		switch (c) {
		case 'f':
			vhost->perm_config.foreground = 1;
			break;
		case 'p':
			strlcpy(pid_file, optarg, sizeof(pid_file));
			break;
		case 'c':
			strlcpy(cfg_file, optarg, sizeof(cfg_file));
			break;
		case 'd':
			vhost->perm_config.log_level = atoi(optarg);
			global_log_prio = vhost->perm_config.log_level;
			debug_asked = 1;
			break;
		case 't':
			test_only = 1;
			break;
		case 'e':
			vhost->perm_config.log_stderr = 1;
			break;
		case 's':
			vhost->perm_config.syslog = 1;
			break;
		case OPT_NO_CHDIR:
			vhost->perm_config.no_chdir = 1;
			break;
		case 'h':
			usage();
			exit(EXIT_SUCCESS);
		case 'v':
			print_version();
			exit(EXIT_SUCCESS);
		case 'x':
			vhost->perm_config.pr_dumpable = 1;
			break;
		}
	}

	if (optind != argc) {
		fprintf(stderr, ERRSTR
			"no additional command line options are allowed\n\n");
		exit(EXIT_FAILURE);
	}

	if (vhost->perm_config.log_stderr == 0 &&
	    vhost->perm_config.syslog == 0) {
		vhost->perm_config.syslog = 1; /* default if nothing specified*/
		if (debug_asked)
			vhost->perm_config.log_stderr =
				1; /* compatible with previous behavior */
	}

	if (access(cfg_file, R_OK) != 0) {
		fprintf(stderr, ERRSTR "cannot access config file: %s\n",
			cfg_file);
		fprintf(stderr,
			"Usage: %s [-c config]\nUse %s --help for more information.\n",
			argv[0], argv[0]);
		exit(EXIT_FAILURE);
	}

	parse_cfg_file(pool, cfg_file, head, worker ? CFG_FLAG_WORKER : 0);

	if (test_only)
		exit(EXIT_SUCCESS);

	return 0;
}

static void archive_cfg(struct list_head *head)
{
	attic_entry_st *e;
	struct vhost_cfg_st *vhost = NULL;

	list_for_each(head, vhost, list)
	{
		/* we don't clear anything as it may be referenced by some
		 * client (proc_st). We move everything to attic and
		 * once nothing is in use we clear that */

		e = talloc(vhost, attic_entry_st);
		if (e == NULL) {
			/* we leak, but better than crashing */
			return;
		}

		e->usage_count = vhost->perm_config.config->usage_count;

		/* we rely on talloc doing that recursively */
		talloc_steal(e, vhost->perm_config.config);
		vhost->perm_config.config = NULL;

		if (e->usage_count == NULL || *e->usage_count == 0) {
			talloc_free(e);
		} else {
			list_add(&vhost->perm_config.attic, &e->list);
		}
	}
}

static void clear_cfg(struct list_head *head)
{
	vhost_cfg_st *cpos = NULL, *ctmp;

	list_for_each_safe(head, cpos, ctmp, list)
	{
		/* we rely on talloc freeing recursively */
		talloc_free(cpos->perm_config.config);
		cpos->perm_config.config = NULL;
	}
}

void clear_vhosts(struct list_head *head)
{
	vhost_cfg_st *vhost = NULL, *ctmp;

	list_for_each_safe(head, vhost, ctmp, list)
	{
		tls_vhost_deinit(vhost);
		/* we rely on talloc freeing recursively */
		talloc_free(vhost->perm_config.config);
		vhost->perm_config.config = NULL;
	}
}

static void append(const char *option)
{
	static int have_previous_val;

	if (have_previous_val == 0) {
		have_previous_val = 1;
	} else {
		fprintf(stderr, ", ");
	}
	fprintf(stderr, "%s", option);
}

static void print_version(void)
{
	const char *p;

	fputs(PACKAGE_STRING, stderr);
	fprintf(stderr, "\n\nCompiled with: ");
#ifdef HAVE_LIBSECCOMP
	append("seccomp");
#endif
#ifdef HAVE_LIBWRAP
	append("tcp-wrappers");
#endif
#ifdef HAVE_LIBOATH
	append("oath");
#endif
#ifdef HAVE_RADIUS
	append("radius");
#endif
#ifdef HAVE_GSSAPI
	append("gssapi");
#endif
#ifdef HAVE_PAM
	append("PAM");
#endif
	append("PKCS#11");
#ifdef ANYCONNECT_CLIENT_COMPAT
	append("AnyConnect");
#endif
#ifdef SUPPORT_OIDC_AUTH
	append("oidc_auth");
#endif
	fprintf(stderr, "\n");

	p = gnutls_check_version(NULL);
	if (strcmp(p, GNUTLS_VERSION) != 0) {
		fprintf(stderr, "GnuTLS version: %s (compiled with %s)\n", p,
			GNUTLS_VERSION);
	} else {
		fprintf(stderr, "GnuTLS version: %s\n", p);
	}
}

void reload_cfg_file(void *pool, struct list_head *configs,
		     unsigned int sec_mod)
{
	struct vhost_cfg_st *vhost = NULL;
	unsigned int flags = CFG_FLAG_RELOAD;

	if (sec_mod)
		flags |= CFG_FLAG_SECMOD;

	/* Archive or clear any non-permanent configs */
	if (!sec_mod)
		archive_cfg(configs);
	else
		clear_cfg(configs);

	/* Create new config structures and apply defaults */
	list_for_each(configs, vhost, list)
	{
		if (vhost->perm_config.config == NULL)
			cfg_new(vhost, 1);
	}

	/* parse the config again */
	parse_cfg_file(pool, cfg_file, configs, flags);
}

void write_pid_file(void)
{
	FILE *fp;

	if (pid_file[0] == 0)
		return;

	fp = fopen(pid_file, "w");
	if (fp == NULL) {
		fprintf(stderr, ERRSTR "cannot open pid file '%s'\n", pid_file);
		exit(EXIT_FAILURE);
	}

	fprintf(fp, "%u", (unsigned int)getpid());
	fclose(fp);
}

void remove_pid_file(void)
{
	if (pid_file[0] == 0)
		return;

	(void)remove(pid_file);
}

int _add_multi_line_val(void *pool, char ***varname, size_t *num,
			const char *value)
{
	unsigned int _max = DEFAULT_CONFIG_ENTRIES;
	void *tmp;

	if (*varname == NULL) {
		*num = 0;
		*varname = talloc_array(pool, char *, _max);
		if (*varname == NULL)
			return -1;
	}

	if (*num >= _max - 1) {
		tmp = talloc_realloc(pool, *varname, char *, (*num) + 2);
		if (tmp == NULL)
			return -1;
		*varname = tmp;
	}

	(*varname)[*num] = talloc_strdup(*varname, value);
	(*num)++;

	(*varname)[*num] = NULL;
	return 0;
}

void clear_old_configs(struct list_head *head)
{
	attic_entry_st *e = NULL, *pos;
	vhost_cfg_st *cpos = NULL;

	list_for_each(head, cpos, list)
	{
		/* go through the attic and clear old configurations if unused */
		list_for_each_safe(&cpos->perm_config.attic, e, pos, list)
		{
			if (*e->usage_count == 0) {
				list_del(&e->list);
				talloc_free(e);
			}
		}
	}
}

// ocserv and ocserv-worker both load and parse the configuration files.
// As part of the process of loading the config files, auth / acct methods
// are enabled based on the content of the acct_mod_st and auth_mod_st tables.
// These auth tables are present in the auth sub-subsystem. Linking against
// the auth subsystem pulls in a very large set of dependent binaries which
// increases the overall memory footprint. To avoid this, we provide stub
// versions of acct_mod_st and auth_mod_st tables that the ocserv-worker
// process can link against.
#if defined(OCSERV_WORKER_PROCESS)

// Group information is populated by the auth subsystem.
// When compiles as part of ocserv-worker, the auth subsystem is not present.
// To work around this, the group information is passed from ocserv-main to
// ocserv-worker, which then caches it and returns it when queried.
static void pam_group_list(void *pool, void *_additional, char ***groupname,
			   unsigned int *groupname_size)
{
	*groupname = pam_auth_group_list;
	*groupname_size = pam_auth_group_list_size;
}

static void gssapi_group_list(void *pool, void *_additional, char ***groupname,
			      unsigned int *groupname_size)
{
	*groupname = gssapi_auth_group_list;
	*groupname_size = gssapi_auth_group_list_size;
}

static void plain_group_list(void *pool, void *_additional, char ***groupname,
			     unsigned int *groupname_size)
{
	*groupname = plain_auth_group_list;
	*groupname_size = plain_auth_group_list_size;
}

const struct acct_mod_st radius_acct_funcs = { .type = ACCT_TYPE_RADIUS,
					       .auth_types = ALL_AUTH_TYPES,
					       .vhost_init = NULL,
					       .vhost_deinit = NULL,
					       .open_session = NULL,
					       .close_session = NULL,
					       .session_stats = NULL };

const struct acct_mod_st pam_acct_funcs = {
	.type = ACCT_TYPE_PAM,
	.auth_types = ALL_AUTH_TYPES,
	.open_session = NULL,
	.close_session = NULL,
};

const struct auth_mod_st pam_auth_funcs = { .type = AUTH_TYPE_PAM |
						    AUTH_TYPE_USERNAME_PASS,
					    .auth_init = NULL,
					    .auth_deinit = NULL,
					    .auth_msg = NULL,
					    .auth_pass = NULL,
					    .auth_group = NULL,
					    .auth_user = NULL,
					    .group_list = pam_group_list };

const struct auth_mod_st gssapi_auth_funcs = { .type = AUTH_TYPE_GSSAPI,
					       .auth_init = NULL,
					       .auth_deinit = NULL,
					       .auth_msg = NULL,
					       .auth_pass = NULL,
					       .auth_user = NULL,
					       .auth_group = NULL,
					       .vhost_init = NULL,
					       .vhost_deinit = NULL,
					       .group_list =
						       gssapi_group_list };

const struct auth_mod_st plain_auth_funcs = { .type = AUTH_TYPE_PLAIN |
						      AUTH_TYPE_USERNAME_PASS,
					      .allows_retries = 1,
					      .vhost_init = NULL,
					      .auth_init = NULL,
					      .auth_deinit = NULL,
					      .auth_msg = NULL,
					      .auth_pass = NULL,
					      .auth_user = NULL,
					      .auth_group = NULL,
					      .group_list = plain_group_list };

const struct auth_mod_st radius_auth_funcs = { .type = AUTH_TYPE_RADIUS |
						       AUTH_TYPE_USERNAME_PASS,
					       .allows_retries = 1,
					       .vhost_init = NULL,
					       .vhost_deinit = NULL,
					       .auth_init = NULL,
					       .auth_deinit = NULL,
					       .auth_msg = NULL,
					       .auth_pass = NULL,
					       .auth_user = NULL,
					       .auth_group = NULL,
					       .group_list = NULL };

const struct auth_mod_st oidc_auth_funcs = { .type = AUTH_TYPE_OIDC,
					     .allows_retries = 1,
					     .vhost_init = NULL,
					     .vhost_deinit = NULL,
					     .auth_init = NULL,
					     .auth_deinit = NULL,
					     .auth_msg = NULL,
					     .auth_pass = NULL,
					     .auth_user = NULL,
					     .auth_group = NULL,
					     .group_list = NULL };

#else
int get_cert_names(struct worker_st *ws, const gnutls_datum_t *raw)
{
	return -1;
}
#endif

char secmod_socket_file_name_socket_file[_POSIX_PATH_MAX] = { 0 };

void restore_secmod_socket_file_name(const char *save_path)
{
	strlcpy(secmod_socket_file_name_socket_file, save_path,
		sizeof(secmod_socket_file_name_socket_file));
}

/* Creates a permanent filename to use for secmod to main communication
 */
const char *secmod_socket_file_name(struct perm_cfg_st *perm_config)
{
	unsigned int rnd;
	int ret;

	if (secmod_socket_file_name_socket_file[0] != 0)
		return secmod_socket_file_name_socket_file;

	ret = gnutls_rnd(GNUTLS_RND_NONCE, &rnd, sizeof(rnd));
	if (ret < 0)
		exit(EXIT_FAILURE);

	/* make socket name */
	snprintf(secmod_socket_file_name_socket_file,
		 sizeof(secmod_socket_file_name_socket_file), "%s.%x",
		 perm_config->socket_file_prefix, rnd);

	return secmod_socket_file_name_socket_file;
}
