1135446Strhodes/*
2262706Serwin * Copyright (C) 2004-2011, 2013  Internet Systems Consortium, Inc. ("ISC")
3135446Strhodes * Copyright (C) 1999-2002  Internet Software Consortium.
4135446Strhodes *
5186462Sdougb * Permission to use, copy, modify, and/or distribute this software for any
6135446Strhodes * purpose with or without fee is hereby granted, provided that the above
7135446Strhodes * copyright notice and this permission notice appear in all copies.
8135446Strhodes *
9135446Strhodes * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
10135446Strhodes * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11135446Strhodes * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
12135446Strhodes * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13135446Strhodes * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
14135446Strhodes * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15135446Strhodes * PERFORMANCE OF THIS SOFTWARE.
16135446Strhodes */
17135446Strhodes
18254897Serwin/* $Id: os.c,v 1.107 2011/03/02 00:02:54 marka Exp $ */
19135446Strhodes
20170222Sdougb/*! \file */
21170222Sdougb
22135446Strhodes#include <config.h>
23135446Strhodes#include <stdarg.h>
24135446Strhodes
25135446Strhodes#include <sys/types.h>	/* dev_t FreeBSD 2.1 */
26135446Strhodes#include <sys/stat.h>
27135446Strhodes
28135446Strhodes#include <ctype.h>
29135446Strhodes#include <errno.h>
30135446Strhodes#include <fcntl.h>
31135446Strhodes#include <grp.h>		/* Required for initgroups() on IRIX. */
32135446Strhodes#include <pwd.h>
33135446Strhodes#include <stdio.h>
34135446Strhodes#include <stdlib.h>
35135446Strhodes#include <signal.h>
36135446Strhodes#include <syslog.h>
37135446Strhodes#ifdef HAVE_TZSET
38135446Strhodes#include <time.h>
39135446Strhodes#endif
40135446Strhodes#include <unistd.h>
41135446Strhodes
42135446Strhodes#include <isc/buffer.h>
43135446Strhodes#include <isc/file.h>
44135446Strhodes#include <isc/print.h>
45186462Sdougb#include <isc/resource.h>
46135446Strhodes#include <isc/result.h>
47135446Strhodes#include <isc/strerror.h>
48135446Strhodes#include <isc/string.h>
49135446Strhodes
50135446Strhodes#include <named/main.h>
51135446Strhodes#include <named/os.h>
52153816Sdougb#ifdef HAVE_LIBSCF
53153816Sdougb#include <named/ns_smf_globals.h>
54153816Sdougb#endif
55135446Strhodes
56135446Strhodesstatic char *pidfile = NULL;
57135446Strhodesstatic int devnullfd = -1;
58135446Strhodes
59135446Strhodes#ifndef ISC_FACILITY
60135446Strhodes#define ISC_FACILITY LOG_DAEMON
61135446Strhodes#endif
62135446Strhodes
63135446Strhodes/*
64135446Strhodes * If there's no <linux/capability.h>, we don't care about <sys/prctl.h>
65135446Strhodes */
66135446Strhodes#ifndef HAVE_LINUX_CAPABILITY_H
67135446Strhodes#undef HAVE_SYS_PRCTL_H
68135446Strhodes#endif
69135446Strhodes
70135446Strhodes/*
71135446Strhodes * Linux defines:
72135446Strhodes *	(T) HAVE_LINUXTHREADS
73193149Sdougb *	(C) HAVE_SYS_CAPABILITY_H (or HAVE_LINUX_CAPABILITY_H)
74135446Strhodes *	(P) HAVE_SYS_PRCTL_H
75135446Strhodes * The possible cases are:
76135446Strhodes *	none:	setuid() normally
77135446Strhodes *	T:	no setuid()
78135446Strhodes *	C:	setuid() normally, drop caps (keep CAP_SETUID)
79135446Strhodes *	T+C:	no setuid(), drop caps (don't keep CAP_SETUID)
80135446Strhodes *	T+C+P:	setuid() early, drop caps (keep CAP_SETUID)
81135446Strhodes *	C+P:	setuid() normally, drop caps (keep CAP_SETUID)
82135446Strhodes *	P:	not possible
83135446Strhodes *	T+P:	not possible
84135446Strhodes *
85135446Strhodes * if (C)
86135446Strhodes *	caps = BIND_SERVICE + CHROOT + SETGID
87135446Strhodes *	if ((T && C && P) || !T)
88135446Strhodes *		caps += SETUID
89135446Strhodes *	endif
90135446Strhodes *	capset(caps)
91135446Strhodes * endif
92135446Strhodes * if (T && C && P && -u)
93135446Strhodes *	setuid()
94135446Strhodes * else if (T && -u)
95135446Strhodes *	fail
96135446Strhodes * --> start threads
97135446Strhodes * if (!T && -u)
98135446Strhodes *	setuid()
99135446Strhodes * if (C && (P || !-u))
100135446Strhodes *	caps = BIND_SERVICE
101135446Strhodes *	capset(caps)
102135446Strhodes * endif
103135446Strhodes *
104135446Strhodes * It will be nice when Linux threads work properly with setuid().
105135446Strhodes */
106135446Strhodes
107135446Strhodes#ifdef HAVE_LINUXTHREADS
108135446Strhodesstatic pid_t mainpid = 0;
109135446Strhodes#endif
110135446Strhodes
111135446Strhodesstatic struct passwd *runas_pw = NULL;
112135446Strhodesstatic isc_boolean_t done_setuid = ISC_FALSE;
113143731Sdougbstatic int dfd[2] = { -1, -1 };
114135446Strhodes
115135446Strhodes#ifdef HAVE_LINUX_CAPABILITY_H
116135446Strhodes
117135446Strhodesstatic isc_boolean_t non_root = ISC_FALSE;
118135446Strhodesstatic isc_boolean_t non_root_caps = ISC_FALSE;
119135446Strhodes
120186462Sdougb#ifdef HAVE_SYS_CAPABILITY_H
121186462Sdougb#include <sys/capability.h>
122186462Sdougb#else
123262706Serwin#ifdef HAVE_LINUX_TYPES_H
124262706Serwin#include <linux/types.h>
125262706Serwin#endif
126170222Sdougb/*%
127135446Strhodes * We define _LINUX_FS_H to prevent it from being included.  We don't need
128135446Strhodes * anything from it, and the files it includes cause warnings with 2.2
129135446Strhodes * kernels, and compilation failures (due to conflicts between <linux/string.h>
130135446Strhodes * and <string.h>) on 2.3 kernels.
131135446Strhodes */
132135446Strhodes#define _LINUX_FS_H
133193149Sdougb#include <linux/capability.h>
134193149Sdougb#include <syscall.h>
135193149Sdougb#ifndef SYS_capset
136193149Sdougb#ifndef __NR_capset
137193149Sdougb#include <asm/unistd.h> /* Slackware 4.0 needs this. */
138193149Sdougb#endif /* __NR_capset */
139193149Sdougb#define SYS_capset __NR_capset
140193149Sdougb#endif /* SYS_capset */
141193149Sdougb#endif /* HAVE_SYS_CAPABILITY_H */
142135446Strhodes
143135446Strhodes#ifdef HAVE_SYS_PRCTL_H
144135446Strhodes#include <sys/prctl.h>		/* Required for prctl(). */
145135446Strhodes
146135446Strhodes/*
147135446Strhodes * If the value of PR_SET_KEEPCAPS is not in <sys/prctl.h>, define it
148135446Strhodes * here.  This allows setuid() to work on systems running a new enough
149135446Strhodes * kernel but with /usr/include/linux pointing to "standard" kernel
150135446Strhodes * headers.
151135446Strhodes */
152135446Strhodes#ifndef PR_SET_KEEPCAPS
153135446Strhodes#define PR_SET_KEEPCAPS 8
154135446Strhodes#endif
155135446Strhodes
156135446Strhodes#endif /* HAVE_SYS_PRCTL_H */
157135446Strhodes
158193149Sdougb#ifdef HAVE_LIBCAP
159193149Sdougb#define SETCAPS_FUNC "cap_set_proc "
160193149Sdougb#else
161193149Sdougbtypedef unsigned int cap_t;
162193149Sdougb#define SETCAPS_FUNC "syscall(capset) "
163193149Sdougb#endif /* HAVE_LIBCAP */
164135446Strhodes
165135446Strhodesstatic void
166193149Sdougblinux_setcaps(cap_t caps) {
167193149Sdougb#ifndef HAVE_LIBCAP
168135446Strhodes	struct __user_cap_header_struct caphead;
169135446Strhodes	struct __user_cap_data_struct cap;
170193149Sdougb#endif
171135446Strhodes	char strbuf[ISC_STRERRORSIZE];
172135446Strhodes
173135446Strhodes	if ((getuid() != 0 && !non_root_caps) || non_root)
174135446Strhodes		return;
175193149Sdougb#ifndef HAVE_LIBCAP
176135446Strhodes	memset(&caphead, 0, sizeof(caphead));
177135446Strhodes	caphead.version = _LINUX_CAPABILITY_VERSION;
178135446Strhodes	caphead.pid = 0;
179135446Strhodes	memset(&cap, 0, sizeof(cap));
180135446Strhodes	cap.effective = caps;
181135446Strhodes	cap.permitted = caps;
182153816Sdougb	cap.inheritable = 0;
183193149Sdougb#endif
184193149Sdougb#ifdef HAVE_LIBCAP
185193149Sdougb	if (cap_set_proc(caps) < 0) {
186186462Sdougb#else
187186462Sdougb	if (syscall(SYS_capset, &caphead, &cap) < 0) {
188193149Sdougb#endif
189186462Sdougb		isc__strerror(errno, strbuf, sizeof(strbuf));
190193149Sdougb		ns_main_earlyfatal(SETCAPS_FUNC "failed: %s:"
191186462Sdougb				   " please ensure that the capset kernel"
192186462Sdougb				   " module is loaded.  see insmod(8)",
193186462Sdougb				   strbuf);
194186462Sdougb	}
195135446Strhodes}
196135446Strhodes
197193149Sdougb#ifdef HAVE_LIBCAP
198193149Sdougb#define SET_CAP(flag) \
199193149Sdougb	do { \
200193149Sdougb		capval = (flag); \
201193149Sdougb		cap_flag_value_t curval; \
202193149Sdougb		err = cap_get_flag(curcaps, capval, CAP_PERMITTED, &curval); \
203193149Sdougb		if (err != -1 && curval) { \
204193149Sdougb			err = cap_set_flag(caps, CAP_EFFECTIVE, 1, &capval, CAP_SET); \
205193149Sdougb			if (err == -1) { \
206193149Sdougb				isc__strerror(errno, strbuf, sizeof(strbuf)); \
207193149Sdougb				ns_main_earlyfatal("cap_set_proc failed: %s", strbuf); \
208193149Sdougb			} \
209193149Sdougb			\
210193149Sdougb			err = cap_set_flag(caps, CAP_PERMITTED, 1, &capval, CAP_SET); \
211193149Sdougb			if (err == -1) { \
212193149Sdougb				isc__strerror(errno, strbuf, sizeof(strbuf)); \
213193149Sdougb				ns_main_earlyfatal("cap_set_proc failed: %s", strbuf); \
214193149Sdougb			} \
215193149Sdougb		} \
216193149Sdougb	} while (0)
217193149Sdougb#define INIT_CAP \
218193149Sdougb	do { \
219193149Sdougb		caps = cap_init(); \
220193149Sdougb		if (caps == NULL) { \
221193149Sdougb			isc__strerror(errno, strbuf, sizeof(strbuf)); \
222193149Sdougb			ns_main_earlyfatal("cap_init failed: %s", strbuf); \
223193149Sdougb		} \
224193149Sdougb		curcaps = cap_get_proc(); \
225193149Sdougb		if (curcaps == NULL) { \
226193149Sdougb			isc__strerror(errno, strbuf, sizeof(strbuf)); \
227193149Sdougb			ns_main_earlyfatal("cap_get_proc failed: %s", strbuf); \
228193149Sdougb		} \
229193149Sdougb	} while (0)
230193149Sdougb#define FREE_CAP \
231193149Sdougb	{ \
232193149Sdougb		cap_free(caps); \
233193149Sdougb		cap_free(curcaps); \
234193149Sdougb	} while (0)
235193149Sdougb#else
236193149Sdougb#define SET_CAP(flag) do { caps |= (1 << (flag)); } while (0)
237193149Sdougb#define INIT_CAP do { caps = 0; } while (0)
238193149Sdougb#endif /* HAVE_LIBCAP */
239193149Sdougb
240135446Strhodesstatic void
241135446Strhodeslinux_initialprivs(void) {
242193149Sdougb	cap_t caps;
243193149Sdougb#ifdef HAVE_LIBCAP
244193149Sdougb	cap_t curcaps;
245193149Sdougb	cap_value_t capval;
246193149Sdougb	char strbuf[ISC_STRERRORSIZE];
247193149Sdougb	int err;
248193149Sdougb#endif
249135446Strhodes
250170222Sdougb	/*%
251135446Strhodes	 * We don't need most privileges, so we drop them right away.
252135446Strhodes	 * Later on linux_minprivs() will be called, which will drop our
253135446Strhodes	 * capabilities to the minimum needed to run the server.
254135446Strhodes	 */
255193149Sdougb	INIT_CAP;
256135446Strhodes
257135446Strhodes	/*
258135446Strhodes	 * We need to be able to bind() to privileged ports, notably port 53!
259135446Strhodes	 */
260193149Sdougb	SET_CAP(CAP_NET_BIND_SERVICE);
261135446Strhodes
262135446Strhodes	/*
263135446Strhodes	 * We need chroot() initially too.
264135446Strhodes	 */
265193149Sdougb	SET_CAP(CAP_SYS_CHROOT);
266135446Strhodes
267135446Strhodes#if defined(HAVE_SYS_PRCTL_H) || !defined(HAVE_LINUXTHREADS)
268135446Strhodes	/*
269135446Strhodes	 * We can setuid() only if either the kernel supports keeping
270135446Strhodes	 * capabilities after setuid() (which we don't know until we've
271135446Strhodes	 * tried) or we're not using threads.  If either of these is
272135446Strhodes	 * true, we want the setuid capability.
273135446Strhodes	 */
274193149Sdougb	SET_CAP(CAP_SETUID);
275135446Strhodes#endif
276135446Strhodes
277135446Strhodes	/*
278135446Strhodes	 * Since we call initgroups, we need this.
279135446Strhodes	 */
280193149Sdougb	SET_CAP(CAP_SETGID);
281135446Strhodes
282135446Strhodes	/*
283135446Strhodes	 * Without this, we run into problems reading a configuration file
284135446Strhodes	 * owned by a non-root user and non-world-readable on startup.
285135446Strhodes	 */
286193149Sdougb	SET_CAP(CAP_DAC_READ_SEARCH);
287135446Strhodes
288135446Strhodes	/*
289135446Strhodes	 * XXX  We might want to add CAP_SYS_RESOURCE, though it's not
290135446Strhodes	 *      clear it would work right given the way linuxthreads work.
291135446Strhodes	 * XXXDCL But since we need to be able to set the maximum number
292135446Strhodes	 * of files, the stack size, data size, and core dump size to
293135446Strhodes	 * support named.conf options, this is now being added to test.
294135446Strhodes	 */
295193149Sdougb	SET_CAP(CAP_SYS_RESOURCE);
296135446Strhodes
297224092Sdougb	/*
298224092Sdougb	 * We need to be able to set the ownership of the containing
299224092Sdougb	 * directory of the pid file when we create it.
300224092Sdougb	 */
301224092Sdougb	SET_CAP(CAP_CHOWN);
302224092Sdougb
303135446Strhodes	linux_setcaps(caps);
304193149Sdougb
305193149Sdougb#ifdef HAVE_LIBCAP
306193149Sdougb	FREE_CAP;
307193149Sdougb#endif
308135446Strhodes}
309135446Strhodes
310135446Strhodesstatic void
311135446Strhodeslinux_minprivs(void) {
312193149Sdougb	cap_t caps;
313193149Sdougb#ifdef HAVE_LIBCAP
314193149Sdougb	cap_t curcaps;
315193149Sdougb	cap_value_t capval;
316193149Sdougb	char strbuf[ISC_STRERRORSIZE];
317193149Sdougb	int err;
318193149Sdougb#endif
319135446Strhodes
320193149Sdougb	INIT_CAP;
321170222Sdougb	/*%
322135446Strhodes	 * Drop all privileges except the ability to bind() to privileged
323135446Strhodes	 * ports.
324135446Strhodes	 *
325135446Strhodes	 * It's important that we drop CAP_SYS_CHROOT.  If we didn't, it
326135446Strhodes	 * chroot() could be used to escape from the chrooted area.
327135446Strhodes	 */
328135446Strhodes
329193149Sdougb	SET_CAP(CAP_NET_BIND_SERVICE);
330135446Strhodes
331135446Strhodes	/*
332135446Strhodes	 * XXX  We might want to add CAP_SYS_RESOURCE, though it's not
333135446Strhodes	 *      clear it would work right given the way linuxthreads work.
334135446Strhodes	 * XXXDCL But since we need to be able to set the maximum number
335135446Strhodes	 * of files, the stack size, data size, and core dump size to
336135446Strhodes	 * support named.conf options, this is now being added to test.
337135446Strhodes	 */
338193149Sdougb	SET_CAP(CAP_SYS_RESOURCE);
339135446Strhodes
340135446Strhodes	linux_setcaps(caps);
341193149Sdougb
342193149Sdougb#ifdef HAVE_LIBCAP
343193149Sdougb	FREE_CAP;
344193149Sdougb#endif
345135446Strhodes}
346135446Strhodes
347135446Strhodes#ifdef HAVE_SYS_PRCTL_H
348135446Strhodesstatic void
349135446Strhodeslinux_keepcaps(void) {
350135446Strhodes	char strbuf[ISC_STRERRORSIZE];
351170222Sdougb	/*%
352135446Strhodes	 * Ask the kernel to allow us to keep our capabilities after we
353135446Strhodes	 * setuid().
354135446Strhodes	 */
355135446Strhodes
356135446Strhodes	if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) < 0) {
357135446Strhodes		if (errno != EINVAL) {
358135446Strhodes			isc__strerror(errno, strbuf, sizeof(strbuf));
359135446Strhodes			ns_main_earlyfatal("prctl() failed: %s", strbuf);
360135446Strhodes		}
361135446Strhodes	} else {
362135446Strhodes		non_root_caps = ISC_TRUE;
363135446Strhodes		if (getuid() != 0)
364135446Strhodes			non_root = ISC_TRUE;
365135446Strhodes	}
366135446Strhodes}
367135446Strhodes#endif
368135446Strhodes
369135446Strhodes#endif	/* HAVE_LINUX_CAPABILITY_H */
370135446Strhodes
371135446Strhodes
372135446Strhodesstatic void
373135446Strhodessetup_syslog(const char *progname) {
374135446Strhodes	int options;
375135446Strhodes
376135446Strhodes	options = LOG_PID;
377135446Strhodes#ifdef LOG_NDELAY
378135446Strhodes	options |= LOG_NDELAY;
379135446Strhodes#endif
380135446Strhodes	openlog(isc_file_basename(progname), options, ISC_FACILITY);
381135446Strhodes}
382135446Strhodes
383135446Strhodesvoid
384135446Strhodesns_os_init(const char *progname) {
385135446Strhodes	setup_syslog(progname);
386135446Strhodes#ifdef HAVE_LINUX_CAPABILITY_H
387135446Strhodes	linux_initialprivs();
388135446Strhodes#endif
389135446Strhodes#ifdef HAVE_LINUXTHREADS
390135446Strhodes	mainpid = getpid();
391135446Strhodes#endif
392135446Strhodes#ifdef SIGXFSZ
393135446Strhodes	signal(SIGXFSZ, SIG_IGN);
394135446Strhodes#endif
395135446Strhodes}
396135446Strhodes
397135446Strhodesvoid
398135446Strhodesns_os_daemonize(void) {
399135446Strhodes	pid_t pid;
400135446Strhodes	char strbuf[ISC_STRERRORSIZE];
401135446Strhodes
402143731Sdougb	if (pipe(dfd) == -1) {
403143731Sdougb		isc__strerror(errno, strbuf, sizeof(strbuf));
404143731Sdougb		ns_main_earlyfatal("pipe(): %s", strbuf);
405143731Sdougb	}
406143731Sdougb
407135446Strhodes	pid = fork();
408135446Strhodes	if (pid == -1) {
409135446Strhodes		isc__strerror(errno, strbuf, sizeof(strbuf));
410135446Strhodes		ns_main_earlyfatal("fork(): %s", strbuf);
411135446Strhodes	}
412143731Sdougb	if (pid != 0) {
413143731Sdougb		int n;
414143731Sdougb		/*
415143731Sdougb		 * Wait for the child to finish loading for the first time.
416143731Sdougb		 * This would be so much simpler if fork() worked once we
417186462Sdougb		 * were multi-threaded.
418143731Sdougb		 */
419143731Sdougb		(void)close(dfd[1]);
420143731Sdougb		do {
421143731Sdougb			char buf;
422143731Sdougb			n = read(dfd[0], &buf, 1);
423143731Sdougb			if (n == 1)
424143731Sdougb				_exit(0);
425143731Sdougb		} while (n == -1 && errno == EINTR);
426143731Sdougb		_exit(1);
427143731Sdougb	}
428143731Sdougb	(void)close(dfd[0]);
429135446Strhodes
430135446Strhodes	/*
431135446Strhodes	 * We're the child.
432135446Strhodes	 */
433135446Strhodes
434135446Strhodes#ifdef HAVE_LINUXTHREADS
435135446Strhodes	mainpid = getpid();
436135446Strhodes#endif
437135446Strhodes
438135446Strhodes	if (setsid() == -1) {
439135446Strhodes		isc__strerror(errno, strbuf, sizeof(strbuf));
440135446Strhodes		ns_main_earlyfatal("setsid(): %s", strbuf);
441135446Strhodes	}
442135446Strhodes
443135446Strhodes	/*
444135446Strhodes	 * Try to set stdin, stdout, and stderr to /dev/null, but press
445135446Strhodes	 * on even if it fails.
446135446Strhodes	 *
447135446Strhodes	 * XXXMLG The close() calls here are unneeded on all but NetBSD, but
448135446Strhodes	 * are harmless to include everywhere.  dup2() is supposed to close
449135446Strhodes	 * the FD if it is in use, but unproven-pthreads-0.16 is broken
450135446Strhodes	 * and will end up closing the wrong FD.  This will be fixed eventually,
451135446Strhodes	 * and these calls will be removed.
452135446Strhodes	 */
453135446Strhodes	if (devnullfd != -1) {
454135446Strhodes		if (devnullfd != STDIN_FILENO) {
455135446Strhodes			(void)close(STDIN_FILENO);
456135446Strhodes			(void)dup2(devnullfd, STDIN_FILENO);
457135446Strhodes		}
458135446Strhodes		if (devnullfd != STDOUT_FILENO) {
459135446Strhodes			(void)close(STDOUT_FILENO);
460135446Strhodes			(void)dup2(devnullfd, STDOUT_FILENO);
461135446Strhodes		}
462135446Strhodes		if (devnullfd != STDERR_FILENO) {
463135446Strhodes			(void)close(STDERR_FILENO);
464135446Strhodes			(void)dup2(devnullfd, STDERR_FILENO);
465135446Strhodes		}
466135446Strhodes	}
467135446Strhodes}
468135446Strhodes
469135446Strhodesvoid
470143731Sdougbns_os_started(void) {
471143731Sdougb	char buf = 0;
472143731Sdougb
473143731Sdougb	/*
474193149Sdougb	 * Signal to the parent that we started successfully.
475143731Sdougb	 */
476143731Sdougb	if (dfd[0] != -1 && dfd[1] != -1) {
477193149Sdougb		if (write(dfd[1], &buf, 1) != 1)
478193149Sdougb			ns_main_earlyfatal("unable to signal parent that we "
479193149Sdougb					   "otherwise started successfully.");
480143731Sdougb		close(dfd[1]);
481143731Sdougb		dfd[0] = dfd[1] = -1;
482143731Sdougb	}
483143731Sdougb}
484143731Sdougb
485143731Sdougbvoid
486135446Strhodesns_os_opendevnull(void) {
487135446Strhodes	devnullfd = open("/dev/null", O_RDWR, 0);
488135446Strhodes}
489135446Strhodes
490135446Strhodesvoid
491135446Strhodesns_os_closedevnull(void) {
492135446Strhodes	if (devnullfd != STDIN_FILENO &&
493135446Strhodes	    devnullfd != STDOUT_FILENO &&
494135446Strhodes	    devnullfd != STDERR_FILENO) {
495135446Strhodes		close(devnullfd);
496135446Strhodes		devnullfd = -1;
497135446Strhodes	}
498135446Strhodes}
499135446Strhodes
500135446Strhodesstatic isc_boolean_t
501135446Strhodesall_digits(const char *s) {
502135446Strhodes	if (*s == '\0')
503135446Strhodes		return (ISC_FALSE);
504135446Strhodes	while (*s != '\0') {
505135446Strhodes		if (!isdigit((*s)&0xff))
506135446Strhodes			return (ISC_FALSE);
507135446Strhodes		s++;
508135446Strhodes	}
509135446Strhodes	return (ISC_TRUE);
510135446Strhodes}
511135446Strhodes
512135446Strhodesvoid
513135446Strhodesns_os_chroot(const char *root) {
514135446Strhodes	char strbuf[ISC_STRERRORSIZE];
515153816Sdougb#ifdef HAVE_LIBSCF
516153816Sdougb	ns_smf_chroot = 0;
517153816Sdougb#endif
518135446Strhodes	if (root != NULL) {
519193149Sdougb#ifdef HAVE_CHROOT
520135446Strhodes		if (chroot(root) < 0) {
521135446Strhodes			isc__strerror(errno, strbuf, sizeof(strbuf));
522135446Strhodes			ns_main_earlyfatal("chroot(): %s", strbuf);
523135446Strhodes		}
524193149Sdougb#else
525193149Sdougb		ns_main_earlyfatal("chroot(): disabled");
526193149Sdougb#endif
527135446Strhodes		if (chdir("/") < 0) {
528135446Strhodes			isc__strerror(errno, strbuf, sizeof(strbuf));
529135446Strhodes			ns_main_earlyfatal("chdir(/): %s", strbuf);
530135446Strhodes		}
531153816Sdougb#ifdef HAVE_LIBSCF
532153816Sdougb		/* Set ns_smf_chroot flag on successful chroot. */
533153816Sdougb		ns_smf_chroot = 1;
534153816Sdougb#endif
535135446Strhodes	}
536135446Strhodes}
537135446Strhodes
538135446Strhodesvoid
539135446Strhodesns_os_inituserinfo(const char *username) {
540135446Strhodes	char strbuf[ISC_STRERRORSIZE];
541135446Strhodes	if (username == NULL)
542135446Strhodes		return;
543135446Strhodes
544135446Strhodes	if (all_digits(username))
545135446Strhodes		runas_pw = getpwuid((uid_t)atoi(username));
546135446Strhodes	else
547135446Strhodes		runas_pw = getpwnam(username);
548135446Strhodes	endpwent();
549135446Strhodes
550135446Strhodes	if (runas_pw == NULL)
551135446Strhodes		ns_main_earlyfatal("user '%s' unknown", username);
552135446Strhodes
553135446Strhodes	if (getuid() == 0) {
554135446Strhodes		if (initgroups(runas_pw->pw_name, runas_pw->pw_gid) < 0) {
555135446Strhodes			isc__strerror(errno, strbuf, sizeof(strbuf));
556135446Strhodes			ns_main_earlyfatal("initgroups(): %s", strbuf);
557135446Strhodes		}
558135446Strhodes	}
559135446Strhodes
560135446Strhodes}
561135446Strhodes
562135446Strhodesvoid
563135446Strhodesns_os_changeuser(void) {
564135446Strhodes	char strbuf[ISC_STRERRORSIZE];
565135446Strhodes	if (runas_pw == NULL || done_setuid)
566135446Strhodes		return;
567135446Strhodes
568135446Strhodes	done_setuid = ISC_TRUE;
569135446Strhodes
570135446Strhodes#ifdef HAVE_LINUXTHREADS
571135446Strhodes#ifdef HAVE_LINUX_CAPABILITY_H
572135446Strhodes	if (!non_root_caps)
573143731Sdougb		ns_main_earlyfatal("-u with Linux threads not supported: "
574143731Sdougb				   "requires kernel support for "
575143731Sdougb				   "prctl(PR_SET_KEEPCAPS)");
576143731Sdougb#else
577143731Sdougb	ns_main_earlyfatal("-u with Linux threads not supported: "
578143731Sdougb			   "no capabilities support or capabilities "
579143731Sdougb			   "disabled at build time");
580135446Strhodes#endif
581135446Strhodes#endif
582135446Strhodes
583135446Strhodes	if (setgid(runas_pw->pw_gid) < 0) {
584135446Strhodes		isc__strerror(errno, strbuf, sizeof(strbuf));
585135446Strhodes		ns_main_earlyfatal("setgid(): %s", strbuf);
586135446Strhodes	}
587135446Strhodes
588135446Strhodes	if (setuid(runas_pw->pw_uid) < 0) {
589135446Strhodes		isc__strerror(errno, strbuf, sizeof(strbuf));
590135446Strhodes		ns_main_earlyfatal("setuid(): %s", strbuf);
591135446Strhodes	}
592135446Strhodes
593165071Sdougb#if defined(HAVE_SYS_PRCTL_H) && defined(PR_SET_DUMPABLE)
594165071Sdougb	/*
595165071Sdougb	 * Restore the ability of named to drop core after the setuid()
596165071Sdougb	 * call has disabled it.
597165071Sdougb	 */
598186462Sdougb	if (prctl(PR_SET_DUMPABLE,1,0,0,0) < 0) {
599186462Sdougb		isc__strerror(errno, strbuf, sizeof(strbuf));
600186462Sdougb		ns_main_earlywarning("prctl(PR_SET_DUMPABLE) failed: %s",
601186462Sdougb				     strbuf);
602186462Sdougb	}
603165071Sdougb#endif
604186462Sdougb#if defined(HAVE_LINUX_CAPABILITY_H) && !defined(HAVE_LINUXTHREADS)
605186462Sdougb	linux_minprivs();
606186462Sdougb#endif
607135446Strhodes}
608135446Strhodes
609135446Strhodesvoid
610186462Sdougbns_os_adjustnofile() {
611186462Sdougb#ifdef HAVE_LINUXTHREADS
612186462Sdougb	isc_result_t result;
613186462Sdougb	isc_resourcevalue_t newvalue;
614186462Sdougb
615186462Sdougb	/*
616186462Sdougb	 * Linux: max number of open files specified by one thread doesn't seem
617186462Sdougb	 * to apply to other threads on Linux.
618186462Sdougb	 */
619186462Sdougb	newvalue = ISC_RESOURCE_UNLIMITED;
620186462Sdougb
621186462Sdougb	result = isc_resource_setlimit(isc_resource_openfiles, newvalue);
622186462Sdougb	if (result != ISC_R_SUCCESS)
623186462Sdougb		ns_main_earlywarning("couldn't adjust limit on open files");
624186462Sdougb#endif
625186462Sdougb}
626186462Sdougb
627186462Sdougbvoid
628135446Strhodesns_os_minprivs(void) {
629135446Strhodes#ifdef HAVE_SYS_PRCTL_H
630135446Strhodes	linux_keepcaps();
631135446Strhodes#endif
632135446Strhodes
633135446Strhodes#ifdef HAVE_LINUXTHREADS
634135446Strhodes	ns_os_changeuser(); /* Call setuid() before threads are started */
635135446Strhodes#endif
636135446Strhodes
637135446Strhodes#if defined(HAVE_LINUX_CAPABILITY_H) && defined(HAVE_LINUXTHREADS)
638135446Strhodes	linux_minprivs();
639135446Strhodes#endif
640135446Strhodes}
641135446Strhodes
642135446Strhodesstatic int
643224092Sdougbsafe_open(const char *filename, mode_t mode, isc_boolean_t append) {
644135446Strhodes	int fd;
645135446Strhodes	struct stat sb;
646135446Strhodes
647135446Strhodes	if (stat(filename, &sb) == -1) {
648135446Strhodes		if (errno != ENOENT)
649135446Strhodes			return (-1);
650135446Strhodes	} else if ((sb.st_mode & S_IFREG) == 0) {
651135446Strhodes		errno = EOPNOTSUPP;
652135446Strhodes		return (-1);
653135446Strhodes	}
654135446Strhodes
655135446Strhodes	if (append)
656224092Sdougb		fd = open(filename, O_WRONLY|O_CREAT|O_APPEND, mode);
657135446Strhodes	else {
658193149Sdougb		if (unlink(filename) < 0 && errno != ENOENT)
659193149Sdougb			return (-1);
660224092Sdougb		fd = open(filename, O_WRONLY|O_CREAT|O_EXCL, mode);
661135446Strhodes	}
662135446Strhodes	return (fd);
663135446Strhodes}
664135446Strhodes
665135446Strhodesstatic void
666135446Strhodescleanup_pidfile(void) {
667193149Sdougb	int n;
668135446Strhodes	if (pidfile != NULL) {
669193149Sdougb		n = unlink(pidfile);
670193149Sdougb		if (n == -1 && errno != ENOENT)
671193149Sdougb			ns_main_earlywarning("unlink '%s': failed", pidfile);
672135446Strhodes		free(pidfile);
673135446Strhodes	}
674135446Strhodes	pidfile = NULL;
675135446Strhodes}
676135446Strhodes
677193149Sdougbstatic int
678193149Sdougbmkdirpath(char *filename, void (*report)(const char *, ...)) {
679193149Sdougb	char *slash = strrchr(filename, '/');
680193149Sdougb	char strbuf[ISC_STRERRORSIZE];
681193149Sdougb	unsigned int mode;
682193149Sdougb
683193149Sdougb	if (slash != NULL && slash != filename) {
684193149Sdougb		struct stat sb;
685193149Sdougb		*slash = '\0';
686193149Sdougb
687193149Sdougb		if (stat(filename, &sb) == -1) {
688193149Sdougb			if (errno != ENOENT) {
689193149Sdougb				isc__strerror(errno, strbuf, sizeof(strbuf));
690193149Sdougb				(*report)("couldn't stat '%s': %s", filename,
691193149Sdougb					  strbuf);
692193149Sdougb				goto error;
693193149Sdougb			}
694193149Sdougb			if (mkdirpath(filename, report) == -1)
695193149Sdougb				goto error;
696224092Sdougb			/*
697224092Sdougb			 * Handle "//", "/./" and "/../" in path.
698224092Sdougb			 */
699224092Sdougb			if (!strcmp(slash + 1, "") ||
700224092Sdougb			    !strcmp(slash + 1, ".") ||
701224092Sdougb			    !strcmp(slash + 1, "..")) {
702224092Sdougb				*slash = '/';
703224092Sdougb				return (0);
704224092Sdougb			}
705193149Sdougb			mode = S_IRUSR | S_IWUSR | S_IXUSR;	/* u=rwx */
706193149Sdougb			mode |= S_IRGRP | S_IXGRP;		/* g=rx */
707193149Sdougb			mode |= S_IROTH | S_IXOTH;		/* o=rx */
708193149Sdougb			if (mkdir(filename, mode) == -1) {
709193149Sdougb				isc__strerror(errno, strbuf, sizeof(strbuf));
710193149Sdougb				(*report)("couldn't mkdir '%s': %s", filename,
711193149Sdougb					  strbuf);
712193149Sdougb				goto error;
713193149Sdougb			}
714224092Sdougb			if (runas_pw != NULL &&
715224092Sdougb			    chown(filename, runas_pw->pw_uid,
716224092Sdougb				  runas_pw->pw_gid) == -1) {
717224092Sdougb				isc__strerror(errno, strbuf, sizeof(strbuf));
718224092Sdougb				(*report)("couldn't chown '%s': %s", filename,
719224092Sdougb					  strbuf);
720224092Sdougb			}
721193149Sdougb		}
722193149Sdougb		*slash = '/';
723193149Sdougb	}
724193149Sdougb	return (0);
725193149Sdougb
726193149Sdougb error:
727193149Sdougb	*slash = '/';
728193149Sdougb	return (-1);
729193149Sdougb}
730193149Sdougb
731224092Sdougbstatic void
732224092Sdougbsetperms(uid_t uid, gid_t gid) {
733224092Sdougb	char strbuf[ISC_STRERRORSIZE];
734224092Sdougb#if !defined(HAVE_SETEGID) && defined(HAVE_SETRESGID)
735224092Sdougb	gid_t oldgid, tmpg;
736224092Sdougb#endif
737224092Sdougb#if !defined(HAVE_SETEUID) && defined(HAVE_SETRESUID)
738224092Sdougb	uid_t olduid, tmpu;
739224092Sdougb#endif
740224092Sdougb#if defined(HAVE_SETEGID)
741224092Sdougb	if (getegid() != gid && setegid(gid) == -1) {
742224092Sdougb		isc__strerror(errno, strbuf, sizeof(strbuf));
743224092Sdougb		ns_main_earlywarning("unable to set effective gid to %ld: %s",
744224092Sdougb				     (long)gid, strbuf);
745224092Sdougb	}
746224092Sdougb#elif defined(HAVE_SETRESGID)
747224092Sdougb	if (getresgid(&tmpg, &oldgid, &tmpg) == -1 || oldgid != gid) {
748224092Sdougb		if (setresgid(-1, gid, -1) == -1) {
749224092Sdougb			isc__strerror(errno, strbuf, sizeof(strbuf));
750224092Sdougb			ns_main_earlywarning("unable to set effective "
751224092Sdougb					     "gid to %d: %s", gid, strbuf);
752224092Sdougb		}
753224092Sdougb	}
754224092Sdougb#endif
755224092Sdougb
756224092Sdougb#if defined(HAVE_SETEUID)
757224092Sdougb	if (geteuid() != uid && seteuid(uid) == -1) {
758224092Sdougb		isc__strerror(errno, strbuf, sizeof(strbuf));
759224092Sdougb		ns_main_earlywarning("unable to set effective uid to %ld: %s",
760224092Sdougb				     (long)uid, strbuf);
761224092Sdougb	}
762224092Sdougb#elif defined(HAVE_SETRESUID)
763224092Sdougb	if (getresuid(&tmpu, &olduid, &tmpu) == -1 || olduid != uid) {
764224092Sdougb		if (setresuid(-1, uid, -1) == -1) {
765224092Sdougb			isc__strerror(errno, strbuf, sizeof(strbuf));
766224092Sdougb			ns_main_earlywarning("unable to set effective "
767224092Sdougb					     "uid to %d: %s", uid, strbuf);
768224092Sdougb		}
769224092Sdougb	}
770224092Sdougb#endif
771224092Sdougb}
772224092Sdougb
773224092SdougbFILE *
774224092Sdougbns_os_openfile(const char *filename, mode_t mode, isc_boolean_t switch_user) {
775224092Sdougb	char strbuf[ISC_STRERRORSIZE], *f;
776224092Sdougb	FILE *fp;
777224092Sdougb	int fd;
778224092Sdougb
779224092Sdougb	/*
780224092Sdougb	 * Make the containing directory if it doesn't exist.
781224092Sdougb	 */
782224092Sdougb	f = strdup(filename);
783224092Sdougb	if (f == NULL) {
784224092Sdougb		isc__strerror(errno, strbuf, sizeof(strbuf));
785224092Sdougb		ns_main_earlywarning("couldn't strdup() '%s': %s",
786224092Sdougb				     filename, strbuf);
787224092Sdougb		return (NULL);
788224092Sdougb	}
789224092Sdougb	if (mkdirpath(f, ns_main_earlywarning) == -1) {
790224092Sdougb		free(f);
791224092Sdougb		return (NULL);
792224092Sdougb	}
793224092Sdougb	free(f);
794224092Sdougb
795224092Sdougb	if (switch_user && runas_pw != NULL) {
796225361Sdougb#ifndef HAVE_LINUXTHREADS
797225361Sdougb		gid_t oldgid = getgid();
798225361Sdougb#endif
799224092Sdougb		/* Set UID/GID to the one we'll be running with eventually */
800224092Sdougb		setperms(runas_pw->pw_uid, runas_pw->pw_gid);
801224092Sdougb
802224092Sdougb		fd = safe_open(filename, mode, ISC_FALSE);
803224092Sdougb
804224092Sdougb#ifndef HAVE_LINUXTHREADS
805224092Sdougb		/* Restore UID/GID to root */
806225361Sdougb		setperms(0, oldgid);
807224092Sdougb#endif /* HAVE_LINUXTHREADS */
808224092Sdougb
809224092Sdougb		if (fd == -1) {
810224092Sdougb#ifndef HAVE_LINUXTHREADS
811224092Sdougb			fd = safe_open(filename, mode, ISC_FALSE);
812224092Sdougb			if (fd != -1) {
813224092Sdougb				ns_main_earlywarning("Required root "
814224092Sdougb						     "permissions to open "
815224092Sdougb						     "'%s'.", filename);
816224092Sdougb			} else {
817224092Sdougb				ns_main_earlywarning("Could not open "
818224092Sdougb						     "'%s'.", filename);
819224092Sdougb			}
820224092Sdougb			ns_main_earlywarning("Please check file and "
821224092Sdougb					     "directory permissions "
822224092Sdougb					     "or reconfigure the filename.");
823224092Sdougb#else /* HAVE_LINUXTHREADS */
824224092Sdougb			ns_main_earlywarning("Could not open "
825224092Sdougb					     "'%s'.", filename);
826224092Sdougb			ns_main_earlywarning("Please check file and "
827224092Sdougb					     "directory permissions "
828224092Sdougb					     "or reconfigure the filename.");
829224092Sdougb#endif /* HAVE_LINUXTHREADS */
830224092Sdougb		}
831224092Sdougb	} else {
832224092Sdougb		fd = safe_open(filename, mode, ISC_FALSE);
833224092Sdougb	}
834224092Sdougb
835224092Sdougb	if (fd < 0) {
836224092Sdougb		isc__strerror(errno, strbuf, sizeof(strbuf));
837224092Sdougb		ns_main_earlywarning("could not open file '%s': %s",
838224092Sdougb				     filename, strbuf);
839224092Sdougb		return (NULL);
840224092Sdougb	}
841224092Sdougb
842224092Sdougb	fp = fdopen(fd, "w");
843224092Sdougb	if (fp == NULL) {
844224092Sdougb		isc__strerror(errno, strbuf, sizeof(strbuf));
845224092Sdougb		ns_main_earlywarning("could not fdopen() file '%s': %s",
846224092Sdougb				     filename, strbuf);
847224092Sdougb	}
848224092Sdougb
849224092Sdougb	return (fp);
850224092Sdougb}
851224092Sdougb
852135446Strhodesvoid
853135446Strhodesns_os_writepidfile(const char *filename, isc_boolean_t first_time) {
854135446Strhodes	FILE *lockfile;
855135446Strhodes	pid_t pid;
856135446Strhodes	char strbuf[ISC_STRERRORSIZE];
857135446Strhodes	void (*report)(const char *, ...);
858135446Strhodes
859135446Strhodes	/*
860135446Strhodes	 * The caller must ensure any required synchronization.
861135446Strhodes	 */
862135446Strhodes
863135446Strhodes	report = first_time ? ns_main_earlyfatal : ns_main_earlywarning;
864135446Strhodes
865135446Strhodes	cleanup_pidfile();
866135446Strhodes
867135446Strhodes	if (filename == NULL)
868135446Strhodes		return;
869135446Strhodes
870224092Sdougb	pidfile = strdup(filename);
871135446Strhodes	if (pidfile == NULL) {
872135446Strhodes		isc__strerror(errno, strbuf, sizeof(strbuf));
873224092Sdougb		(*report)("couldn't strdup() '%s': %s", filename, strbuf);
874135446Strhodes		return;
875135446Strhodes	}
876193149Sdougb
877224092Sdougb	lockfile = ns_os_openfile(filename, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH,
878224092Sdougb				  first_time);
879135446Strhodes	if (lockfile == NULL) {
880135446Strhodes		cleanup_pidfile();
881135446Strhodes		return;
882135446Strhodes	}
883135446Strhodes#ifdef HAVE_LINUXTHREADS
884135446Strhodes	pid = mainpid;
885135446Strhodes#else
886135446Strhodes	pid = getpid();
887135446Strhodes#endif
888135446Strhodes	if (fprintf(lockfile, "%ld\n", (long)pid) < 0) {
889135446Strhodes		(*report)("fprintf() to pid file '%s' failed", filename);
890135446Strhodes		(void)fclose(lockfile);
891135446Strhodes		cleanup_pidfile();
892135446Strhodes		return;
893135446Strhodes	}
894135446Strhodes	if (fflush(lockfile) == EOF) {
895135446Strhodes		(*report)("fflush() to pid file '%s' failed", filename);
896135446Strhodes		(void)fclose(lockfile);
897135446Strhodes		cleanup_pidfile();
898135446Strhodes		return;
899135446Strhodes	}
900135446Strhodes	(void)fclose(lockfile);
901135446Strhodes}
902135446Strhodes
903135446Strhodesvoid
904135446Strhodesns_os_shutdown(void) {
905135446Strhodes	closelog();
906135446Strhodes	cleanup_pidfile();
907135446Strhodes}
908135446Strhodes
909135446Strhodesisc_result_t
910135446Strhodesns_os_gethostname(char *buf, size_t len) {
911135446Strhodes	int n;
912135446Strhodes
913135446Strhodes	n = gethostname(buf, len);
914135446Strhodes	return ((n == 0) ? ISC_R_SUCCESS : ISC_R_FAILURE);
915135446Strhodes}
916135446Strhodes
917135446Strhodesstatic char *
918135446Strhodesnext_token(char **stringp, const char *delim) {
919135446Strhodes	char *res;
920135446Strhodes
921135446Strhodes	do {
922135446Strhodes		res = strsep(stringp, delim);
923135446Strhodes		if (res == NULL)
924135446Strhodes			break;
925135446Strhodes	} while (*res == '\0');
926135446Strhodes	return (res);
927135446Strhodes}
928135446Strhodes
929135446Strhodesvoid
930135446Strhodesns_os_shutdownmsg(char *command, isc_buffer_t *text) {
931135446Strhodes	char *input, *ptr;
932135446Strhodes	unsigned int n;
933135446Strhodes	pid_t pid;
934135446Strhodes
935135446Strhodes	input = command;
936135446Strhodes
937135446Strhodes	/* Skip the command name. */
938135446Strhodes	ptr = next_token(&input, " \t");
939135446Strhodes	if (ptr == NULL)
940135446Strhodes		return;
941135446Strhodes
942135446Strhodes	ptr = next_token(&input, " \t");
943135446Strhodes	if (ptr == NULL)
944135446Strhodes		return;
945186462Sdougb
946135446Strhodes	if (strcmp(ptr, "-p") != 0)
947135446Strhodes		return;
948135446Strhodes
949135446Strhodes#ifdef HAVE_LINUXTHREADS
950135446Strhodes	pid = mainpid;
951135446Strhodes#else
952135446Strhodes	pid = getpid();
953135446Strhodes#endif
954135446Strhodes
955135446Strhodes	n = snprintf((char *)isc_buffer_used(text),
956135446Strhodes		     isc_buffer_availablelength(text),
957135446Strhodes		     "pid: %ld", (long)pid);
958135446Strhodes	/* Only send a message if it is complete. */
959225361Sdougb	if (n > 0 && n < isc_buffer_availablelength(text))
960135446Strhodes		isc_buffer_add(text, n);
961135446Strhodes}
962135446Strhodes
963135446Strhodesvoid
964135446Strhodesns_os_tzset(void) {
965135446Strhodes#ifdef HAVE_TZSET
966135446Strhodes	tzset();
967135446Strhodes#endif
968135446Strhodes}
969