Index | Thread | Search

From:
Martijn van Duren <openbsd+tech@list.imperialat.at>
Subject:
split daemon(3) functionality for better debugability
To:
tech@openbsd.org
Date:
Fri, 20 Feb 2026 18:49:25 +0100

Download raw body.

Thread
Hello tech@,



When it comes to privsep daemons one of the things that always felled a

little off to me was the calling of daemon(3) before the setup phase was

complete. For most daemons this is not a big problem, but specifically

smtpd has bitten me a couple of times when I messed up one of my (custom)

filters, resulting in rcttl start smtpd gives me an "(ok)", but right

after it crashes. Another daemon which suffers greatly from this is

radiusd(8), which calls daemon(3) before parsing the config file.



I've been discussing with yasuoka@ for some time now and this is what

we came up with.

For brevity let's define 4 levels of processes:

A) The process spawning the deamon (e.g. shell)

B) The original daemon process, with A as its parent

C) The detached daemon process, with B as its parent

D) Any child as an (in)direct descendant from C forked before

   daemon_detach()



daemon_spawn() is supposed to replace daemon(3) inside B. The biggest

difference is that daemon_spawn() doesn't kill itself, but instead

creates a socketpair(2), generate a random value, and leave stderr open.

In case fork(2) fails daemon_spawn() returns -1 with errno set.

Everything after a successful fork(2) can only be returned from C.

If a problem occurs inside B the a message is printed to stderr,

followed a SIGTERM to C, a waitpid(2), and an _exit(1).

If C exits before fully detaching the exit code is picked up and

B exits the with the same exit code. If something else than the

random value is written back to the socketpair, that implies that the fd

is leaked to somewhere it's not supposed to be and C gets a SIGTERM,

before B exits with an exit value of 1.



After daemon_spawn() has created C initialization can continue as

normal, up to the point where normal operation commence. Most often

this is right after log_init switches to syslog, or just before

event_dispatch(). daemon_detach() inside C writes out the random

value to inform B that it can perform its exit(0), followed by doing a

dup2(nullfd, STDERR_FILENO) if applicable, to make sure that the tty

isn't being kept open. D also needs to call daemon_detach() to

make sure STDERR_FILENO is replaced with nullfd, these children don't

interact with B, which is guaranteed via FD_CLOEXEC | FD_CLOFORK on

the socketpair.



This code makes the following assumptions:

- STDERR_FILENO is available for debugging.

- SIGTERM isn't being masked out by C.

- In case C needs to be terminated, D kills themselves by virtue

  of losing connection with C. AFAIK this holds true for every of our

  daemons. But this caveat also holds true for every other call to
  err(3),
 or fatal().



The diff below contains an implementation for radiusd, as OK by

yasuoka@. But since I would like to see this at least in smtpd(8) as

well (and probably others), I'm asking for wider opinions about the

daemon.c bits. This work is heavily inspired by previous work of kn@

on daemonfd(). Both kn@'s daemonfd(), and daemon(3) should be able

to be build upon daemon_{spawn,detach}().



martijn@



diff refs/heads/master refs/heads/radiusd/daemon_spawn

commit - 3d36af1f1ebb63f29f1bdcaa411f391f42d04f6c

commit + 5057bcc86fe6824094c22470f0902f9742d68ae8

blob - /dev/null

blob + 1539df99dfa041be5fd5707bffb4568b5ef7963b (mode 644)

--- /dev/null

+++ usr.sbin/radiusd/daemon.c

@@ -0,0 +1,167 @@

+/*	$OpenBSD$ */

+/*-

+ * 

+ * Copyright (c) 2025 Martijn van Duren <martijn@openbsd.org>

+ * Copyright (c) 1990, 1993

+ *	The Regents of the University of California.  All rights reserved.

+ *

+ * 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.

+ * 3. Neither the name of the University nor the names of its contributors

+ *    may be used to endorse or promote products derived from this software

+ *    without specific prior written permission.

+ *

+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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/socket.h>

+#include <sys/un.h>

+#include <sys/wait.h>

+

+#include <errno.h>

+#include <fcntl.h>

+#include <paths.h>

+#include <unistd.h>

+#include <signal.h>

+#include <stdlib.h>

+#include <stdio.h>

+#include <string.h>

+#include <unistd.h>

+

+#include "daemon.h"

+

+static int	confirm;

+static uint32_t	magicval;

+static pid_t	parentpid = 0;

+

+int

+daemon_spawn(int chdirfd, int nullfd)

+{

+	pid_t pid;

+	int sp[2];

+	int status;

+	uint32_t val[2];

+	ssize_t rret;

+	int serrno;

+

+	if (socketpair(AF_UNIX, SOCK_STREAM, 0, sp) == -1)

+		return -1;

+	magicval = arc4random();

+

+	switch (pid = fork()) {

+	case -1:

+		serrno = errno;

+		close(sp[0]);

+		close(sp[1]);

+		errno = serrno;

+		return -1;

+	case 0:

+		break;

+	default:

+		/* Doesn't return */

+		close(sp[0]);

+		while (1) {

+			rret = read(sp[1], val, sizeof(val));

+			switch (rret) {

+			case -1:

+				if (errno == EINTR)

+					continue;

+				fprintf(stderr,

+				    "Can't determine daemon process state: "

+				    "%s\n", strerror(errno));

+				fflush(stderr);

+				kill(pid, SIGTERM);

+				(void)waitpid(pid, &status, 0);

+				_exit(1);

+			case 0:

+				/* Make sure it's really gone */

+				kill(pid, SIGTERM);

+

+				if (waitpid(pid, &status, 0) <= 0 ||

+				    WEXITSTATUS(status) == 0)

+					_exit(1);

+				_exit(WEXITSTATUS(status));

+			case 4:

+				if (val[0] != magicval) {

+					fprintf(stderr, "Unexpected verify "

+					    "value from daemon process\n");

+					fflush(stderr);

+					kill(pid, SIGTERM);

+					(void)waitpid(pid, &status, 0);

+					_exit(1);

+				}

+				_exit(0);

+			default:

+				fprintf(stderr, "Unexpected verify value "

+				    "length from daemon process\n");

+				fflush(stderr);

+				kill(pid, SIGTERM);

+				(void)waitpid(pid, &status, 0);

+				_exit(1);

+			}

+		}

+	}

+

+	parentpid = getpid();

+

+	close(sp[1]);

+	confirm = sp[0];

+	if (fcntl(confirm, F_SETFD, FD_CLOEXEC | FD_CLOFORK) == -1) {

+		return -1;

+	}

+

+	if (setsid() == -1)

+		return -1;

+

+	if (chdirfd != -1)

+		(void)fchdir(chdirfd);

+

+	if (nullfd != -1) {

+		(void)dup2(nullfd, STDIN_FILENO);

+		(void)dup2(nullfd, STDOUT_FILENO);

+	}

+	return 0;

+}

+

+void

+daemon_detach(int nullfd)

+{

+	ssize_t n;

+

+	/*

+	 * It's either the parent pid in case of fork, * or 0 in case of exec.

+	 * Both shouldn't match in helper processes.

+	 */

+	if (parentpid == getpid()) {

+		while ((n = write(confirm, &magicval, sizeof(magicval))) == -1) {

+			if (errno != EINTR)

+				break;

+		}

+		if (n != sizeof(magicval)) {

+			fprintf(stderr,

+			    "Failed to write magic value to attached process: %s",

+			    n == -1 ? strerror(errno) : "invalid length written");

+			fflush(stderr);

+			_exit(1);

+		}

+	}

+

+	if (nullfd != -1)

+		(void)dup2(nullfd, STDERR_FILENO);

+}

blob - /dev/null

blob + 1af159c45e6c34af2e5b5c5e57d6d6be3e5e89b2 (mode 644)

--- /dev/null

+++ usr.sbin/radiusd/daemon.h

@@ -0,0 +1,18 @@

+/*	$OpenBSD$ */

+/*

+ * Copyright (c) 2025 Martijn van Duren <martijn@openbsd.org>

+ *

+ * Permission to use, copy, modify, and distribute this software for any

+ * purpose with or without fee is hereby granted, provided that the above

+ * copyright notice and this permission notice appear in all copies.

+ *

+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES

+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF

+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR

+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES

+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN

+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF

+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

+ */

+int daemon_spawn(int, int);

+void daemon_detach(int);

blob - d331d85b74421c7e07e461eef4c6456b71660328

blob + fd560c5317fc0fcb3100c8d661538fa37fdffe85

--- usr.sbin/radiusd/radiusd/Makefile

+++ usr.sbin/radiusd/radiusd/Makefile

@@ -4,7 +4,7 @@ PROG=		radiusd

 BINDIR=		/usr/sbin

 MAN=		radiusd.8 radiusd.conf.5

 SRCS=		radiusd.c radius_subr.c parse.y log.c util.c imsg_subr.c

-SRCS+=		control.c

+SRCS+=		control.c daemon.c

 LDADD+=		-lradius -lcrypto -levent -lutil

 DPADD=		${LIBRADIUS} ${LIBCRYPTO} ${LIBEVENT} ${LIBUTIL}

 

blob - 94a1b260a61e704c10378c452c2fb584bde57deb

blob + 496d317fe6458e7dfe7f24460d00d8052e05e699

--- usr.sbin/radiusd/radiusd.c

+++ usr.sbin/radiusd/radiusd.c

@@ -44,6 +44,7 @@

 

 #include <radius.h>

 

+#include "daemon.h"

 #include "radiusd.h"

 #include "radiusd_local.h"

 #include "radius_subr.h"

@@ -100,7 +101,6 @@ static void		 radiusd_module_account_request(struct ra

 			    struct radius_query *);

 static int		 imsg_compose_radius_packet(struct imsgbuf *,

 			    uint32_t, u_int, RADIUS_PACKET *);

-static void		 close_stdio(void);

 

 static u_int		 radius_query_id_seq = 0;

 int			 debug = 0;

@@ -121,6 +121,7 @@ main(int argc, char *argv[])

 	extern char		*__progname;

 	const char		*conffile = CONFFILE;

 	int			 ch, error;

+	int			 nullfd;

 	struct radiusd		*radiusd;

 	bool			 noaction = false;

 	struct passwd		*pw;

@@ -156,8 +157,12 @@ main(int argc, char *argv[])

 	TAILQ_INIT(&radiusd->listen);

 	TAILQ_INIT(&radiusd->query);

 

-	if (!noaction && debug == 0)

-		daemon(0, 1);	/* pend closing stdio files */

+	if (!noaction && debug == 0) {

+		if ((nullfd = open(_PATH_DEVNULL, O_RDWR)) == -1)

+			err(1, "open(%s)", _PATH_DEVNULL);

+		if (daemon_spawn(-1, nullfd) == -1)

+			err(1, "daemon_spawn");

+	}

 

 	if (parse_config(conffile, radiusd) != 0)

 		errx(EXIT_FAILURE, "config error");

@@ -167,9 +172,6 @@ main(int argc, char *argv[])

 		exit(EXIT_SUCCESS);

 	}

 

-	if (debug == 0)

-		close_stdio(); /* close stdio files now */

-

 	if (control_init(RADIUSD_SOCK) == -1)

 		exit(EXIT_FAILURE);

 

@@ -205,6 +207,11 @@ main(int argc, char *argv[])

 	if (pledge("stdio inet", NULL) == -1)

 		err(EXIT_FAILURE, "pledge");

 

+	if (debug == 0) {

+		daemon_detach(nullfd);

+		close(nullfd);

+	}

+

 	event_loop(0);

 

 	if (radiusd->error != 0)

@@ -1905,20 +1912,6 @@ imsg_compose_radius_packet(struct imsgbuf *ibuf, uint3

 	return (0);

 }

 

-static void

-close_stdio(void)

-{

-	int	fd;

-

-	if ((fd = open(_PATH_DEVNULL, O_RDWR)) != -1) {

-		dup2(fd, STDIN_FILENO);

-		dup2(fd, STDOUT_FILENO);

-		dup2(fd, STDERR_FILENO);

-		if (fd > STDERR_FILENO)

-			close(fd);

-	}

-}

-

 /***********************************************************************

  * imsg_event

  ***********************************************************************/

blob - 41a87b38c983f9da14acc153e5c31f058f0455c6

blob + 9a95f5ecd0fd33549bc31f5bc45c7fe21623a9f0

--- usr.sbin/radiusd/radiusd_bsdauth/Makefile

+++ usr.sbin/radiusd/radiusd_bsdauth/Makefile

@@ -2,7 +2,7 @@

 

 PROG=		radiusd_bsdauth

 BINDIR=		/usr/libexec/radiusd

-SRCS=		radiusd_bsdauth.c radiusd_module.c imsg_subr.c

+SRCS=		radiusd_bsdauth.c radiusd_module.c imsg_subr.c daemon.c

 LDADD+=		-lradius -lcrypto -lutil

 DPADD+=		${LIBRADIUS} ${LIBCRYPTO} ${LIBUTIL}

 MAN=		radiusd_bsdauth.8

blob - 68135b9b7a274f778345c3d9f7699061bef927fc

blob + 62c29e88c34d495559181f6be7a35aa0c1b53d9b

--- usr.sbin/radiusd/radiusd_bsdauth.c

+++ usr.sbin/radiusd/radiusd_bsdauth.c

@@ -29,6 +29,7 @@

 #include <grp.h>

 #include <imsg.h>

 #include <login_cap.h>

+#include <paths.h>

 #include <pwd.h>

 #include <stdbool.h>

 #include <stdio.h>

@@ -37,6 +38,7 @@

 #include <syslog.h>

 #include <unistd.h>

 

+#include "daemon.h"

 #include "radiusd.h"

 #include "radiusd_module.h"

 

@@ -50,6 +52,7 @@ struct module_bsdauth {

 enum {

 	IMSG_BSDAUTH_OK = 1000,

 	IMSG_BSDAUTH_NG,

+	IMSG_BSDAUTH_DEBUG,

 	IMSG_BSDAUTH_USERCHECK,

 	IMSG_BSDAUTH_GROUPCHECK

 };

@@ -64,6 +67,7 @@ struct auth_groupcheck_args {

 

 __dead static void

 		 module_bsdauth_main(void);

+static void	 module_bsdauth_start(void *);

 static void	 module_bsdauth_config_set(void *, const char *, int,

 		    char * const *);

 static void	 module_bsdauth_userpass(void *, u_int, const char *,

@@ -73,9 +77,11 @@ __dead static void

 		 fatal(const char *);

 

 static struct module_handlers module_bsdauth_handlers = {

+	.start = module_bsdauth_start,

 	.userpass = module_bsdauth_userpass,

 	.config_set = module_bsdauth_config_set

 };

+static int	 debug = 0, nullfd = -1;

 

 int

 main(int argc, char *argv[])

@@ -88,6 +94,9 @@ main(int argc, char *argv[])

 	pid_t		 pid;

 	char		*saved_argv0;

 

+	if ((nullfd = open(_PATH_DEVNULL, O_RDWR)) == -1)

+		err(1, "open(%s)", _PATH_DEVNULL);

+

 	while ((ch = getopt(argc, argv, "M")) != -1)

 		switch (ch) {

 		case 'M':

@@ -129,6 +138,16 @@ main(int argc, char *argv[])

 				break;

 			datalen = imsg.hdr.len - IMSG_HEADER_SIZE;

 			switch (imsg.hdr.type) {

+			case IMSG_BSDAUTH_DEBUG:

+			    {

+				if (datalen != sizeof(debug))

+					err(EXIT_FAILURE, "Invalid message");

+				debug = *(int *)imsg.data;

+				if (!debug)

+					daemon_detach(nullfd);

+				close(nullfd);

+				break;

+			    }

 			case IMSG_BSDAUTH_USERCHECK:

 			    {

 				char		*user, *pass;

@@ -281,6 +300,20 @@ module_bsdauth_main(void)

 }

 

 static void

+module_bsdauth_start(void *ctx)

+{

+	struct module_bsdauth	 *module = ctx;

+

+	imsg_compose(&module->ibuf, IMSG_BSDAUTH_DEBUG, 0, 0, -1,

+	    &debug, sizeof(debug));

+	imsgbuf_flush(&module->ibuf);

+	module_send_message(module->base, IMSG_OK, NULL);

+	if (!debug)

+		daemon_detach(nullfd);

+	close(nullfd);

+}

+

+static void

 module_bsdauth_config_set(void *ctx, const char *name, int argc,

     char * const * argv)

 {

@@ -309,6 +342,9 @@ module_bsdauth_config_set(void *ctx, const char *name,

 		groups[i] = NULL;

 		module->okgroups = groups;

 		module_send_message(module->base, IMSG_OK, NULL);

+	} else if (strcmp(name, "_debug") == 0) {

+		debug = 1;

+		module_send_message(module->base, IMSG_OK, NULL);

 	} else if (strncmp(name, "_", 1) == 0)

 		/* ignore all internal messages */

 		module_send_message(module->base, IMSG_OK, NULL);

blob - 701564f1119155c31d4229352a9ab4ec849b3ff6

blob + 003b2bae66b6449130639e094ecbf5fed93a013c

--- usr.sbin/radiusd/radiusd_eap2mschap/Makefile

+++ usr.sbin/radiusd/radiusd_eap2mschap/Makefile

@@ -3,6 +3,7 @@

 PROG=		radiusd_eap2mschap

 BINDIR=		/usr/libexec/radiusd

 SRCS=		radiusd_eap2mschap.c radiusd_module.c radius_subr.c log.c

+SRCS+=		daemon.c

 CFLAGS+=	-DUSE_LIBEVENT

 LDADD+=		-lradius -lutil -lcrypto -levent

 DPADD+=		${LIBRADIUS} ${LIBUTIL} ${LIBCRYPTO} ${LIBEVENT}

blob - 53b6ccf3e6a58fee51a6f80072465ddd2e2d3715

blob + b5941905b5f372cfb7c1b4c3d8aacf61b7554af8

--- usr.sbin/radiusd/radiusd_eap2mschap.c

+++ usr.sbin/radiusd/radiusd_eap2mschap.c

@@ -25,6 +25,8 @@

 #include <assert.h>

 #include <err.h>

 #include <event.h>

+#include <fcntl.h>

+#include <paths.h>

 #include <radius.h>

 #include <stdbool.h>

 #include <stddef.h>

@@ -36,6 +38,7 @@

 #include <time.h>

 #include <unistd.h>

 

+#include "daemon.h"

 #include "radiusd.h"

 #include "radiusd_module.h"

 #include "radius_subr.h"

@@ -45,6 +48,8 @@

 

 #include "eap2mschap_local.h"

 

+static int	 debug = 0, nullfd = -1;

+

 int

 main(int argc, char *argv[])

 {

@@ -57,6 +62,9 @@ main(int argc, char *argv[])

 	};

 	struct eap2mschap	eap2mschap;

 

+	if ((nullfd = open(_PATH_DEVNULL, O_RDWR)) == -1)

+		err(1, "open(%s)", _PATH_DEVNULL);

+

 	eap2mschap_init(&eap2mschap);

 	if ((eap2mschap.base = module_create(STDIN_FILENO, &eap2mschap,

 	    &handlers)) == NULL)

@@ -73,6 +81,7 @@ main(int argc, char *argv[])

 		err(1, "pledge");

 

 	module_start(eap2mschap.base);

+

 	event_loop(0);

 

 	module_destroy(eap2mschap.base);

@@ -102,6 +111,9 @@ eap2mschap_start(void *ctx)

 	module_send_message(self->base, IMSG_OK, NULL);

 

 	evtimer_set(&self->ev_eapt, eap2mschap_on_eapt, self);

+	if (!debug)

+		daemon_detach(nullfd);

+	close(nullfd);

 }

 

 void

@@ -120,9 +132,10 @@ eap2mschap_config_set(void *ctx, const char *name, int

 			    "chap-name is too long");

 			return;

 		}

-	} else if (strcmp(name, "_debug") == 0)

+	} else if (strcmp(name, "_debug") == 0) {

+		debug = 1;

 		log_init(1);

-	else if (strncmp(name, "_", 1) == 0)

+	} else if (strncmp(name, "_", 1) == 0)

 		/* ignore all internal messages */;

 	else {

 		module_send_message(self->base, IMSG_NG,

blob - 7c15471c29b4ed7e643dab5a13d3f56c10dfe99d

blob + 78e94e571085ce948d8373a7b807f099e94080ed

--- usr.sbin/radiusd/radiusd_file/Makefile

+++ usr.sbin/radiusd/radiusd_file/Makefile

@@ -3,6 +3,7 @@

 PROG=		radiusd_file

 BINDIR=		/usr/libexec/radiusd

 SRCS=		radiusd_file.c radiusd_module.c imsg_subr.c log.c chap_ms.c

+SRCS+=		daemon.c

 #SRCS+=		radius_subr.c

 LDADD+=		-lradius -lcrypto -lutil

 DPADD+=		${LIBRADIUS} ${LIBCRYPTO} ${LIBUTIL}

blob - c2ec82a6c9b475d2189d03946897bf605c05111a

blob + 2b2874e799dfd3b1af03e25fdb2e479a0d4393a4

--- usr.sbin/radiusd/radiusd_file.c

+++ usr.sbin/radiusd/radiusd_file.c

@@ -29,6 +29,7 @@

 #include <imsg.h>

 #include <limits.h>

 #include <md5.h>

+#include <paths.h>

 #include <radius.h>

 #include <stddef.h>

 #include <stdint.h>

@@ -37,6 +38,7 @@

 #include <unistd.h>

 

 #include "chap_ms.h"

+#include "daemon.h"

 #include "imsg_subr.h"

 #include "log.h"

 #include "radiusd.h"

@@ -63,6 +65,7 @@ struct module_file_userinfo {

 enum {

 	IMSG_RADIUSD_FILE_OK = 1000,

 	IMSG_RADIUSD_FILE_NG,

+	IMSG_RADIUSD_FILE_DEBUG,

 	IMSG_RADIUSD_FILE_PARAMS,

 	IMSG_RADIUSD_FILE_USERINFO

 };

@@ -90,6 +93,7 @@ static struct module_handlers module_file_handlers = {

 	.config_set		= module_file_config_set,

 	.start			= module_file_start

 };

+static int	 nullfd = -1;

 

 int

 main(int argc, char *argv[])

@@ -104,6 +108,9 @@ main(int argc, char *argv[])

 	struct module_file_params	*paramsp, params;

 	char				 pathdb[PATH_MAX];

 

+	if ((nullfd = open(_PATH_DEVNULL, O_RDWR)) == -1)

+		err(1, "open(%s)", _PATH_DEVNULL);

+

 	while ((ch = getopt(argc, argv, "M")) != -1)

 		switch (ch) {

 		case 'M':

@@ -132,9 +139,10 @@ main(int argc, char *argv[])

 		err(EXIT_FAILURE, "imsgbuf_init");

 

 	/* Receive parameters from the main process. */

-	if (imsg_sync_read(&ibuf, 2000) <= 0 ||

-	    (n = imsg_get(&ibuf, &imsg)) <= 0)

-		exit(EXIT_FAILURE);

+	if (imsg_sync_read(&ibuf, 2000) <= 0)

+		fatal("imsg_sync_read");

+	if ((n = imsg_get(&ibuf, &imsg)) <= 0)

+		fatal("imsg_get");

 	if (imsg.hdr.type != IMSG_RADIUSD_FILE_PARAMS)

 		err(EXIT_FAILURE, "Receieved unknown message type %d",

 		    imsg.hdr.type);

@@ -190,9 +198,21 @@ parent_dispatch_main(struct module_file_params *params

 	char				*buf, *db[2], *str;

 	int				 ret;

 	struct module_file_userinfo	*ent;

+	int				 debug;

 

 	datalen = imsg->hdr.len - IMSG_HEADER_SIZE;

 	switch (imsg->hdr.type) {

+	case IMSG_RADIUSD_FILE_DEBUG:

+		if (datalen != sizeof(debug)) {

+			log_warn("%s: received "

+			    "IMSG_RADIUSD_FILE_DEBUG is wrong", __func__);

+			goto on_error;

+		}

+		debug = *(int *)imsg->data;

+		if (!debug)

+			daemon_detach(nullfd);

+		close(nullfd);

+		break;

 	case IMSG_RADIUSD_FILE_USERINFO:

 		if (datalen == 0 ||

 		    *((char *)imsg->data + datalen - 1) != '\0') {

@@ -250,6 +270,7 @@ module_file_main(void)

 

 	if (pledge("stdio", NULL) == -1)

 		err(EXIT_FAILURE, "pledge");

+

 	while (module_run(module_file.base) == 0)

 		;

 

@@ -336,6 +357,12 @@ module_file_start(void *ctx)

 	imsgbuf_flush(&module->ibuf);

 

 	module_send_message(module->base, IMSG_OK, NULL);

+	imsg_compose(&module->ibuf, IMSG_RADIUSD_FILE_DEBUG, 0, 0, -1,

+	    &module->params.debug, sizeof(module->params.debug));

+	imsgbuf_flush(&module->ibuf);

+	if (!module->params.debug)

+		daemon_detach(nullfd);

+	close(nullfd);

 }

 

 void

blob - 81d90b91534ddaee2cb9ce3850701be6767aef78

blob + cb35d7e2aa0efc406ddb08a693167b6f9074f606

--- usr.sbin/radiusd/radiusd_ipcp/Makefile

+++ usr.sbin/radiusd/radiusd_ipcp/Makefile

@@ -2,7 +2,7 @@

 

 PROG=		radiusd_ipcp

 BINDIR=		/usr/libexec/radiusd

-SRCS=		radiusd_ipcp.c radiusd_module.c log.c

+SRCS=		radiusd_ipcp.c radiusd_module.c log.c daemon.c

 CFLAGS+=	-DUSE_LIBEVENT

 LDADD+=		-lradius -lcrypto -lutil -levent

 DPADD+=		${LIBRADIUS} ${LIBCRYPTO} ${LIBUTIL} ${LIBEVENT}

blob - 3c8715b0f84c8bbbc1406db58e5c78ed701278df

blob + db6d8f141d27af435860d01e9d556b8938262f98

--- usr.sbin/radiusd/radiusd_ipcp.c

+++ usr.sbin/radiusd/radiusd_ipcp.c

@@ -30,6 +30,7 @@

 #include <errno.h>

 #include <event.h>

 #include <fcntl.h>

+#include <paths.h>

 #include <pwd.h>

 #include <radius.h>

 #include <stdbool.h>

@@ -42,6 +43,7 @@

 #include <unistd.h>

 #include <imsg.h>

 

+#include "daemon.h"

 #include "radiusd.h"

 #include "radiusd_module.h"

 #include "radiusd_ipcp.h"

@@ -224,6 +226,8 @@ RB_PROTOTYPE_STATIC(assigned_ipv4_tree, assigned_ipv4,

     assigned_ipv4_compar);

 RB_PROTOTYPE_STATIC(user_tree, user, tree, user_compar);

 

+static int	 debug = 0, nullfd = -1;

+

 int

 main(int argc, char *argv[])

 {

@@ -237,6 +241,9 @@ main(int argc, char *argv[])

 		.dispatch_control =	ipcp_dispatch_control

 	};

 

+	if ((nullfd = open(_PATH_DEVNULL, O_RDWR)) == -1)

+		err(1, "open(%s)", _PATH_DEVNULL);

+

 	ipcp_init(&module_ipcp);

 

 	if ((module_ipcp.base = module_create(STDIN_FILENO, &module_ipcp,

@@ -258,6 +265,7 @@ main(int argc, char *argv[])

 	event_init();

 

 	module_start(module_ipcp.base);

+

 	event_loop(0);

 

 	ipcp_fini(&module_ipcp);

@@ -330,6 +338,10 @@ ipcp_start(void *ctx)

 	}

 

 	module_send_message(self->base, IMSG_OK, NULL);

+

+	if (!debug)

+		daemon_detach(nullfd);

+	close(nullfd);

 }

 

 void

@@ -550,8 +562,10 @@ ipcp_config_set(void *ctx, const char *name, int argc,

 		TAILQ_INIT(&dae0->reqs);

 		TAILQ_INSERT_TAIL(&module->daes, dae0, next);

 		dae0->ipcp = module;

-	} else if (strcmp(name, "_debug") == 0)

+	} else if (strcmp(name, "_debug") == 0) {

 		log_init(1);

+		debug = 1;

+	}

 	else if (strncmp(name, "_", 1) == 0)

 		/* ignore */;

 	else {

blob - d77c2c35474ce4c85a750e1f29b049c70fabc974

blob + 296c85c1be044ee4125f532cd48a8454a1b56356

--- usr.sbin/radiusd/radiusd_radius/Makefile

+++ usr.sbin/radiusd/radiusd_radius/Makefile

@@ -3,6 +3,7 @@

 PROG=		radiusd_radius

 BINDIR=		/usr/libexec/radiusd

 SRCS=		radiusd_radius.c radiusd_module.c util.c imsg_subr.c log.c

+SRCS+=		daemon.c

 CFLAGS+=	-DUSE_LIBEVENT

 LDADD+=		-lradius -lcrypto -lutil -levent

 DPADD+=		${LIBRADIUS} ${LIBCRYPTO} ${LIBUTIL} ${LIBEVENT}

blob - 52619c4557788054f808c656b3c654de69fb339e

blob + d5c964c9e9ebf43bbe988084c0145db754057e51

--- usr.sbin/radiusd/radiusd_radius.c

+++ usr.sbin/radiusd/radiusd_radius.c

@@ -25,6 +25,7 @@

 #include <errno.h>

 #include <event.h>

 #include <fcntl.h>

+#include <paths.h>

 #include <stdbool.h>

 #include <stdio.h>

 #include <stdlib.h>

@@ -34,6 +35,7 @@

 

 #include <radius.h>

 

+#include "daemon.h"

 #include "radiusd.h"

 #include "radiusd_module.h"

 #include "util.h"

@@ -110,6 +112,8 @@ static struct module_handlers module_radius_handlers =

 	.access_request = module_radius_access_request

 };

 

+static int	 debug = 0, nullfd = -1;

+

 #ifndef nitems

 #define nitems(_x)    (sizeof((_x)) / sizeof((_x)[0]))

 #endif

@@ -119,6 +123,9 @@ main(int argc, char *argv[])

 {

 	static struct module_radius module_radius;

 

+	if ((nullfd = open(_PATH_DEVNULL, O_RDWR)) == -1)

+		err(1, "open(%s)", _PATH_DEVNULL);

+

 	module_radius_init(&module_radius);

 	openlog(NULL, LOG_PID, LOG_DAEMON);

 

@@ -136,6 +143,7 @@ main(int argc, char *argv[])

 		err(EXIT_FAILURE, "pledge");

 

 	module_start(module_radius.base);

+

 	event_loop(0);

 

 	module_destroy(module_radius.base);

@@ -220,9 +228,10 @@ module_radius_config_set(void *ctx, const char *paramn

 			    (u_long) sizeof(module->secret) - 1);

 			return;

 		}

-	} else if (strcmp(paramname, "_debug") == 0)

+	} else if (strcmp(paramname, "_debug") == 0) {

+		debug = 1;

 		log_init(1);

-	else if (strncmp(paramname, "_", 1) == 0)

+	} else if (strncmp(paramname, "_", 1) == 0)

 		/* nothing */; /* ignore all internal messages */

 	else {

 		module_send_message(module->base, IMSG_NG,

@@ -266,6 +275,10 @@ module_radius_start(void *ctx)

 	module_send_message(module->base, IMSG_OK, NULL);

 

 	module_notify_secret(module->base, module->secret);

+

+	if (!debug)

+		daemon_detach(nullfd);

+	close(nullfd);

 }

 

 static void

blob - e720296fe406f742ebf7bbb9de9eb0994854e9ec

blob + 3d46d6abee24b1f23f4d9fd5918ac73f4af27609

--- usr.sbin/radiusd/radiusd_standard/Makefile

+++ usr.sbin/radiusd/radiusd_standard/Makefile

@@ -2,7 +2,7 @@

 

 PROG=		radiusd_standard

 BINDIR=		/usr/libexec/radiusd

-SRCS=		radiusd_standard.c radiusd_module.c

+SRCS=		radiusd_standard.c radiusd_module.c daemon.c

 LDADD=		-lradius -lcrypto -lutil

 DPADD=		${LIBRADIUS} ${LIBCRYPTO} ${LIBUTIL}

 MAN=		radiusd_standard.8

blob - b925ec4c152117b11c9b2a276128068b8164d864

blob + 713f1e3eaf8827298a152f7e08cdfb027e5428f8

--- usr.sbin/radiusd/radiusd_standard.c

+++ usr.sbin/radiusd/radiusd_standard.c

@@ -22,6 +22,8 @@

 

 #include <err.h>

 #include <errno.h>

+#include <fcntl.h>

+#include <paths.h>

 #include <radius.h>

 #include <stdbool.h>

 #include <stdint.h>

@@ -31,6 +33,7 @@

 #include <syslog.h>

 #include <unistd.h>

 

+#include "daemon.h"

 #include "radiusd.h"

 #include "radiusd_module.h"

 

@@ -58,6 +61,7 @@ struct radius_const_str {

 

 static void	 radius_const_print(FILE *, RADIUS_PACKET *, uint8_t,

 		    const char *, struct radius_const_str *);

+static void	 module_standard_start(void *);

 static void	 module_standard_config_set(void *, const char *, int,

 		    char * const *);

 static void	 module_standard_reqdeco(void *, u_int, const u_char *, size_t);

@@ -79,12 +83,14 @@ static struct radius_const_str

 		 service_type_consts[], framed_protocol_consts[],

 		 acct_status_type_consts[], acct_authentic_consts[],

 		 terminate_cause_consts[], tunnel_medium_type_consts[];

+static int	 debug = 0, nullfd = -1;

 

 int

 main(int argc, char *argv[])

 {

 	struct module_standard module_standard;

 	struct module_handlers handlers = {

+		.start = module_standard_start,

 		.config_set = module_standard_config_set,

 		.request_decoration = module_standard_reqdeco,

 		.response_decoration = module_standard_resdeco,

@@ -92,6 +98,9 @@ main(int argc, char *argv[])

 	};

 	struct attr		*attr;

 

+	if ((nullfd = open(_PATH_DEVNULL, O_RDWR)) == -1)

+		err(1, "open(%s)", _PATH_DEVNULL);

+

 	memset(&module_standard, 0, sizeof(module_standard));

 	TAILQ_INIT(&module_standard.remove_reqattrs);

 	TAILQ_INIT(&module_standard.remove_resattrs);

@@ -125,6 +134,17 @@ main(int argc, char *argv[])

 }

 

 static void

+module_standard_start(void *ctx)

+{

+	struct module_standard *module = ctx;

+

+	module_send_message(module->base, IMSG_OK, NULL);

+	if (!debug)

+		daemon_detach(nullfd);

+	close(nullfd);

+}

+

+static void

 module_standard_config_set(void *ctx, const char *name, int argc,

     char * const * argv)

 {

@@ -198,7 +218,9 @@ module_standard_config_set(void *ctx, const char *name

 		else

 			SYNTAX_ASSERT(attr == NULL,

 			    "wrong number for `remove-response-attribute`");

-	} else if (strncmp(name, "_", 1) == 0)

+	} else if (strcmp(name, "_debug") == 0)

+		debug = 1;

+	else if (strncmp(name, "_", 1) == 0)

 		/* nothing */; /* ignore all internal messages */

 	else {

 		module_send_message(module->base, IMSG_NG,