config: add syslog-facility option to allow routing logs independently

ocserv always logged to the syslog daemon(3) facility, making it
impossible to route its messages separately from other daemons.
Adds a syslog-facility config key (and --syslog-facility CLI flag)
accepting daemon/user/auth/authpriv/local0-local7; defaults to daemon.

Resolves: #691

Signed-off-by: Nikos Mavrogiannopoulos <n.mavrogiannopoulos@gmail.com>
This commit is contained in:
Nikos Mavrogiannopoulos
2026-06-03 19:52:36 +02:00
parent 708f42a455
commit 0f5c628048
10 changed files with 299 additions and 14 deletions
+1
View File
@@ -1,4 +1,5 @@
* Version 1.5.0 (unreleased)
- Added `syslog-facility` option to log to specified syslog facility (#691)
- Fixed worker hang when a client disappears silently (#638)
- Removed the 'cgroup' configuration option (used for cgroups v1).
Administrators should use systemd resource controls instead; see
+5
View File
@@ -67,6 +67,11 @@ server.
* **-s, --syslog**:
Log to syslog (default).
* **--syslog-facility**=_NAME_:
Set the syslog facility used when logging to syslog. _NAME_ must be one of:
`daemon` (default), `user`, `auth`, `authpriv`, `local0``local7`.
This option is also available as the configuration file key **syslog-facility**.
* **--no-chdir**:
Do not perform a chdir on daemonize.
+7
View File
@@ -559,6 +559,13 @@ pid-file = /var/run/ocserv.pid
# [scope: global (non-reloadable)]
log-level = 2
# Syslog facility to use when logging to syslog.
# Allowed values: daemon (default), user, auth, authpriv, local0..local7.
# Choosing a local facility lets you direct ocserv messages to a dedicated
# log file via rsyslog/syslog-ng without affecting other daemon logs.
# [scope: global (non-reloadable)]
#syslog-facility = daemon
# Set the protocol-defined priority (SO_PRIORITY) for packets to
# be sent. That is a number from 0 to 6 with 0 being the lowest
# priority. Alternatively this can be used to set the IP Type-
+64 -7
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2013-2023 Nikos Mavrogiannopoulos
* 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
@@ -62,6 +62,31 @@
#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] = "";
@@ -749,6 +774,7 @@ static void apply_default_conf(vhost_cfg_st *vhost, unsigned int reload)
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;
@@ -1119,6 +1145,14 @@ static int cfg_ini_handler(void *_ctx, const char *section, const char *name,
} 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;
}
@@ -2182,13 +2216,21 @@ static void check_cfg(vhost_cfg_st *vhost, vhost_cfg_st *defvhost,
}
#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' }, { NULL, 0, 0, 0 }
{ "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)
@@ -2221,6 +2263,10 @@ static void usage(void)
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");
@@ -2276,6 +2322,17 @@ int cmd_parser(void *pool, int argc, char **argv, struct list_head *head,
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);
+3 -3
View File
@@ -1601,13 +1601,13 @@ int main(int argc, char *argv[])
if (GETSCONFIG(s)->log_stderr && GETSCONFIG(s)->syslog)
flags |= LOG_PERROR;
#endif
openlog("ocserv", flags, LOG_DAEMON);
openlog("ocserv", flags, GETSCONFIG(s)->syslog_facility);
syslog_open = 1;
}
#ifdef HAVE_LIBWRAP
allow_severity = LOG_DAEMON | LOG_INFO;
deny_severity = LOG_DAEMON | LOG_WARNING;
allow_severity = GETSCONFIG(s)->syslog_facility | LOG_INFO;
deny_severity = GETSCONFIG(s)->syslog_facility | LOG_WARNING;
#endif
if (GETSCONFIG(s)->foreground == 0) {
+1
View File
@@ -250,6 +250,7 @@ struct static_cfg_st {
unsigned int log_level; /* [scope: global (non-reloadable)] */
unsigned int log_stderr; /* [scope: global (non-reloadable)] */
unsigned int syslog; /* [scope: global (non-reloadable)] */
int syslog_facility; /* [scope: global (non-reloadable)] */
unsigned int pr_dumpable; /* [scope: global (non-reloadable)] */
+3 -3
View File
@@ -163,13 +163,13 @@ int main(int argc, char *argv[])
if (GETSCONFIG(s)->log_stderr)
flags |= LOG_PERROR;
#endif
openlog("ocserv", flags, LOG_DAEMON);
openlog("ocserv", flags, GETSCONFIG(s)->syslog_facility);
syslog_open = 1;
}
#ifdef HAVE_LIBWRAP
allow_severity = LOG_DAEMON | LOG_INFO;
deny_severity = LOG_DAEMON | LOG_WARNING;
allow_severity = GETSCONFIG(s)->syslog_facility | LOG_INFO;
deny_severity = GETSCONFIG(s)->syslog_facility | LOG_WARNING;
#endif
#ifdef HAVE_GSSAPI
+9 -1
View File
@@ -113,6 +113,14 @@ endforeach
# --------------------------------------------------------------------------
if have_cwrap
# syslog-shim: LD_PRELOAD library used by test-syslog-facility to intercept
# openlog(3) and record the facility argument without a real syslog daemon.
syslog_shim = shared_library('syslog-shim', 'syslog-shim.c',
dependencies: [meson.get_compiler('c').find_library('dl', required: false)],
install: false,
)
test_env.set('SYSLOG_SHIM', syslog_shim.full_path())
cwrap_scripts = [
'test-pass', 'test-pass-cert', 'test-pass-cert-rfc822name',
'test-cert', 'test-group-pass', 'test-pass-group-cert',
@@ -126,7 +134,7 @@ if have_cwrap
'drain-server', 'drain-server-fail',
'test-ignore-querystring-of-post',
'test-group-cert', 'test-fork', 'test-pass-svc', 'test-cert-svc',
'test-secmod-kill',
'test-secmod-kill', 'test-syslog-facility',
]
if have_cwrap_all
+83
View File
@@ -0,0 +1,83 @@
/*
* Copyright (C) 2026 Nikos Mavrogiannopoulos
*
* 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.
*
* LD_PRELOAD shim that intercepts openlog(3) and appends the facility name
* (one per line) to the file named by $SYSLOG_SHIM_OUT.
*
* Used by test-syslog-facility to verify that ocserv passes the configured
* syslog facility to openlog() without requiring a real syslog daemon.
* Multiple processes (main + worker) call openlog(); the test uses grep to
* confirm the expected facility appears at least once.
*/
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <syslog.h>
static const char *facility_name(int facility)
{
switch (facility) {
case LOG_DAEMON:
return "daemon";
case LOG_USER:
return "user";
case LOG_AUTH:
return "auth";
case LOG_LOCAL0:
return "local0";
case LOG_LOCAL1:
return "local1";
case LOG_LOCAL2:
return "local2";
case LOG_LOCAL3:
return "local3";
case LOG_LOCAL4:
return "local4";
case LOG_LOCAL5:
return "local5";
case LOG_LOCAL6:
return "local6";
case LOG_LOCAL7:
return "local7";
#ifdef LOG_AUTHPRIV
case LOG_AUTHPRIV:
return "authpriv";
#endif
default:
return "unknown";
}
}
void openlog(const char *ident, int logopt, int facility)
{
static void (*real_openlog)(const char *, int, int);
const char *out;
FILE *f;
out = getenv("SYSLOG_SHIM_OUT");
if (out != NULL) {
f = fopen(out, "a");
if (f != NULL) {
fprintf(f, "%s\n", facility_name(facility));
fclose(f);
}
}
if (real_openlog == NULL)
real_openlog = dlsym(RTLD_NEXT, "openlog");
if (real_openlog != NULL)
real_openlog(ident, logopt, facility);
}
+123
View File
@@ -0,0 +1,123 @@
#!/bin/sh
#
# Copyright (C) 2026 Nikos Mavrogiannopoulos
#
# This file is part of ocserv.
#
# ocserv 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.
#
# ocserv 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.
#
# Test: syslog-facility config option (Relates: #691)
#
# Starts ocserv under an LD_PRELOAD shim that intercepts openlog(3) and
# records the facility argument to a temp file. Verifies that:
# - the default facility is "daemon" when syslog-facility is not set
# - setting syslog-facility = auth changes the recorded facility to "auth"
#
# Requires cwrap (socket_wrapper + uid_wrapper) to run without root.
SERV="${SERV:-../src/ocserv}"
srcdir=${srcdir:-.}
NO_NEED_ROOT=1
. "$(dirname "$0")/common.sh"
if test -z "${SYSLOG_SHIM}" || ! test -f "${SYSLOG_SHIM}"; then
echo "SYSLOG_SHIM not set or not built; skipping"
exit 77
fi
eval "${GETPORT}"
SHIM_OUT="syslog-shim-out.$$.tmp"
CONFIG_FILE="syslog-facility.$$.conf.tmp"
SERV_LOG="syslog-facility-serv.$$.log"
finish() {
set +e
test -n "${PID}" && kill "${PID}" >/dev/null 2>&1
test -n "${PID}" && wait "${PID}" 2>/dev/null
rm -f "${SHIM_OUT}" "${CONFIG_FILE}" "${SERV_LOG}"
test -n "${SOCKDIR}" && rm -rf "${SOCKDIR}"
}
trap finish EXIT
write_config() {
facility="$1"
cat >"${CONFIG_FILE}" <<EOF
auth = "plain[${srcdir}/data/test1.passwd]"
tcp-port = ${PORT}
socket-file = ./ocserv-syslog-test-$$.sock
server-cert = ${srcdir}/certs/server-cert.pem
server-key = ${srcdir}/certs/server-key.pem
device = vpns
ipv4-network = 192.168.100.0
ipv4-netmask = 255.255.255.0
run-as-user = $(id -un)
run-as-group = $(id -gn)
log-level = 0
EOF
if test -n "${facility}"; then
echo "syslog-facility = ${facility}" >>"${CONFIG_FILE}"
fi
}
start_server() {
rm -f "${SHIM_OUT}" "${SERV_LOG}"
LD_PRELOAD="libsocket_wrapper.so:libuid_wrapper.so:${SYSLOG_SHIM}" \
UID_WRAPPER=1 UID_WRAPPER_ROOT=1 \
SYSLOG_SHIM_OUT="${SHIM_OUT}" \
${SERV} -f -c "${CONFIG_FILE}" \
>"${SERV_LOG}" 2>&1 &
PID=$!
# Give the main process time to call openlog() during startup
sleep 2
}
check_facility() {
expected="$1"
label="$2"
if ! test -f "${SHIM_OUT}"; then
echo "FAIL ${label}: openlog() was not called (server did not start?)"
echo "--- server log ---"
cat "${SERV_LOG}" 2>/dev/null || echo "(empty)"
echo "---"
exit 1
fi
if ! grep -q "^${expected}$" "${SHIM_OUT}"; then
echo "FAIL ${label}: expected facility '${expected}', got:"
cat "${SHIM_OUT}"
exit 1
fi
echo "PASS ${label}: facility = ${expected}"
}
stop_server() {
test -n "${PID}" && kill "${PID}" >/dev/null 2>&1
test -n "${PID}" && wait "${PID}" 2>/dev/null
PID=
}
# ---- Test 1: default facility is "daemon" ----
echo "Testing default syslog facility..."
write_config ""
start_server
check_facility "daemon" "default-facility"
stop_server
# ---- Test 2: syslog-facility = auth ----
echo "Testing syslog-facility = auth..."
write_config "auth"
start_server
check_facility "auth" "auth-facility"
stop_server
echo "test-syslog-facility: all checks passed"
exit 0