git: 2567168dc498 - main - syslogd: Refresh configuration using libcasper

From: Jake Freeland <jfree_at_FreeBSD.org>
Date: Wed, 27 Nov 2024 22:27:11 UTC
The branch main has been updated by jfree:

URL: https://cgit.FreeBSD.org/src/commit/?id=2567168dc49869475db79176bf5f6ae9761bc75a

commit 2567168dc49869475db79176bf5f6ae9761bc75a
Author:     Jake Freeland <jfree@FreeBSD.org>
AuthorDate: 2024-11-27 22:25:12 +0000
Commit:     Jake Freeland <jfree@FreeBSD.org>
CommitDate: 2024-11-27 22:25:12 +0000

    syslogd: Refresh configuration using libcasper
    
    When a SIGHUP signal is sent to syslogd, the configuration is reparsed,
    leading to new resource acquisition.
    
    If syslogd is running in capability mode and a SIGHUP is received, new
    resources cannot be acquired. To mitigate this issue, libcasper is used
    to parse the configuration.
    
    The libcasper process runs outside of capability mode and is capable of
    parsing syslogd's configuration and obtaining new resources. These
    resources are then sent to the syslogd process via nvlist.
    
    Reviewed by:    markj
    Differential Revision:  https://reviews.freebsd.org/D41464
---
 usr.sbin/syslogd/Makefile             |   3 +-
 usr.sbin/syslogd/syslogd.c            | 234 ++++++++++++---------------
 usr.sbin/syslogd/syslogd.h            |  15 +-
 usr.sbin/syslogd/syslogd_cap.c        |   9 +-
 usr.sbin/syslogd/syslogd_cap.h        |  14 ++
 usr.sbin/syslogd/syslogd_cap_config.c | 296 ++++++++++++++++++++++++++++++++++
 6 files changed, 436 insertions(+), 135 deletions(-)

diff --git a/usr.sbin/syslogd/Makefile b/usr.sbin/syslogd/Makefile
index e9e7455ed2cc..4088b2e03651 100644
--- a/usr.sbin/syslogd/Makefile
+++ b/usr.sbin/syslogd/Makefile
@@ -14,7 +14,8 @@ SYSLOGD_DPACAKGE=	syslogd
 LIBADD=	util
 
 .if ${MK_CASPER} != "no"
-SRCS+=	syslogd_cap.c
+SRCS+=	syslogd_cap.c \
+	syslogd_cap_config.c
 CFLAGS+= -DWITH_CASPER
 LIBADD+= cap_net casper nv
 .endif
diff --git a/usr.sbin/syslogd/syslogd.c b/usr.sbin/syslogd/syslogd.c
index 8937aa72804d..0c05884cc17d 100644
--- a/usr.sbin/syslogd/syslogd.c
+++ b/usr.sbin/syslogd/syslogd.c
@@ -131,7 +131,7 @@
 #include "syslogd_cap.h"
 #include "ttymsg.h"
 
-static const char *ConfFile = _PATH_LOGCONF;
+const char *ConfFile = _PATH_LOGCONF;
 static const char *PidFile = _PATH_LOGPID;
 static const char include_str[] = "include";
 static const char include_ext[] = ".conf";
@@ -276,7 +276,7 @@ static int	nulldesc;	/* /dev/null descriptor */
 static bool	Debug;		/* debug flag */
 static bool	Foreground = false; /* Run in foreground, instead of daemonizing */
 static bool	resolve = true;	/* resolve hostname */
-static char	LocalHostName[MAXHOSTNAMELEN];	/* our hostname */
+char		LocalHostName[MAXHOSTNAMELEN];	/* our hostname */
 static const char *LocalDomain;	/* our local domain name */
 static bool	Initialized;	/* set when we have initialized ourselves */
 static int	MarkInterval = 20 * 60;	/* interval between marks in seconds */
@@ -313,7 +313,7 @@ struct iovlist;
 static bool	allowaddr(char *);
 static void	addpeer(const char *, const char *, mode_t);
 static void	addsock(const char *, const char *, mode_t);
-static void	cfline(const char *, const char *, const char *, const char *);
+static nvlist_t *cfline(const char *, const char *, const char *, const char *);
 static const char *cvthname(struct sockaddr *);
 static void	deadq_enter(int);
 static void	deadq_remove(struct deadq_entry *);
@@ -325,7 +325,6 @@ static void	fprintlog_first(struct filed *, const char *, const char *,
 static void	fprintlog_write(struct filed *, struct iovlist *, int);
 static void	fprintlog_successive(struct filed *, int);
 static void	init(bool);
-static void	logerror(const char *);
 static void	logmsg(int, const struct logtime *, const char *, const char *,
     const char *, const char *, const char *, const char *, int);
 static void	markit(void);
@@ -335,7 +334,7 @@ static int	socklist_recv_sock(struct socklist *);
 static int	skip_message(const char *, const char *, int);
 static int	evaluate_prop_filter(const struct prop_filter *filter,
     const char *value);
-static struct prop_filter *prop_filter_compile(const char *);
+static nvlist_t *prop_filter_compile(const char *);
 static void	parsemsg(const char *, char *);
 static void	printsys(char *);
 static int	p_open(const char *, pid_t *);
@@ -1387,7 +1386,7 @@ skip_message(const char *name, const char *spec, int checkcase)
 	int exclude = 0;
 	/* Behaviour on explicit match */
 
-	if (spec == NULL)
+	if (spec == NULL || *spec == '\0')
 		return (0);
 	switch (*spec) {
 	case '-':
@@ -1445,7 +1444,7 @@ evaluate_prop_filter(const struct prop_filter *filter, const char *value)
 
 	/* a shortcut for equal with different length is always false */
 	if (filter->cmp_type == FILT_CMP_EQUAL &&
-	    valuelen != filter->pflt_strlen)
+	    valuelen != strlen(filter->pflt_strval))
 		return (!exclude);
 
 	if (filter->cmp_flags & FILT_FLAG_ICASE)
@@ -2178,7 +2177,7 @@ cvthname(struct sockaddr *f)
 /*
  * Print syslogd errors some place.
  */
-static void
+void
 logerror(const char *msg)
 {
 	char buf[512];
@@ -2255,8 +2254,8 @@ configfiles(const struct dirent *dp)
 	return (1);
 }
 
-static void
-parseconfigfile(FILE *cf, bool allow_includes)
+static nvlist_t *
+parseconfigfile(FILE *cf, bool allow_includes, nvlist_t *nvl_conf)
 {
 	FILE *cf2;
 	struct dirent **ent;
@@ -2316,7 +2315,7 @@ parseconfigfile(FILE *cf, bool allow_includes)
 				if (cf2 == NULL)
 					continue;
 				dprintf("reading %s\n", file);
-				parseconfigfile(cf2, false);
+				parseconfigfile(cf2, false, nvl_conf);
 				fclose(cf2);
 			}
 			free(ent);
@@ -2386,29 +2385,55 @@ parseconfigfile(FILE *cf, bool allow_includes)
 		}
 		for (i = strlen(cline) - 1; i >= 0 && isspace(cline[i]); i--)
 			cline[i] = '\0';
-		cfline(cline, prog, host, pfilter);
+		nvlist_append_nvlist_array(nvl_conf, "filed_list",
+		    cfline(cline, prog, host, pfilter));
+
 	}
+	return (nvl_conf);
 }
 
-static void
+nvlist_t *
 readconfigfile(const char *path)
 {
 	FILE *cf;
+	nvlist_t *nvl_conf = nvlist_create(0);
 
 	if ((cf = fopen(path, "r")) != NULL) {
-		parseconfigfile(cf, true);
+		nvl_conf = parseconfigfile(cf, true, nvl_conf);
 		(void)fclose(cf);
 	} else {
-		dprintf("cannot open %s\n", ConfFile);
-		cfline("*.ERR\t/dev/console", "*", "*", "*");
-		cfline("*.PANIC\t*", "*", "*", "*");
+		dprintf("cannot open %s\n", path);
+		nvlist_append_nvlist_array(nvl_conf, "filed_list",
+		    cfline("*.ERR\t/dev/console", "*", "*", "*"));
+		nvlist_append_nvlist_array(nvl_conf, "filed_list",
+		    cfline("*.PANIC\t*", "*", "*", "*"));
+	}
+	return (nvl_conf);
+}
+
+static void
+fill_flist(nvlist_t *nvl_conf)
+{
+	const nvlist_t * const *filed_list;
+	size_t nfileds;
+
+	if (!nvlist_exists_nvlist_array(nvl_conf, "filed_list"))
+		return;
+	filed_list = nvlist_get_nvlist_array(nvl_conf, "filed_list",
+	    &nfileds);
+	for (size_t i = 0; i < nfileds; ++i) {
+		struct filed *f;
+
+		f = nvlist_to_filed(filed_list[i]);
+		STAILQ_INSERT_TAIL(&fhead, f, next);
 	}
+	nvlist_destroy(nvl_conf);
 }
 
 /*
  * Close all open log files.
  */
-static void
+void
 closelogfiles(void)
 {
 	struct filed *f;
@@ -2433,14 +2458,12 @@ closelogfiles(void)
 			break;
 		}
 
-		free(f->f_program);
-		free(f->f_host);
 		if (f->f_prop_filter) {
 			switch (f->f_prop_filter->cmp_type) {
 			case FILT_CMP_REGEX:
 				regfree(f->f_prop_filter->pflt_re);
 				free(f->f_prop_filter->pflt_re);
-				break;
+				/* FALLTHROUGH */
 			case FILT_CMP_CONTAINS:
 			case FILT_CMP_EQUAL:
 			case FILT_CMP_STARTS:
@@ -2504,7 +2527,7 @@ init(bool reload)
 
 	Initialized = false;
 	closelogfiles();
-	readconfigfile(ConfFile);
+	fill_flist(readconfigfile(ConfFile));
 	Initialized = true;
 
 	if (Debug) {
@@ -2561,7 +2584,7 @@ init(bool reload)
 			default:
 				break;
 			}
-			if (f->f_program)
+			if (*f->f_program != '\0')
 				printf(" (%s)", f->f_program);
 			printf("\n");
 		}
@@ -2597,29 +2620,18 @@ init(bool reload)
 /*
  * Compile property-based filter.
  */
-static struct prop_filter *
+static nvlist_t *
 prop_filter_compile(const char *cfilter)
 {
-	struct prop_filter *pfilter;
+	nvlist_t *nvl_pfilter;
+	struct prop_filter pfilter = { };
 	char *filter, *filter_endpos, *filter_begpos, *p;
 	char **ap, *argv[2] = {NULL, NULL};
-	int re_flags = REG_NOSUB;
 	int escaped;
 
-	pfilter = calloc(1, sizeof(*pfilter));
-	if (pfilter == NULL) {
-		logerror("pfilter calloc");
-		exit(1);
-	}
-	if (*cfilter == '*') {
-		pfilter->prop_type = FILT_PROP_NOOP;
-		return (pfilter);
-	}
 	filter = strdup(cfilter);
-	if (filter == NULL) {
-		logerror("strdup");
-		exit(1);
-	}
+	if (filter == NULL)
+		err(1, "strdup");
 	filter_begpos = filter;
 
 	/*
@@ -2640,48 +2652,48 @@ prop_filter_compile(const char *cfilter)
 	}
 
 	if (argv[0] == NULL || argv[1] == NULL) {
-		logerror("filter parse error");
+		dprintf("filter parse error");
 		goto error;
 	}
 
 	/* fill in prop_type */
 	if (strcasecmp(argv[0], "msg") == 0)
-		pfilter->prop_type = FILT_PROP_MSG;
+		pfilter.prop_type = FILT_PROP_MSG;
 	else if (strcasecmp(argv[0], "hostname") == 0)
-		pfilter->prop_type = FILT_PROP_HOSTNAME;
+		pfilter.prop_type = FILT_PROP_HOSTNAME;
 	else if (strcasecmp(argv[0], "source") == 0)
-		pfilter->prop_type = FILT_PROP_HOSTNAME;
+		pfilter.prop_type = FILT_PROP_HOSTNAME;
 	else if (strcasecmp(argv[0], "programname") == 0)
-		pfilter->prop_type = FILT_PROP_PROGNAME;
+		pfilter.prop_type = FILT_PROP_PROGNAME;
 	else {
-		logerror("unknown property");
+		dprintf("unknown property");
 		goto error;
 	}
 
 	/* full in cmp_flags (i.e. !contains, icase_regex, etc.) */
 	if (*argv[1] == '!') {
-		pfilter->cmp_flags |= FILT_FLAG_EXCLUDE;
+		pfilter.cmp_flags |= FILT_FLAG_EXCLUDE;
 		argv[1]++;
 	}
 	if (strncasecmp(argv[1], "icase_", (sizeof("icase_") - 1)) == 0) {
-		pfilter->cmp_flags |= FILT_FLAG_ICASE;
+		pfilter.cmp_flags |= FILT_FLAG_ICASE;
 		argv[1] += sizeof("icase_") - 1;
 	}
 
 	/* fill in cmp_type */
 	if (strcasecmp(argv[1], "contains") == 0)
-		pfilter->cmp_type = FILT_CMP_CONTAINS;
+		pfilter.cmp_type = FILT_CMP_CONTAINS;
 	else if (strcasecmp(argv[1], "isequal") == 0)
-		pfilter->cmp_type = FILT_CMP_EQUAL;
+		pfilter.cmp_type = FILT_CMP_EQUAL;
 	else if (strcasecmp(argv[1], "startswith") == 0)
-		pfilter->cmp_type = FILT_CMP_STARTS;
+		pfilter.cmp_type = FILT_CMP_STARTS;
 	else if (strcasecmp(argv[1], "regex") == 0)
-		pfilter->cmp_type = FILT_CMP_REGEX;
+		pfilter.cmp_type = FILT_CMP_REGEX;
 	else if (strcasecmp(argv[1], "ereregex") == 0) {
-		pfilter->cmp_type = FILT_CMP_REGEX;
-		re_flags |= REG_EXTENDED;
+		pfilter.cmp_type = FILT_CMP_REGEX;
+		pfilter.cmp_flags |= REG_EXTENDED;
 	} else {
-		logerror("unknown cmp function");
+		dprintf("unknown cmp function");
 		goto error;
 	}
 
@@ -2693,7 +2705,7 @@ prop_filter_compile(const char *cfilter)
 	/* remove leading whitespace and check for '"' next character  */
 	filter += strspn(filter, ", \t\n");
 	if (*filter != '"' || strlen(filter) < 3) {
-		logerror("property value parse error");
+		dprintf("property value parse error");
 		goto error;
 	}
 	filter++;
@@ -2725,33 +2737,18 @@ prop_filter_compile(const char *cfilter)
 
 	/* We should not have anything but whitespace left after closing '"' */
 	if (*p != '\0' && strspn(p, " \t\n") != strlen(p)) {
-		logerror("property value parse error");
+		dprintf("property value parse error");
 		goto error;
 	}
 
-	if (pfilter->cmp_type == FILT_CMP_REGEX) {
-		pfilter->pflt_re = calloc(1, sizeof(*pfilter->pflt_re));
-		if (pfilter->pflt_re == NULL) {
-			logerror("RE calloc() error");
-			goto error;
-		}
-		if (pfilter->cmp_flags & FILT_FLAG_ICASE)
-			re_flags |= REG_ICASE;
-		if (regcomp(pfilter->pflt_re, filter, re_flags) != 0) {
-			logerror("RE compilation error");
-			goto error;
-		}
-	} else {
-		pfilter->pflt_strval = strdup(filter);
-		pfilter->pflt_strlen = strlen(filter);
-	}
+	pfilter.pflt_strval = filter;
+	/* An nvlist is heap allocated heap here. */
+	nvl_pfilter = prop_filter_to_nvlist(&pfilter);
 
 	free(filter_begpos);
-	return (pfilter);
+	return (nvl_pfilter);
 error:
 	free(filter_begpos);
-	free(pfilter->pflt_re);
-	free(pfilter);
 	return (NULL);
 }
 
@@ -2760,7 +2757,7 @@ parse_selector(const char *p, struct filed *f)
 {
 	int i, pri;
 	int pri_done = 0, pri_cmp = 0, pri_invert = 0;
-	char *bp, buf[LINE_MAX], ebuf[100];
+	char *bp, buf[LINE_MAX];
 	const char *q;
 
 	/* find the end of this facility name list */
@@ -2812,10 +2809,7 @@ parse_selector(const char *p, struct filed *f)
 
 		pri = decode(buf, prioritynames);
 		if (pri < 0) {
-			errno = 0;
-			(void)snprintf(ebuf, sizeof(ebuf),
-			    "unknown priority name \"%s\"", buf);
-			logerror(ebuf);
+			dprintf("unknown priority name \"%s\"", buf);
 			free(f);
 			return (NULL);
 		}
@@ -2839,11 +2833,7 @@ parse_selector(const char *p, struct filed *f)
 		} else {
 			i = decode(buf, facilitynames);
 			if (i < 0) {
-				errno = 0;
-				(void)snprintf(ebuf, sizeof(ebuf),
-				    "unknown facility name \"%s\"",
-				    buf);
-				logerror(ebuf);
+				dprintf("unknown facility name \"%s\"", buf);
 				free(f);
 				return (NULL);
 			}
@@ -2870,6 +2860,7 @@ parse_action(const char *p, struct filed *f)
 	} else
 		syncfile = true;
 
+	f->f_file = -1;
 	switch (*p) {
 	case '@':
 		{
@@ -2910,7 +2901,7 @@ parse_action(const char *p, struct filed *f)
 		};
 		error = getaddrinfo(f->f_hname, p ? p : "syslog", &hints, &res);
 		if (error) {
-			logerror(gai_strerror(error));
+			dprintf("%s\n", gai_strerror(error));
 			break;
 		}
 		f->f_addr = res;
@@ -2920,7 +2911,7 @@ parse_action(const char *p, struct filed *f)
 	case '/':
 		if ((f->f_file = open(p, logflags, 0600)) < 0) {
 			f->f_type = F_UNUSED;
-			logerror(p);
+			dprintf("%s\n", p);
 			break;
 		}
 		if (syncfile)
@@ -2969,74 +2960,59 @@ parse_action(const char *p, struct filed *f)
 /*
  * Crack a configuration file line
  */
-static void
+static nvlist_t *
 cfline(const char *line, const char *prog, const char *host,
     const char *pfilter)
 {
-	struct filed *f;
+	nvlist_t *nvl_filed;
+	struct filed f = { };
 	const char *p;
 
 	dprintf("cfline(\"%s\", f, \"%s\", \"%s\", \"%s\")\n", line, prog,
 	    host, pfilter);
 
-	f = calloc(1, sizeof(*f));
-	if (f == NULL) {
-		logerror("malloc");
-		exit(1);
-	}
-	errno = 0;	/* keep strerror() stuff out of logerror messages */
-
 	for (int i = 0; i <= LOG_NFACILITIES; i++)
-		f->f_pmask[i] = INTERNAL_NOPRI;
+		f.f_pmask[i] = INTERNAL_NOPRI;
 
 	/* save hostname if any */
-	if (host && *host == '*')
-		host = NULL;
-	if (host) {
+	if (host != NULL && *host != '*') {
 		int hl;
 
-		f->f_host = strdup(host);
-		if (f->f_host == NULL) {
-			logerror("strdup");
-			exit(1);
-		}
-		hl = strlen(f->f_host);
-		if (hl > 0 && f->f_host[hl-1] == '.')
-			f->f_host[--hl] = '\0';
+		strlcpy(f.f_host, host, sizeof(f.f_host));
+		hl = strlen(f.f_host);
+		if (hl > 0 && f.f_host[hl-1] == '.')
+			f.f_host[--hl] = '\0';
 		/* RFC 5424 prefers logging FQDNs. */
 		if (RFC3164OutputFormat)
-			trimdomain(f->f_host, hl);
+			trimdomain(f.f_host, hl);
 	}
 
 	/* save program name if any */
-	if (prog && *prog == '*')
-		prog = NULL;
-	if (prog) {
-		f->f_program = strdup(prog);
-		if (f->f_program == NULL) {
-			logerror("strdup");
-			exit(1);
-		}
-	}
-
-	if (pfilter) {
-		f->f_prop_filter = prop_filter_compile(pfilter);
-		if (f->f_prop_filter == NULL) {
-			logerror("filter compile error");
-			exit(1);
-		}
-	}
+	if (prog != NULL && *prog != '*')
+		strlcpy(f.f_program, prog, sizeof(f.f_program));
 
 	/* scan through the list of selectors */
 	for (p = line; *p != '\0' && *p != '\t' && *p != ' ';)
-		p = parse_selector(p, f);
+		p = parse_selector(p, &f);
 
 	/* skip to action part */
 	while (*p == '\t' || *p == ' ')
 		p++;
-	parse_action(p, f);
+	parse_action(p, &f);
+
+	/* An nvlist is heap allocated heap here. */
+	nvl_filed = filed_to_nvlist(&f);
+
+	if (pfilter && *pfilter != '*') {
+		nvlist_t *nvl_pfilter;
+
+		nvl_pfilter = prop_filter_compile(pfilter);
+		if (nvl_pfilter == NULL)
+			err(1, "filter compile error");
+		nvlist_add_nvlist(nvl_filed, "f_prop_filter", nvl_pfilter);
+	}
 
-	STAILQ_INSERT_TAIL(&fhead, f, next);
+	return (nvl_filed);
 }
 
 /*
diff --git a/usr.sbin/syslogd/syslogd.h b/usr.sbin/syslogd/syslogd.h
index 012338d2c7c1..16de03590ada 100644
--- a/usr.sbin/syslogd/syslogd.h
+++ b/usr.sbin/syslogd/syslogd.h
@@ -64,6 +64,7 @@
 #define _SYSLOGD_H_
 
 #include <sys/param.h>
+#include <sys/nv.h>
 #include <sys/queue.h>
 #include <sys/time.h>
 
@@ -71,6 +72,8 @@
 #include <sys/syslog.h>
 
 #include <regex.h>
+#include <stdbool.h>
+#include <stdio.h>
 
 #define	MAXLINE		8192		/* maximum line length */
 #define	MAXSVLINE	MAXLINE		/* maximum saved line length */
@@ -107,7 +110,6 @@ struct prop_filter {
 #define	FILT_FLAG_EXTENDED	(1 << 1)
 #define	FILT_FLAG_ICASE		(1 << 2)
 	char *pflt_strval;
-	size_t	pflt_strlen;
 	regex_t *pflt_re;
 };
 
@@ -132,8 +134,8 @@ struct filed {
 	enum f_type f_type;
 
 	/* Used for filtering. */
-	char	*f_host;			/* host from which to recd. */
-	char	*f_program;			/* program this applies to */
+	char	f_host[MAXHOSTNAMELEN];		/* host from which to recd. */
+	char	f_program[MAXPATHLEN];		/* program this applies to */
 	struct prop_filter *f_prop_filter;	/* property-based filter */
 	u_char	f_pmask[LOG_NFACILITIES+1];	/* priority mask */
 	u_char	f_pcmp[LOG_NFACILITIES+1];	/* compare priority */
@@ -170,4 +172,11 @@ struct filed {
 	STAILQ_ENTRY(filed) next;		/* next in linked list */
 };
 
+extern const char *ConfFile;
+extern char LocalHostName[MAXHOSTNAMELEN];
+
+void closelogfiles(void);
+void logerror(const char *);
+nvlist_t *readconfigfile(const char *);
+
 #endif /* !_SYSLOGD_H_ */
diff --git a/usr.sbin/syslogd/syslogd_cap.c b/usr.sbin/syslogd/syslogd_cap.c
index 71fc89854c58..6f64ee8878bf 100644
--- a/usr.sbin/syslogd/syslogd_cap.c
+++ b/usr.sbin/syslogd/syslogd_cap.c
@@ -32,6 +32,7 @@
 #include <sys/socket.h>
 
 #include <libcasper.h>
+#include <string.h>
 
 #include <casper/cap_net.h>
 
@@ -39,10 +40,14 @@
 
 /* This is where libcasper receives commands via nvlist. */
 static int
-casper_command(const char *cmd __unused, const nvlist_t *limits __unused,
-    nvlist_t *nvlin __unused, nvlist_t *nvlout __unused)
+casper_command(const char *cmd, const nvlist_t *limits __unused,
+    nvlist_t *nvlin, nvlist_t *nvlout)
 {
 	int error = EINVAL;
+
+	if (strcmp(cmd, "readconfigfile") == 0)
+		error = casper_readconfigfile(nvlin, nvlout);
+
 	return (error);
 }
 
diff --git a/usr.sbin/syslogd/syslogd_cap.h b/usr.sbin/syslogd/syslogd_cap.h
index fdfc969b3d0f..24b448ff2352 100644
--- a/usr.sbin/syslogd/syslogd_cap.h
+++ b/usr.sbin/syslogd/syslogd_cap.h
@@ -47,6 +47,20 @@
 
 #include "syslogd.h"
 
+nvlist_t *cap_readconfigfile(cap_channel_t *, const char *);
+int casper_readconfigfile(nvlist_t *, nvlist_t *);
+
+nvlist_t *filed_to_nvlist(const struct filed *);
+nvlist_t *prop_filter_to_nvlist(const struct prop_filter *pfilter);
+
+struct filed *nvlist_to_filed(const nvlist_t *);
+struct prop_filter *nvlist_to_prop_filter(const nvlist_t *nvl_prop_filter);
+
+#else /* !WITH_CASPER */
+
+#define	cap_readconfigfile(chan, cf) \
+	readconfigfile(cf)
+
 #endif /* WITH_CASPER */
 
 #endif /* !_SYSLOGD_CAP_H_ */
diff --git a/usr.sbin/syslogd/syslogd_cap_config.c b/usr.sbin/syslogd/syslogd_cap_config.c
new file mode 100644
index 000000000000..366c035925d6
--- /dev/null
+++ b/usr.sbin/syslogd/syslogd_cap_config.c
@@ -0,0 +1,296 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2023 The FreeBSD Foundation
+ *
+ * This software was developed by Jake Freeland <jfree@FreeBSD.org>
+ * under sponsorship from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <assert.h>
+#include <err.h>
+#include <libcasper.h>
+#include <netdb.h>
+#include <string.h>
+
+#include <casper/cap_net.h>
+
+#include "syslogd_cap.h"
+
+/*
+ * Convert the given prop_filter structure into an nvlist.
+ * Return a heap allocated pointer to the resulting nvlist.
+ */
+nvlist_t *
+prop_filter_to_nvlist(const struct prop_filter *pfilter)
+{
+	nvlist_t *nvl_prop_filter = nvlist_create(0);
+
+	nvlist_add_number(nvl_prop_filter, "prop_type", pfilter->prop_type);
+	nvlist_add_number(nvl_prop_filter, "cmp_type", pfilter->cmp_type);
+	nvlist_add_number(nvl_prop_filter, "cmp_flags", pfilter->cmp_flags);
+	nvlist_add_string(nvl_prop_filter, "pflt_strval", pfilter->pflt_strval);
+	/*
+	 * Do not bother adding pflt_re. It will be recompiled
+	 * using pflt_strval later, if applicable.
+	 */
+
+	return (nvl_prop_filter);
+}
+
+/*
+ * Convert the given nvlist into a prop_filter structure.
+ * Return a heap allocated pointer to the resulting prop_filter.
+ */
+struct prop_filter *
+nvlist_to_prop_filter(const nvlist_t *nvl_prop_filter)
+{
+	struct prop_filter *pfilter;
+
+	pfilter = calloc(1, sizeof(*pfilter));
+	if (pfilter == NULL)
+		err(1, "calloc");
+	pfilter->prop_type = nvlist_get_number(nvl_prop_filter, "prop_type");
+	pfilter->cmp_type = nvlist_get_number(nvl_prop_filter, "cmp_type");
+	pfilter->cmp_flags = nvlist_get_number(nvl_prop_filter, "cmp_flags");
+	pfilter->pflt_strval = strdup(nvlist_get_string(nvl_prop_filter,
+	    "pflt_strval"));
+	if (pfilter->cmp_type == FILT_CMP_REGEX) {
+		int re_flags = REG_NOSUB;
+		pfilter->pflt_re = calloc(1, sizeof(*pfilter->pflt_re));
+		if (pfilter->pflt_re == NULL)
+			errx(1, "RE calloc() error");
+		if ((pfilter->cmp_flags & FILT_FLAG_EXTENDED) != 0)
+			re_flags |= REG_EXTENDED;
+		if ((pfilter->cmp_flags & FILT_FLAG_ICASE) != 0)
+			re_flags |= REG_ICASE;
+		if (regcomp(pfilter->pflt_re, pfilter->pflt_strval,
+		    re_flags) != 0)
+			errx(1, "RE compilation error");
+	}
+
+	return (pfilter);
+}
+
+/*
+ * Convert the given struct filed into an nvl_filed nvlist.
+ * Return a heap allocated pointer to the resulting nvlist.
+ */
+nvlist_t *
+filed_to_nvlist(const struct filed *filed)
+{
+	nvlist_t *nvl_filed = nvlist_create(0);
+	enum f_type f_type = filed->f_type;
+	size_t i, sz;
+
+	nvlist_add_number(nvl_filed, "f_type", f_type);
+	nvlist_add_string(nvl_filed, "f_host", filed->f_host);
+	nvlist_add_string(nvl_filed, "f_program", filed->f_program);
+	if (filed->f_prop_filter != NULL) {
+		nvlist_add_nvlist(nvl_filed, "f_prop_filter",
+		    prop_filter_to_nvlist(filed->f_prop_filter));
+	}
+	sz = nitems(filed->f_pmask);
+	for (i = 0; i < sz; ++i) {
+		nvlist_append_number_array(nvl_filed, "f_pmask",
+		    filed->f_pmask[i]);
+	}
+	sz = nitems(filed->f_pcmp);
+	for (i = 0; i < sz; ++i) {
+		nvlist_append_number_array(nvl_filed, "f_pcmp",
+		    filed->f_pcmp[i]);
+	}
+
+	if (filed->f_file >= 0)
+		nvlist_add_descriptor(nvl_filed, "f_file", filed->f_file);
+	nvlist_add_number(nvl_filed, "f_flags", filed->f_flags);
+	if (f_type == F_WALL || f_type == F_USERS) {
+		sz = nitems(filed->f_uname);
+		for (i = 0; i < sz; ++i) {
+			nvlist_append_string_array(nvl_filed, "f_uname",
+			    filed->f_uname[i]);
+		}
+	} else if (f_type == F_FILE || f_type == F_CONSOLE || f_type == F_TTY) {
+		nvlist_add_string(nvl_filed, "f_fname", filed->f_fname);
+	} else if (f_type == F_FORW) {
+		struct addrinfo *ai = filed->f_addr, *cur;
+		nvlist_t *nvl_addrinfo;
+
+		nvlist_add_string(nvl_filed, "f_hname", filed->f_hname);
+		if (filed->f_addr != NULL) {
+			for (cur = ai; cur != NULL; cur = cur->ai_next) {
+				nvl_addrinfo = addrinfo_pack(cur);
+				nvlist_append_nvlist_array(nvl_filed,
+				    "f_addr", nvl_addrinfo);
+				nvlist_destroy(nvl_addrinfo);
+			}
+		}
+	} else if (filed->f_type == F_PIPE) {
+		nvlist_add_string(nvl_filed, "f_pname", filed->f_pname);
+		if (filed->f_procdesc >= 0) {
+			nvlist_add_descriptor(nvl_filed, "f_procdesc",
+			    filed->f_procdesc);
+		}
+	}
+
+	/*
+	 * Book-keeping fields are not transferred.
+	 */
+
+	return (nvl_filed);
+}
+
+/*
+ * Convert the given nvl_filed nvlist into a struct filed.
+ * Return a heap allocated pointer to the resulting struct
+ * filed.
+ */
+struct filed *
+nvlist_to_filed(const nvlist_t *nvl_filed)
+{
+	struct filed *filed;
+	enum f_type f_type;
+	const uint64_t *narr;
+	size_t i, sz;
+
+	filed = calloc(1, sizeof(*filed));
+	if (filed == NULL)
+		err(1, "calloc");
+
+	f_type = filed->f_type = nvlist_get_number(nvl_filed, "f_type");
+	(void)strlcpy(filed->f_host, nvlist_get_string(nvl_filed, "f_host"),
+	    sizeof(filed->f_host));
+	(void)strlcpy(filed->f_program, nvlist_get_string(nvl_filed,
+	    "f_program"), sizeof(filed->f_program));
+	if (nvlist_exists_nvlist(nvl_filed, "f_prop_filter")) {
+		filed->f_prop_filter = nvlist_to_prop_filter(
+		    nvlist_get_nvlist(nvl_filed, "f_prop_filter"));
+	}
+	narr = nvlist_get_number_array(nvl_filed, "f_pmask", &sz);
+	assert(sz == nitems(filed->f_pmask));
+	for (i = 0; i < sz; ++i)
+		filed->f_pmask[i] = narr[i];
+	narr = nvlist_get_number_array(nvl_filed, "f_pcmp", &sz);
+	assert(sz == nitems(filed->f_pcmp));
+	for (i = 0; i < sz; ++i)
+		filed->f_pcmp[i] = narr[i];
+
+	if (nvlist_exists_descriptor(nvl_filed, "f_file"))
+		filed->f_file = dup(nvlist_get_descriptor(nvl_filed, "f_file"));
+	else
+		filed->f_file = -1;
+	filed->f_flags = nvlist_get_number(nvl_filed, "f_flags");
+	if (f_type == F_WALL || f_type == F_USERS) {
+		const char * const *f_uname;
+
+		f_uname = nvlist_get_string_array(nvl_filed, "f_uname", &sz);
+		assert(sz == nitems(filed->f_uname));
+		for (i = 0; i < sz; ++i) {
+			(void)strlcpy(filed->f_uname[i], f_uname[i],
+			    sizeof(filed->f_uname[i]));
+		}
+	} else if (f_type == F_FILE || f_type == F_CONSOLE || f_type == F_TTY) {
+		(void)strlcpy(filed->f_fname, nvlist_get_string(nvl_filed,
+		    "f_fname"), sizeof(filed->f_fname));
+	} else if (f_type == F_FORW) {
+		const nvlist_t * const *f_addr;
+		struct addrinfo *ai, **next = NULL;
+
+		(void)strlcpy(filed->f_hname, nvlist_get_string(nvl_filed,
+		    "f_hname"), sizeof(filed->f_hname));
+		f_addr = nvlist_get_nvlist_array(nvl_filed, "f_addr", &sz);
+		for (i = 0; i < sz; ++i) {
+			ai = addrinfo_unpack(f_addr[i]);
+			if (next == NULL)
+				filed->f_addr = ai;
+			else
+				*next = ai;
+			next = &ai->ai_next;
+		}
+	} else if (filed->f_type == F_PIPE) {
+		(void)strlcpy(filed->f_pname, nvlist_get_string(nvl_filed,
+		    "f_pname"), sizeof(filed->f_pname));
+		if (nvlist_exists_descriptor(nvl_filed, "f_procdesc")) {
+			filed->f_procdesc = dup(nvlist_get_descriptor(nvl_filed,
+			    "f_procdesc"));
+		} else {
+			filed->f_procdesc = -1;
+		}
+	}
+
+	/*
+	 * Book-keeping fields are not transferred.
+	 */
+
+	return (filed);
+}
+
+nvlist_t *
+cap_readconfigfile(cap_channel_t *chan, const char *path)
+{
+	nvlist_t *nvl, *nvl_conf;
+
+	nvl = nvlist_create(0);
+	nvlist_add_string(nvl, "cmd", "readconfigfile");
+	nvlist_add_string(nvl, "path", path);
+	/* It is possible that our hostname has changed. */
+	nvlist_add_string(nvl, "LocalHostName", LocalHostName);
+	nvl = cap_xfer_nvlist(chan, nvl);
+	if (nvl == NULL) {
+		logerror("Failed to xfer configuration nvlist");
+		exit(1);
+	}
+	nvl_conf = nvlist_take_nvlist(nvl, "nvl_conf");
+
+	nvlist_destroy(nvl);
+	return (nvl_conf);
+}
+
+/*
+ * Now that we're executing as libcasper, we can obtain the
+ * resources specified in the configuration.
+ */
+int
+casper_readconfigfile(nvlist_t *nvlin, nvlist_t *nvlout)
+{
+	const char *path;
+
+	/*
+	 * Verify that syslogd did not manipulate the
+	 * configuration file path.
+	 */
+	path = nvlist_get_string(nvlin, "path");
+	if (strcmp(path, ConfFile) != 0)
+		err(1, "Configuration file mismatch: %s != %s", path, ConfFile);
+
+	/* Refresh our copy of LocalHostName, in case it changed. */
+	strlcpy(LocalHostName, nvlist_get_string(nvlin, "LocalHostName"),
+	    sizeof(LocalHostName));
+
+	nvlist_move_nvlist(nvlout, "nvl_conf", readconfigfile(path));
+	return (0);
+}