mirror of
https://gitlab.com/openconnect/ocserv.git
synced 2026-08-09 09:51:49 +08:00
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:
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
Executable
+123
@@ -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
|
||||
Reference in New Issue
Block a user