mirror of
https://gitlab.com/openconnect/ocserv.git
synced 2026-08-08 09:21:48 +08:00
* Import system headers as `#include <...>`. * Import local headers as `#include "..."`. * Use consistent header guards (starting with `OC_`). Signed-off-by: Dimitri Papadopoulos <3350651-DimitriPapadopoulos@users.noreply.gitlab.com>
2742 lines
77 KiB
C
2742 lines
77 KiB
C
/*
|
|
* Copyright (C) 2013-2026 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/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 "cfg.pb-c.h"
|
|
#include "main.h"
|
|
#include "tlslib.h"
|
|
#include "occtl/ctl.h"
|
|
#include <gnutls/crypto.h>
|
|
#include "common-config.h"
|
|
|
|
#include <getopt.h>
|
|
#include "common/snapshot.h"
|
|
|
|
#define OLD_DEFAULT_CFG_FILE "/etc/ocserv.conf"
|
|
#define DEFAULT_CFG_FILE "/etc/ocserv/ocserv.conf"
|
|
|
|
static int parse_syslog_facility(const char *name)
|
|
{
|
|
unsigned i;
|
|
static const struct {
|
|
const char *name;
|
|
int facility;
|
|
} table[] = {
|
|
{ "daemon", LOG_DAEMON }, { "user", LOG_USER },
|
|
{ "auth", LOG_AUTH }, { "local0", LOG_LOCAL0 },
|
|
{ "local1", LOG_LOCAL1 }, { "local2", LOG_LOCAL2 },
|
|
{ "local3", LOG_LOCAL3 }, { "local4", LOG_LOCAL4 },
|
|
{ "local5", LOG_LOCAL5 }, { "local6", LOG_LOCAL6 },
|
|
{ "local7", LOG_LOCAL7 },
|
|
#ifdef LOG_AUTHPRIV
|
|
{ "authpriv", LOG_AUTHPRIV },
|
|
#endif
|
|
};
|
|
|
|
for (i = 0; i < ARRAY_SIZE(table); i++) {
|
|
if (strcasecmp(name, table[i].name) == 0)
|
|
return table[i].facility;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
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);
|
|
static bool error_on_vhost(const char *vname, const char *oname);
|
|
static void vhost_inherit_static_config(vhost_cfg_st *vhost,
|
|
vhost_cfg_st *defvhost);
|
|
|
|
#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_array(pool, char *, \
|
|
DEFAULT_CONFIG_ENTRIES); \
|
|
varname2 = talloc_array(pool, 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; \
|
|
}
|
|
|
|
/* Variants for ReloadableConfig optional scalars: also set the has_X flag so
|
|
* that protobuf pack/unpack propagates the value into named vhosts. */
|
|
#define READ_TF_VC(field) \
|
|
{ \
|
|
if (strcasecmp(value, "true") == 0 || \
|
|
strcasecmp(value, "yes") == 0) \
|
|
config->field = 1; \
|
|
else \
|
|
config->field = 0; \
|
|
config->has_##field = 1; \
|
|
}
|
|
|
|
#define READ_NUMERIC_VC(field) \
|
|
{ \
|
|
config->field = strtol(value, NULL, 10); \
|
|
config->has_##field = 1; \
|
|
}
|
|
|
|
#define READ_NUMERIC_VC_U64(field) \
|
|
{ \
|
|
config->field = strtoull(value, NULL, 10); \
|
|
config->has_##field = 1; \
|
|
}
|
|
|
|
#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 static_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 static_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 static_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 static_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 static_cfg_st *config, const char *acct,
|
|
unsigned int is_worker)
|
|
{
|
|
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);
|
|
}
|
|
|
|
/* Two-phase talloc allocator for protobuf-c unpack */
|
|
struct cfg_alloc_ctx {
|
|
TALLOC_CTX *pool;
|
|
void *top; /* NULL until first alloc */
|
|
};
|
|
|
|
static void *cfg_talloc_alloc(void *data, size_t size)
|
|
{
|
|
struct cfg_alloc_ctx *ctx = data;
|
|
|
|
if (ctx->top == NULL) {
|
|
ctx->top = talloc_size(ctx->pool, size);
|
|
return ctx->top;
|
|
}
|
|
return talloc_size(ctx->top, size);
|
|
}
|
|
|
|
static void cfg_talloc_free(void *data, void *ptr)
|
|
{
|
|
(void)data;
|
|
talloc_free(ptr);
|
|
}
|
|
|
|
/*
|
|
* cfg_copy_from_default - deep-copy a ReloadableConfig via protobuf pack+unpack.
|
|
*
|
|
* All fields (strings, repeated, sub-messages) are talloc-allocated under the
|
|
* returned ReloadableConfig so a single talloc_free(copy) releases everything.
|
|
*/
|
|
static ReloadableConfig *cfg_copy_from_default(const ReloadableConfig *def,
|
|
TALLOC_CTX *pool)
|
|
{
|
|
struct cfg_alloc_ctx ctx = { .pool = pool, .top = NULL };
|
|
ProtobufCAllocator alloc = {
|
|
.alloc = cfg_talloc_alloc,
|
|
.free = cfg_talloc_free,
|
|
.allocator_data = &ctx,
|
|
};
|
|
size_t len;
|
|
uint8_t *buf;
|
|
ReloadableConfig *copy;
|
|
|
|
len = reloadable_config__get_packed_size(def);
|
|
buf = talloc_size(pool, len);
|
|
if (buf == NULL) {
|
|
fprintf(stderr, ERRSTR "memory\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
reloadable_config__pack(def, buf);
|
|
copy = (ReloadableConfig *)protobuf_c_message_unpack(
|
|
&reloadable_config__descriptor, &alloc, len, buf);
|
|
talloc_free(buf);
|
|
if (copy == NULL) {
|
|
fprintf(stderr, ERRSTR "protobuf unpack failed\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
return copy;
|
|
}
|
|
|
|
#ifdef HAVE_GSSAPI
|
|
static void parse_kkdcp(ReloadableConfig *config, char **urlfw,
|
|
unsigned int urlfw_size)
|
|
{
|
|
unsigned int i, j;
|
|
char *path, *server, *port, *realm;
|
|
struct addrinfo hints, *res;
|
|
int ret;
|
|
KkdcpConfig *kkdcp;
|
|
KkdcpRealmConfig *kkdcp_realm;
|
|
|
|
/* Allocate array of pointers */
|
|
config->kkdcp = talloc_zero_array(config, KkdcpConfig *, urlfw_size);
|
|
if (config->kkdcp == NULL) {
|
|
fprintf(stderr, ERRSTR "memory\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
config->n_kkdcp = 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->n_kkdcp; j++) {
|
|
if (strcmp(path, config->kkdcp[j]->url) == 0) {
|
|
kkdcp = config->kkdcp[j];
|
|
}
|
|
}
|
|
|
|
if (kkdcp == NULL) {
|
|
kkdcp = talloc_zero(config, KkdcpConfig);
|
|
if (kkdcp == NULL) {
|
|
fprintf(stderr, ERRSTR "memory\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
kkdcp_config__init(kkdcp);
|
|
kkdcp->url = talloc_strdup(kkdcp, path);
|
|
config->kkdcp[config->n_kkdcp] = kkdcp;
|
|
config->n_kkdcp++;
|
|
}
|
|
|
|
/* add realm to this kkdcp entry */
|
|
kkdcp->realms = talloc_realloc(kkdcp, kkdcp->realms,
|
|
KkdcpRealmConfig *,
|
|
kkdcp->n_realms + 1);
|
|
if (kkdcp->realms == NULL) {
|
|
fprintf(stderr, ERRSTR "memory\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
kkdcp_realm = talloc_zero(kkdcp, KkdcpRealmConfig);
|
|
if (kkdcp_realm == NULL) {
|
|
fprintf(stderr, ERRSTR "memory\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
kkdcp_realm_config__init(kkdcp_realm);
|
|
|
|
kkdcp_realm->has_addr = 1;
|
|
kkdcp_realm->addr.len = res->ai_addrlen;
|
|
kkdcp_realm->addr.data = talloc_memdup(
|
|
kkdcp_realm, res->ai_addr, res->ai_addrlen);
|
|
if (kkdcp_realm->addr.data == NULL) {
|
|
fprintf(stderr, ERRSTR "memory\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
kkdcp_realm->has_ai_family = 1;
|
|
kkdcp_realm->ai_family = res->ai_family;
|
|
kkdcp_realm->has_ai_socktype = 1;
|
|
kkdcp_realm->ai_socktype = res->ai_socktype;
|
|
kkdcp_realm->has_ai_protocol = 1;
|
|
kkdcp_realm->ai_protocol = res->ai_protocol;
|
|
kkdcp_realm->realm = talloc_strdup(kkdcp_realm, realm);
|
|
|
|
freeaddrinfo(res);
|
|
|
|
kkdcp->realms[kkdcp->n_realms] = kkdcp_realm;
|
|
kkdcp->n_realms++;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
struct iroute_ctx {
|
|
ReloadableConfig *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->n_known_iroutes, 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(ReloadableConfig *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->n_known_iroutes; j++) {
|
|
if (ip_route_sanity_check(config->known_iroutes,
|
|
&config->known_iroutes[j]) != 0)
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
static void load_iroutes(ReloadableConfig *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)
|
|
{
|
|
ReloadableConfig *c = vhost->config;
|
|
|
|
/* set config (no-zero) default vals
|
|
*/
|
|
if (!reload) { /* perm config defaults */
|
|
tls_vhost_init(vhost);
|
|
vhost->static_config.stats_reset_time =
|
|
24 * 60 * 60 * 7; /* weekly */
|
|
vhost->static_config.log_level = DEFAULT_LOG_LEVEL;
|
|
vhost->static_config.syslog_facility = LOG_DAEMON;
|
|
}
|
|
|
|
vhost->static_config.occtl_socket_file = OCCTL_UNIX_SOCKET;
|
|
|
|
c->has_mobile_idle_timeout = 1;
|
|
c->mobile_idle_timeout = (uint32_t)-1;
|
|
#ifdef ENABLE_COMPRESSION
|
|
c->has_no_compress_limit = 1;
|
|
c->no_compress_limit = DEFAULT_NO_COMPRESS_LIMIT;
|
|
#endif
|
|
c->has_rekey_time = 1;
|
|
c->rekey_time = DEFAULT_REKEY_TIME;
|
|
c->has_cookie_timeout = 1;
|
|
c->cookie_timeout = DEFAULT_COOKIE_RECON_TIMEOUT;
|
|
c->has_auth_timeout = 1;
|
|
c->auth_timeout = DEFAULT_AUTH_TIMEOUT_SECS;
|
|
c->has_ban_time = 1;
|
|
c->ban_time = DEFAULT_BAN_TIME;
|
|
c->has_ban_reset_time = 1;
|
|
c->ban_reset_time = DEFAULT_BAN_RESET_TIME;
|
|
c->has_max_ban_score = 1;
|
|
c->max_ban_score = DEFAULT_MAX_BAN_SCORE;
|
|
c->has_ban_points_wrong_password = 1;
|
|
c->ban_points_wrong_password = DEFAULT_PASSWORD_POINTS;
|
|
c->has_ban_points_connect = 1;
|
|
c->ban_points_connect = DEFAULT_CONNECT_POINTS;
|
|
c->has_ban_points_kkdcp = 1;
|
|
c->ban_points_kkdcp = DEFAULT_KKDCP_POINTS;
|
|
c->has_dpd = 1;
|
|
c->dpd = DEFAULT_DPD_TIME;
|
|
c->network->has_ipv6_subnet_prefix = 1;
|
|
c->network->ipv6_subnet_prefix = 128;
|
|
c->has_dtls_legacy = 1;
|
|
c->dtls_legacy = 1;
|
|
c->has_dtls_psk = 1;
|
|
c->dtls_psk = 1;
|
|
c->has_predictable_ips = 1;
|
|
c->predictable_ips = 1;
|
|
c->has_use_utmp = 1;
|
|
c->use_utmp = 1;
|
|
c->has_keepalive = 1;
|
|
c->keepalive = DEFAULT_KEEPALIVE_TIME;
|
|
c->has_switch_to_tcp_timeout = 1;
|
|
c->switch_to_tcp_timeout = DEFAULT_SWITCH_TO_TCP_TIMEOUT;
|
|
c->has_mobile_dpd = 1;
|
|
c->mobile_dpd = DEFAULT_MOBILE_DPD_TIME;
|
|
}
|
|
|
|
static void cfg_new(struct vhost_cfg_st *vhost, unsigned int reload)
|
|
{
|
|
ReloadableConfig *cfg;
|
|
NetworkConfig *net;
|
|
|
|
cfg = talloc_zero(vhost->pool, ReloadableConfig);
|
|
if (cfg == NULL) {
|
|
fprintf(stderr, ERRSTR "memory\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
reloadable_config__init(cfg);
|
|
|
|
net = talloc_zero(cfg, NetworkConfig);
|
|
if (net == NULL) {
|
|
fprintf(stderr, ERRSTR "memory\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
network_config__init(net);
|
|
cfg->network = net;
|
|
|
|
vhost->config = cfg;
|
|
|
|
vhost->usage_count = talloc_zero(vhost->pool, int);
|
|
if (vhost->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->static_config.sup_config_type = SUP_CONFIG_FILE;
|
|
list_head_init(&vhost->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;
|
|
};
|
|
|
|
static bool error_on_vhost(const char *vname, const char *oname)
|
|
{
|
|
if (vname) {
|
|
fprintf(stderr,
|
|
ERRSTR
|
|
"'%s' cannot be set inside a virtual host section\n",
|
|
oname);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static char *idna_map(void *pool, const char *name, unsigned int size)
|
|
{
|
|
#if GNUTLS_VERSION_NUMBER > 0x030508
|
|
int ret;
|
|
gnutls_datum_t out;
|
|
char *result;
|
|
|
|
ret = gnutls_idna_map(name, size, &out, 0);
|
|
if (ret < 0) {
|
|
goto fallback;
|
|
}
|
|
|
|
result = talloc_strdup(pool, (char *)out.data);
|
|
gnutls_free(out.data);
|
|
return result;
|
|
|
|
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;
|
|
ReloadableConfig *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);
|
|
|
|
/* Pre-populate this named vhost from the default vhost before
|
|
* any of its options are parsed, so subsequent options overlay
|
|
* the inherited values. */
|
|
if (!vhost->cfg_inherited) {
|
|
ReloadableConfig *copy;
|
|
|
|
copy = cfg_copy_from_default(defvhost->config,
|
|
vhost->pool);
|
|
if (copy == NULL) {
|
|
fprintf(stderr, ERRSTR "memory\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
talloc_free(vhost->config);
|
|
vhost->config = copy;
|
|
vhost->cfg_inherited = 1;
|
|
}
|
|
}
|
|
|
|
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) {
|
|
if (error_on_vhost(vhost->name, "listen-host"))
|
|
return 0;
|
|
PREAD_STRING(pool, vhost->static_config.listen_host);
|
|
} else if (strcmp(name, "udp-listen-host") == 0) {
|
|
if (error_on_vhost(vhost->name, "udp-listen-host"))
|
|
return 0;
|
|
PREAD_STRING(pool,
|
|
vhost->static_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->static_config.listen_netns_name =
|
|
talloc_strdup(pool, value);
|
|
} else if (strcmp(name, "tcp-port") == 0) {
|
|
if (error_on_vhost(vhost->name, "tcp-port"))
|
|
return 0;
|
|
READ_NUMERIC(vhost->static_config.port);
|
|
} else if (strcmp(name, "udp-port") == 0) {
|
|
if (error_on_vhost(vhost->name, "udp-port"))
|
|
return 0;
|
|
READ_NUMERIC(vhost->static_config.udp_port);
|
|
} else if (strcmp(name, "run-as-user") == 0) {
|
|
if (error_on_vhost(vhost->name, "run-as-user"))
|
|
return 0;
|
|
const struct passwd *pwd = getpwnam(value);
|
|
|
|
if (pwd == NULL) {
|
|
fprintf(stderr, ERRSTR "unknown user: %s\n",
|
|
value);
|
|
return 0;
|
|
}
|
|
vhost->static_config.uid = pwd->pw_uid;
|
|
} else if (strcmp(name, "run-as-group") == 0) {
|
|
if (error_on_vhost(vhost->name, "run-as-group"))
|
|
return 0;
|
|
const struct group *grp = getgrnam(value);
|
|
|
|
if (grp == NULL) {
|
|
fprintf(stderr, ERRSTR "unknown group: %s\n",
|
|
value);
|
|
return 0;
|
|
}
|
|
vhost->static_config.gid = grp->gr_gid;
|
|
} else if (strcmp(name, "server-cert") == 0) {
|
|
READ_MULTI_LINE(vhost->static_config.cert,
|
|
vhost->static_config.cert_size);
|
|
} else if (strcmp(name, "server-key") == 0) {
|
|
READ_MULTI_LINE(vhost->static_config.key,
|
|
vhost->static_config.key_size);
|
|
} else if (strcmp(name, "debug-no-secmod-stats") == 0) {
|
|
READ_TF(vhost->static_config.debug_no_secmod_stats);
|
|
} else if (strcmp(name, "dh-params") == 0) {
|
|
READ_STRING(vhost->static_config.dh_params_file);
|
|
} else if (strcmp(name, "pin-file") == 0) {
|
|
READ_STRING(vhost->static_config.pin_file);
|
|
} else if (strcmp(name, "srk-pin-file") == 0) {
|
|
READ_STRING(vhost->static_config.srk_pin_file);
|
|
} else if (strcmp(name, "ca-cert") == 0) {
|
|
READ_STRING(vhost->static_config.ca);
|
|
#if !defined(OCSERV_WORKER_PROCESS)
|
|
} else if (strcmp(name, "key-pin") == 0) {
|
|
READ_STRING(vhost->static_config.key_pin);
|
|
} else if (strcmp(name, "srk-pin") == 0) {
|
|
READ_STRING(vhost->static_config.srk_pin);
|
|
#endif
|
|
} else if (strcmp(name, "socket-file") == 0) {
|
|
if (error_on_vhost(vhost->name, "socket-file"))
|
|
return 0;
|
|
PREAD_STRING(pool,
|
|
vhost->static_config.socket_file_prefix);
|
|
} else if (strcmp(name, "occtl-socket-file") == 0) {
|
|
if (error_on_vhost(vhost->name, "occtl-socket-file"))
|
|
return 0;
|
|
PREAD_STRING(pool,
|
|
vhost->static_config.occtl_socket_file);
|
|
} else if (strcmp(name, "chroot-dir") == 0) {
|
|
if (error_on_vhost(vhost->name, "chroot-dir"))
|
|
return 0;
|
|
PREAD_STRING(pool, vhost->static_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 (error_on_vhost(vhost->name,
|
|
"server-stats-reset-time"))
|
|
return 0;
|
|
READ_NUMERIC(vhost->static_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 (error_on_vhost(vhost->name, "sec-mod-scale"))
|
|
return 0;
|
|
READ_NUMERIC(vhost->static_config.sec_mod_scale);
|
|
} else if (strcmp(name, "log-level") == 0) {
|
|
READ_NUMERIC(vhost->static_config.log_level);
|
|
global_log_prio = vhost->static_config.log_level;
|
|
} else if (strcmp(name, "syslog-facility") == 0) {
|
|
int facility = parse_syslog_facility(value);
|
|
if (facility < 0) {
|
|
oc_syslog(LOG_ERR,
|
|
"unknown syslog facility: %s", value);
|
|
return 0;
|
|
}
|
|
vhost->static_config.syslog_facility = facility;
|
|
} else {
|
|
stage1_found = 0;
|
|
}
|
|
if (stage1_found)
|
|
goto exit;
|
|
}
|
|
|
|
/* read the rest of the (non-permanent) configuration */
|
|
pool = vhost->config;
|
|
config = vhost->config;
|
|
|
|
/* When adding allocated data, remember to modify
|
|
* reload_cfg_file();
|
|
*/
|
|
if (strcmp(name, "listen-host-is-dyndns") == 0) {
|
|
READ_TF_VC(is_dyndns);
|
|
} else if (strcmp(name, "listen-proxy-proto") == 0) {
|
|
if (error_on_vhost(vhost->name, "listen-proxy-proto"))
|
|
return 0;
|
|
READ_TF_VC(listen_proxy_proto);
|
|
} else if (strcmp(name, "append-routes") == 0) {
|
|
READ_TF_VC(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_VC(tunnel_all_dns);
|
|
} else if (strcmp(name, "keepalive") == 0) {
|
|
READ_NUMERIC_VC(keepalive);
|
|
} else if (strcmp(name, "switch-to-tcp-timeout") == 0) {
|
|
READ_NUMERIC_VC(switch_to_tcp_timeout);
|
|
} else if (strcmp(name, "dpd") == 0) {
|
|
READ_NUMERIC_VC(dpd);
|
|
} else if (strcmp(name, "mobile-dpd") == 0) {
|
|
READ_NUMERIC_VC(mobile_dpd);
|
|
} else if (strcmp(name, "rate-limit-ms") == 0) {
|
|
if (error_on_vhost(vhost->name, "rate-limit-ms"))
|
|
return 0;
|
|
READ_NUMERIC_VC(rate_limit_ms);
|
|
} else if (strcmp(name, "server-drain-ms") == 0) {
|
|
if (error_on_vhost(vhost->name, "server-drain-ms"))
|
|
return 0;
|
|
READ_NUMERIC_VC(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_VC(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 (error_on_vhost(vhost->name, "connect-script"))
|
|
return 0;
|
|
READ_STRING(config->connect_script);
|
|
} else if (strcmp(name, "host-update-script") == 0) {
|
|
if (error_on_vhost(vhost->name, "host-update-script"))
|
|
return 0;
|
|
READ_STRING(config->host_update_script);
|
|
} else if (strcmp(name, "disconnect-script") == 0) {
|
|
if (error_on_vhost(vhost->name, "disconnect-script"))
|
|
return 0;
|
|
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_VC(dtls_legacy);
|
|
} else if (strcmp(name, "cisco-client-compat") == 0) {
|
|
READ_TF_VC(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;
|
|
config->has_cisco_client_compat = 1;
|
|
}
|
|
} else if (strcmp(name, "cisco-svc-client-compat") == 0) {
|
|
READ_TF_VC(cisco_svc_client_compat);
|
|
} else if (strcmp(name, "dtls-psk") == 0) {
|
|
if (error_on_vhost(vhost->name, "dtls-psk"))
|
|
return 0;
|
|
READ_TF_VC(dtls_psk);
|
|
} else if (strcmp(name, "match-tls-dtls-ciphers") == 0) {
|
|
READ_TF_VC(match_dtls_and_tls);
|
|
#ifdef ENABLE_COMPRESSION
|
|
} else if (strcmp(name, "compression") == 0) {
|
|
READ_TF_VC(enable_compression);
|
|
} else if (strcmp(name, "compression-algo-priority") == 0) {
|
|
if (error_on_vhost(vhost->name, "compression-algo-priority"))
|
|
return 0;
|
|
#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_VC(no_compress_limit);
|
|
#endif
|
|
} else if (strcmp(name, "use-seccomp") == 0) {
|
|
if (error_on_vhost(vhost->name, "use-seccomp"))
|
|
return 0;
|
|
READ_TF_VC(isolate);
|
|
if (config->isolate)
|
|
fprintf(stderr, NOTESTR
|
|
"'use-seccomp' was replaced by 'isolate-workers'\n");
|
|
} else if (strcmp(name, "isolate-workers") == 0) {
|
|
if (error_on_vhost(vhost->name, "isolate-workers"))
|
|
return 0;
|
|
READ_TF_VC(isolate);
|
|
} else if (strcmp(name, "limit-worker-memory") == 0) {
|
|
if (error_on_vhost(vhost->name, "limit-worker-memory"))
|
|
return 0;
|
|
READ_TF_VC(limit_worker_memory);
|
|
} else if (strcmp(name, "sec-mod-db-cleanup-time") == 0) {
|
|
if (error_on_vhost(vhost->name, "sec-mod-db-cleanup-time"))
|
|
return 0;
|
|
READ_NUMERIC_VC(sec_mod_db_cleanup_time);
|
|
} else if (strcmp(name, "predictable-ips") == 0) {
|
|
READ_TF_VC(predictable_ips);
|
|
} else if (strcmp(name, "use-utmp") == 0) {
|
|
if (error_on_vhost(vhost->name, "use-utmp"))
|
|
return 0;
|
|
READ_TF_VC(use_utmp);
|
|
} else if (strcmp(name, "use-dbus") == 0) {
|
|
if (error_on_vhost(vhost->name, "use-dbus"))
|
|
return 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;
|
|
config->has_use_occtl = 1;
|
|
}
|
|
} else if (strcmp(name, "use-occtl") == 0) {
|
|
if (error_on_vhost(vhost->name, "use-occtl"))
|
|
return 0;
|
|
READ_TF_VC(use_occtl);
|
|
} else if (strcmp(name, "try-mtu-discovery") == 0) {
|
|
if (error_on_vhost(vhost->name, "try-mtu-discovery"))
|
|
return 0;
|
|
READ_TF_VC(try_mtu);
|
|
} else if (strcmp(name, "ping-leases") == 0) {
|
|
if (error_on_vhost(vhost->name, "ping-leases"))
|
|
return 0;
|
|
READ_TF_VC(ping_leases);
|
|
} else if (strcmp(name, "restrict-user-to-routes") == 0) {
|
|
READ_TF_VC(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_VC(default_mtu);
|
|
} else if (strcmp(name, "net-priority") == 0) {
|
|
READ_PRIO_TOS(config->net_priority);
|
|
config->has_net_priority = 1;
|
|
} else if (strcmp(name, "output-buffer") == 0) {
|
|
READ_NUMERIC_VC(output_buffer);
|
|
} else if (strcmp(name, "rx-data-per-sec") == 0) {
|
|
READ_NUMERIC_VC_U64(rx_per_sec);
|
|
config->rx_per_sec /= 1000; /* in kb */
|
|
} else if (strcmp(name, "tx-data-per-sec") == 0) {
|
|
READ_NUMERIC_VC_U64(tx_per_sec);
|
|
config->tx_per_sec /= 1000; /* in kb */
|
|
} else if (strcmp(name, "deny-roaming") == 0) {
|
|
READ_TF_VC(deny_roaming);
|
|
} else if (strcmp(name, "stats-report-time") == 0) {
|
|
READ_NUMERIC_VC(stats_report_time);
|
|
} else if (strcmp(name, "rekey-time") == 0) {
|
|
READ_NUMERIC_VC(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;
|
|
}
|
|
config->has_rekey_method = 1;
|
|
} else if (strcmp(name, "cookie-timeout") == 0) {
|
|
READ_NUMERIC_VC(cookie_timeout);
|
|
} else if (strcmp(name, "persistent-cookies") == 0) {
|
|
READ_TF_VC(persistent_cookies);
|
|
} else if (strcmp(name, "session-timeout") == 0) {
|
|
READ_NUMERIC_VC(session_timeout);
|
|
} else if (strcmp(name, "auth-timeout") == 0) {
|
|
if (error_on_vhost(vhost->name, "auth-timeout"))
|
|
return 0;
|
|
READ_NUMERIC_VC(auth_timeout);
|
|
} else if (strcmp(name, "idle-timeout") == 0) {
|
|
READ_NUMERIC_VC(idle_timeout);
|
|
} else if (strcmp(name, "mobile-idle-timeout") == 0) {
|
|
READ_NUMERIC_VC(mobile_idle_timeout);
|
|
} else if (strcmp(name, "max-clients") == 0) {
|
|
if (error_on_vhost(vhost->name, "max-clients"))
|
|
return 0;
|
|
READ_NUMERIC_VC(max_clients);
|
|
} else if (strcmp(name, "min-reauth-time") == 0) {
|
|
if (error_on_vhost(vhost->name, "min-reauth-time"))
|
|
return 0;
|
|
READ_NUMERIC_VC(ban_time);
|
|
fprintf(stderr, NOTESTR
|
|
"'min-reauth-time' was replaced by 'ban-time'\n");
|
|
} else if (strcmp(name, "ban-time") == 0) {
|
|
if (error_on_vhost(vhost->name, "ban-time"))
|
|
return 0;
|
|
READ_NUMERIC_VC(ban_time);
|
|
} else if (strcmp(name, "ban-reset-time") == 0) {
|
|
if (error_on_vhost(vhost->name, "ban-reset-time"))
|
|
return 0;
|
|
READ_NUMERIC_VC(ban_reset_time);
|
|
} else if (strcmp(name, "max-ban-score") == 0) {
|
|
if (error_on_vhost(vhost->name, "max-ban-score"))
|
|
return 0;
|
|
READ_NUMERIC_VC(max_ban_score);
|
|
} else if (strcmp(name, "ban-points-wrong-password") == 0) {
|
|
if (error_on_vhost(vhost->name, "ban-points-wrong-password"))
|
|
return 0;
|
|
READ_NUMERIC_VC(ban_points_wrong_password);
|
|
} else if (strcmp(name, "ban-points-connection") == 0) {
|
|
if (error_on_vhost(vhost->name, "ban-points-connection"))
|
|
return 0;
|
|
READ_NUMERIC_VC(ban_points_connect);
|
|
} else if (strcmp(name, "ban-points-kkdcp") == 0) {
|
|
if (error_on_vhost(vhost->name, "ban-points-kkdcp"))
|
|
return 0;
|
|
READ_NUMERIC_VC(ban_points_kkdcp);
|
|
} else if (strcmp(name, "max-same-clients") == 0) {
|
|
READ_NUMERIC_VC(max_same_clients);
|
|
} else if (strcmp(name, "device") == 0) {
|
|
if (error_on_vhost(vhost->name, "device"))
|
|
return 0;
|
|
PREAD_STRING(config->network, config->network->name);
|
|
} else if (strcmp(name, "proxy-url") == 0) {
|
|
READ_STRING(config->proxy_url);
|
|
} else if (strcmp(name, "ipv4-network") == 0) {
|
|
PREAD_STRING(config->network, config->network->ipv4);
|
|
prefix4 = extract_prefix(config->network->ipv4);
|
|
if (prefix4 != 0) {
|
|
config->network->ipv4_netmask = ipv4_prefix_to_strmask(
|
|
config->network, prefix4);
|
|
}
|
|
} else if (strcmp(name, "ipv4-netmask") == 0) {
|
|
PREAD_STRING(config->network, config->network->ipv4_netmask);
|
|
} else if (strcmp(name, "ipv6-network") == 0) {
|
|
PREAD_STRING(config->network, config->network->ipv6);
|
|
prefix = extract_prefix(config->network->ipv6);
|
|
if (prefix) {
|
|
config->network->has_ipv6_prefix = 1;
|
|
config->network->ipv6_prefix = prefix;
|
|
}
|
|
} else if (strcmp(name, "ipv6-prefix") == 0) {
|
|
READ_NUMERIC(config->network->ipv6_prefix);
|
|
config->network->has_ipv6_prefix = 1;
|
|
|
|
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->has_ipv6_subnet_prefix = 1;
|
|
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->n_custom_header);
|
|
} else if (strcmp(name, "split-dns") == 0) {
|
|
READ_MULTI_LINE(config->split_dns, config->n_split_dns);
|
|
} 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->n_included_http_headers),
|
|
_value) < 0) {
|
|
fprintf(stderr, ERRSTR "memory\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
} else if (strcmp(name, "route") == 0) {
|
|
_add_multi_line_val(config->network, &config->network->routes,
|
|
&config->network->n_routes, value);
|
|
} else if (strcmp(name, "no-route") == 0) {
|
|
_add_multi_line_val(config->network,
|
|
&config->network->no_routes,
|
|
&config->network->n_no_routes, value);
|
|
} 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_VC(select_group_by_url);
|
|
} else if (strcmp(name, "auto-select-group") == 0) {
|
|
READ_TF_VC(auto_select_group);
|
|
} else if (strcmp(name, "select-group") == 0) {
|
|
READ_MULTI_BRACKET_LINE(config->group_list,
|
|
config->friendly_group_list,
|
|
config->n_group_list);
|
|
} else if (strcmp(name, "dns") == 0) {
|
|
_add_multi_line_val(config->network, &config->network->dns,
|
|
&config->network->n_dns, value);
|
|
} else if (strcmp(name, "ipv4-dns") == 0) {
|
|
_add_multi_line_val(config->network, &config->network->dns,
|
|
&config->network->n_dns, value);
|
|
} else if (strcmp(name, "ipv6-dns") == 0) {
|
|
_add_multi_line_val(config->network, &config->network->dns,
|
|
&config->network->n_dns, value);
|
|
} else if (strcmp(name, "nbns") == 0) {
|
|
_add_multi_line_val(config->network, &config->network->nbns,
|
|
&config->network->n_nbns, value);
|
|
} else if (strcmp(name, "ipv4-nbns") == 0) {
|
|
_add_multi_line_val(config->network, &config->network->nbns,
|
|
&config->network->n_nbns, value);
|
|
} else if (strcmp(name, "ipv6-nbns") == 0) {
|
|
_add_multi_line_val(config->network, &config->network->nbns,
|
|
&config->network->n_nbns, value);
|
|
} else if (strcmp(name, "route-add-cmd") == 0) {
|
|
if (error_on_vhost(vhost->name, "route-add-cmd"))
|
|
return 0;
|
|
READ_STRING(config->route_add_cmd);
|
|
} else if (strcmp(name, "route-del-cmd") == 0) {
|
|
if (error_on_vhost(vhost->name, "route-del-cmd"))
|
|
return 0;
|
|
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(config->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_VC(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 (strcmp(name, "no-udp") == 0) {
|
|
READ_TF_VC(no_udp);
|
|
} 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;
|
|
ReloadableConfig *config;
|
|
struct ini_ctx_st ctx;
|
|
vhost_cfg_st *vhost = NULL;
|
|
vhost_cfg_st *defvhost;
|
|
|
|
memset(&ctx, 0, sizeof(ctx));
|
|
ctx.file = file;
|
|
ctx.pool = pool;
|
|
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->static_config.dh_params_file);
|
|
replace_file_with_snapshot(
|
|
&vhost->config->ocsp_response);
|
|
for (index = 0; index < vhost->static_config.cert_size;
|
|
index++) {
|
|
replace_file_with_snapshot(
|
|
&vhost->static_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->static_config.dh_params_file);
|
|
snapshot_create(config_snapshot,
|
|
vhost->config->ocsp_response);
|
|
for (index = 0; index < vhost->static_config.cert_size;
|
|
index++) {
|
|
snapshot_create(
|
|
config_snapshot,
|
|
vhost->static_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->config;
|
|
|
|
if (vhost->auth_init == 0) {
|
|
if (vhost->auth_size == 0) {
|
|
/* Named vhosts may inherit auth from the default;
|
|
* figure_auth_funcs is skipped and auth_methods will
|
|
* be populated by vhost_inherit_static_config below. */
|
|
if (vhost->name == NULL ||
|
|
default_vhost(head)
|
|
->static_config
|
|
.auth_methods == 0) {
|
|
fprintf(stderr,
|
|
ERRSTR
|
|
"%sthe 'auth' configuration option was not specified!\n",
|
|
PREFIX_VHOST(vhost));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
} else {
|
|
figure_auth_funcs(vhost, PREFIX_VHOST(vhost),
|
|
&vhost->static_config,
|
|
vhost->auth, vhost->auth_size,
|
|
1, ctx.is_worker);
|
|
figure_auth_funcs(vhost, PREFIX_VHOST(vhost),
|
|
&vhost->static_config,
|
|
vhost->eauth,
|
|
vhost->eauth_size, 0,
|
|
ctx.is_worker);
|
|
|
|
figure_acct_funcs(vhost, PREFIX_VHOST(vhost),
|
|
&vhost->static_config,
|
|
vhost->acct, ctx.is_worker);
|
|
}
|
|
vhost->auth_init = 1;
|
|
}
|
|
|
|
if (config->auto_select_group != 0 &&
|
|
vhost->static_config.auth[0].amod != NULL &&
|
|
vhost->static_config.auth[0].amod->group_list != NULL) {
|
|
unsigned int grp_sz = 0;
|
|
|
|
vhost->static_config.auth[0].amod->group_list(
|
|
config, vhost->static_config.auth[0].additional,
|
|
&config->group_list, &grp_sz);
|
|
config->n_group_list = grp_sz;
|
|
switch (vhost->static_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 = grp_sz;
|
|
break;
|
|
case AUTH_TYPE_GSSAPI:
|
|
gssapi_auth_group_list = config->group_list;
|
|
gssapi_auth_group_list_size = grp_sz;
|
|
break;
|
|
case AUTH_TYPE_PLAIN | AUTH_TYPE_USERNAME_PASS:
|
|
plain_auth_group_list = config->group_list;
|
|
plain_auth_group_list_size = grp_sz;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (config->expose_iroutes != 0) {
|
|
load_iroutes(config);
|
|
}
|
|
|
|
if (vhost->name) {
|
|
defvhost = default_vhost(head);
|
|
vhost_inherit_static_config(vhost, defvhost);
|
|
} 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;
|
|
vhost->urlfw_size = 0;
|
|
}
|
|
#endif
|
|
if (!ctx.is_worker)
|
|
fprintf(stderr,
|
|
NOTESTR
|
|
"%ssetting '%s' as supplemental config option\n",
|
|
PREFIX_VHOST(vhost),
|
|
sup_config_name(
|
|
vhost->static_config.sup_config_type));
|
|
}
|
|
}
|
|
|
|
#define VHOST_INHERIT(member) \
|
|
({ vhost->static_config.member = defvhost->static_config.member; })
|
|
|
|
#define VHOST_INHERIT_STRDUP(member) \
|
|
({ \
|
|
if (defvhost->static_config.member != NULL) \
|
|
vhost->static_config.member = \
|
|
talloc_strdup(vhost->config, \
|
|
defvhost->static_config.member); \
|
|
})
|
|
|
|
#define VHOST_INHERIT_STRLCPY(member) \
|
|
({ \
|
|
strlcpy(vhost->static_config.member, \
|
|
defvhost->static_config.member, \
|
|
sizeof(vhost->static_config.member)); \
|
|
})
|
|
|
|
/* Inherit a string only when the named vhost did not set its own value. */
|
|
#define VHOST_INHERIT_STRDUP_IF_NULL(member) \
|
|
({ \
|
|
if (vhost->static_config.member == NULL && \
|
|
defvhost->static_config.member != NULL) \
|
|
vhost->static_config.member = talloc_strdup( \
|
|
vhost->pool, defvhost->static_config.member); \
|
|
})
|
|
|
|
/*
|
|
* Initialize per-vhost configuration by inheriting settings from default vhost.
|
|
*
|
|
* The reloadable ReloadableConfig is deep-copied from the default vhost via
|
|
* protobuf pack+unpack so all fields (including strings, repeated fields, and
|
|
* sub-messages) are inherited automatically. Permanent static_cfg_st fields
|
|
* live outside ReloadableConfig and are handled here in two groups:
|
|
*
|
|
* [scope: global (non-reloadable)] — unconditional copy; named vhosts cannot
|
|
* override these (UID, ports, socket paths, etc.).
|
|
*
|
|
* [scope: vhost (non-reloadable)] — conditional copy ("inherit if not set");
|
|
* named vhosts that provide their own value keep it. auth_ctx / acct_ctx
|
|
* are NULL at this point and will be initialised later by sec_auth_init();
|
|
* the `additional` pointer inside auth[]/acct is read-only module config
|
|
* that lives for the server's lifetime, so sharing the pointer is safe.
|
|
*
|
|
* sup_config_type is excluded: cfg_alloc_vhost always pre-sets it to
|
|
* SUP_CONFIG_FILE (non-zero), so there is no reliable sentinel to detect
|
|
* "not explicitly set". Named vhosts that need a different sup-config must
|
|
* set it explicitly.
|
|
*/
|
|
static void vhost_inherit_static_config(vhost_cfg_st *vhost,
|
|
vhost_cfg_st *defvhost)
|
|
{
|
|
unsigned int i;
|
|
|
|
/* --- [scope: global (non-reloadable)] — always copy --- */
|
|
VHOST_INHERIT(port);
|
|
VHOST_INHERIT(udp_port);
|
|
VHOST_INHERIT(uid);
|
|
VHOST_INHERIT(gid);
|
|
VHOST_INHERIT(sec_mod_scale);
|
|
VHOST_INHERIT(stats_reset_time);
|
|
VHOST_INHERIT_STRDUP(socket_file_prefix);
|
|
VHOST_INHERIT_STRDUP(occtl_socket_file);
|
|
VHOST_INHERIT_STRDUP(chroot_dir);
|
|
|
|
/* --- [scope: vhost (non-reloadable)] — inherit if not set --- */
|
|
|
|
/* TLS credentials */
|
|
if (vhost->static_config.cert_size == 0 &&
|
|
defvhost->static_config.cert_size > 0) {
|
|
vhost->static_config.cert_size =
|
|
defvhost->static_config.cert_size;
|
|
vhost->static_config.cert = talloc_array(
|
|
vhost->pool, char *, defvhost->static_config.cert_size);
|
|
for (i = 0; i < defvhost->static_config.cert_size; i++)
|
|
vhost->static_config.cert[i] = talloc_strdup(
|
|
vhost->pool, defvhost->static_config.cert[i]);
|
|
}
|
|
if (vhost->static_config.key_size == 0 &&
|
|
defvhost->static_config.key_size > 0) {
|
|
vhost->static_config.key_size =
|
|
defvhost->static_config.key_size;
|
|
vhost->static_config.key = talloc_array(
|
|
vhost->pool, char *, defvhost->static_config.key_size);
|
|
for (i = 0; i < defvhost->static_config.key_size; i++)
|
|
vhost->static_config.key[i] = talloc_strdup(
|
|
vhost->pool, defvhost->static_config.key[i]);
|
|
}
|
|
VHOST_INHERIT_STRDUP_IF_NULL(ca);
|
|
VHOST_INHERIT_STRDUP_IF_NULL(dh_params_file);
|
|
#ifdef ANYCONNECT_CLIENT_COMPAT
|
|
VHOST_INHERIT_STRDUP_IF_NULL(cert_hash);
|
|
#endif
|
|
|
|
/* PKCS#11 / TPM pins */
|
|
VHOST_INHERIT_STRDUP_IF_NULL(pin_file);
|
|
VHOST_INHERIT_STRDUP_IF_NULL(srk_pin_file);
|
|
VHOST_INHERIT_STRDUP_IF_NULL(key_pin);
|
|
VHOST_INHERIT_STRDUP_IF_NULL(srk_pin);
|
|
|
|
/* Authentication methods */
|
|
if (vhost->static_config.auth_methods == 0 &&
|
|
defvhost->static_config.auth_methods > 0) {
|
|
vhost->static_config.auth_methods =
|
|
defvhost->static_config.auth_methods;
|
|
memcpy(vhost->static_config.auth, defvhost->static_config.auth,
|
|
sizeof(vhost->static_config.auth));
|
|
}
|
|
|
|
/* Accounting */
|
|
if (vhost->static_config.acct.amod == NULL &&
|
|
defvhost->static_config.acct.amod != NULL)
|
|
vhost->static_config.acct = defvhost->static_config.acct;
|
|
}
|
|
|
|
/* sanity checks on config */
|
|
static void check_cfg(vhost_cfg_st *vhost, vhost_cfg_st *defvhost,
|
|
unsigned int silent)
|
|
{
|
|
unsigned int j, i;
|
|
ReloadableConfig *config;
|
|
|
|
assert(vhost->name == NULL || defvhost != NULL);
|
|
|
|
config = vhost->config;
|
|
|
|
if (vhost->static_config.auth[0].enabled == 0) {
|
|
fprintf(stderr,
|
|
ERRSTR "%sno authentication method was specified!\n",
|
|
PREFIX_VHOST(vhost));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (vhost->static_config.socket_file_prefix == NULL) {
|
|
/* The 'socket-file' is mandatory on main server */
|
|
fprintf(stderr, ERRSTR
|
|
"the 'socket-file' configuration option must be specified!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (vhost->static_config.port == 0) {
|
|
fprintf(stderr, ERRSTR "the tcp-port option is mandatory!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (vhost->static_config.cert_size == 0 ||
|
|
vhost->static_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->static_config.cert_size != vhost->static_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->static_config.auth[0].type & AUTH_TYPE_CERTIFICATE) &&
|
|
vhost->static_config.auth_methods == 1) {
|
|
if (config->cisco_client_compat == 0)
|
|
config->cert_req = GNUTLS_CERT_REQUIRE;
|
|
else
|
|
config->cert_req = GNUTLS_CERT_REQUEST;
|
|
config->has_cert_req = 1;
|
|
} else {
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < vhost->static_config.auth_methods; i++) {
|
|
if (vhost->static_config.auth[i].type &
|
|
AUTH_TYPE_CERTIFICATE) {
|
|
config->cert_req = GNUTLS_CERT_REQUEST;
|
|
config->has_cert_req = 1;
|
|
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->static_config.cert &&
|
|
vhost->static_config.cert_hash == NULL) {
|
|
vhost->static_config.cert_hash = calc_sha1_hash(
|
|
vhost->pool, vhost->static_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->static_config.chroot_dir != NULL) {
|
|
char path[_POSIX_PATH_MAX];
|
|
|
|
snprintf(path, sizeof(path), "%s/%s",
|
|
vhost->static_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->static_config.udp_port != 0 &&
|
|
vhost->static_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->config->priorities,
|
|
tmp);
|
|
} else {
|
|
config->priorities = talloc_asprintf(
|
|
config, "%s%s",
|
|
"NORMAL:%SERVER_PRECEDENCE:%COMPAT", tmp);
|
|
}
|
|
}
|
|
|
|
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 == NULL || config->network->name[0] == 0) {
|
|
fprintf(stderr, ERRSTR
|
|
"the 'device' configuration option must be specified!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
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->static_config.udp_listen_host == NULL) {
|
|
vhost->static_config.udp_listen_host =
|
|
vhost->static_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->n_routes; 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->n_routes = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (j = 0; j < config->network->n_no_routes; 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->n_dns; 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->static_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->static_config.sup_config_type));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
}
|
|
|
|
#define OPT_NO_CHDIR 1
|
|
#define OPT_SYSLOG_FACILITY 2
|
|
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' },
|
|
{ "syslog-facility", 1, 0, OPT_SYSLOG_FACILITY },
|
|
{ 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,
|
|
" --syslog-facility=name Syslog facility to use (default: daemon)\n");
|
|
fprintf(stderr,
|
|
" - name: daemon user auth authpriv local0..local7\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->static_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->static_config.log_level = atoi(optarg);
|
|
global_log_prio = vhost->static_config.log_level;
|
|
debug_asked = 1;
|
|
break;
|
|
case 't':
|
|
test_only = 1;
|
|
break;
|
|
case 'e':
|
|
vhost->static_config.log_stderr = 1;
|
|
break;
|
|
case 's':
|
|
vhost->static_config.syslog = 1;
|
|
break;
|
|
case OPT_NO_CHDIR:
|
|
vhost->static_config.no_chdir = 1;
|
|
break;
|
|
case OPT_SYSLOG_FACILITY: {
|
|
int fac = parse_syslog_facility(optarg);
|
|
if (fac < 0) {
|
|
fprintf(stderr,
|
|
ERRSTR "unknown syslog facility: %s\n",
|
|
optarg);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
vhost->static_config.syslog_facility = fac;
|
|
break;
|
|
}
|
|
case 'h':
|
|
usage();
|
|
exit(EXIT_SUCCESS);
|
|
case 'v':
|
|
print_version();
|
|
exit(EXIT_SUCCESS);
|
|
case 'x':
|
|
vhost->static_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->static_config.log_stderr == 0 &&
|
|
vhost->static_config.syslog == 0) {
|
|
vhost->static_config.syslog =
|
|
1; /* default if nothing specified*/
|
|
if (debug_asked)
|
|
vhost->static_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->usage_count;
|
|
talloc_steal(e, vhost->usage_count);
|
|
|
|
/* we rely on talloc doing that recursively */
|
|
talloc_steal(e, vhost->config);
|
|
vhost->config = NULL;
|
|
vhost->usage_count = NULL;
|
|
|
|
/* reset so the next reload re-inherits from the new default */
|
|
vhost->cfg_inherited = 0;
|
|
|
|
if (e->usage_count == NULL || *e->usage_count == 0) {
|
|
talloc_free(e);
|
|
} else {
|
|
list_add(&vhost->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->config);
|
|
cpos->config = NULL;
|
|
/* reset so the next reload re-inherits from the new default */
|
|
cpos->cfg_inherited = 0;
|
|
}
|
|
}
|
|
|
|
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->config);
|
|
vhost->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->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->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 static_cfg_st *static_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",
|
|
static_config->socket_file_prefix, rnd);
|
|
|
|
return secmod_socket_file_name_socket_file;
|
|
}
|