1 /*
2  * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
3  *
4  * This Source Code Form is subject to the terms of the Mozilla Public
5  * License, v. 2.0. If a copy of the MPL was not distributed with this
6  * file, you can obtain one at https://mozilla.org/MPL/2.0/.
7  *
8  * See the COPYRIGHT file distributed with this work for additional
9  * information regarding copyright ownership.
10  */
11 
12 /*! \file */
13 
14 #include <config.h>
15 #include <stdarg.h>
16 #include <stdbool.h>
17 
18 #include <sys/types.h>	/* dev_t FreeBSD 2.1 */
19 #include <sys/stat.h>
20 #ifdef HAVE_UNAME
21 #include <sys/utsname.h>
22 #endif
23 
24 #include <ctype.h>
25 #include <errno.h>
26 #include <fcntl.h>
27 #include <grp.h>		/* Required for initgroups() on IRIX. */
28 #include <pwd.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <signal.h>
32 #include <syslog.h>
33 #ifdef HAVE_TZSET
34 #include <time.h>
35 #endif
36 #include <unistd.h>
37 
38 #include <isc/buffer.h>
39 #include <isc/file.h>
40 #include <isc/print.h>
41 #include <isc/resource.h>
42 #include <isc/result.h>
43 #include <isc/strerror.h>
44 #include <isc/string.h>
45 #include <isc/util.h>
46 
47 #include <named/globals.h>
48 #include <named/main.h>
49 #include <named/os.h>
50 #ifdef HAVE_LIBSCF
51 #include <named/ns_smf_globals.h>
52 #endif
53 
54 static char *pidfile = NULL;
55 static char *lockfile = NULL;
56 static int devnullfd = -1;
57 static int singletonfd = -1;
58 
59 #ifndef ISC_FACILITY
60 #define ISC_FACILITY LOG_DAEMON
61 #endif
62 
63 /*
64  * If there's no <linux/capability.h>, we don't care about <sys/prctl.h>
65  */
66 #ifndef HAVE_LINUX_CAPABILITY_H
67 #undef HAVE_SYS_PRCTL_H
68 #endif
69 
70 /*
71  * Linux defines:
72  *	(T) HAVE_LINUXTHREADS
73  *	(C) HAVE_SYS_CAPABILITY_H (or HAVE_LINUX_CAPABILITY_H)
74  *	(P) HAVE_SYS_PRCTL_H
75  * The possible cases are:
76  *	none:	setuid() normally
77  *	T:	no setuid()
78  *	C:	setuid() normally, drop caps (keep CAP_SETUID)
79  *	T+C:	no setuid(), drop caps (don't keep CAP_SETUID)
80  *	T+C+P:	setuid() early, drop caps (keep CAP_SETUID)
81  *	C+P:	setuid() normally, drop caps (keep CAP_SETUID)
82  *	P:	not possible
83  *	T+P:	not possible
84  *
85  * if (C)
86  *	caps = BIND_SERVICE + CHROOT + SETGID
87  *	if ((T && C && P) || !T)
88  *		caps += SETUID
89  *	endif
90  *	capset(caps)
91  * endif
92  * if (T && C && P && -u)
93  *	setuid()
94  * else if (T && -u)
95  *	fail
96  * --> start threads
97  * if (!T && -u)
98  *	setuid()
99  * if (C && (P || !-u))
100  *	caps = BIND_SERVICE
101  *	capset(caps)
102  * endif
103  *
104  * It will be nice when Linux threads work properly with setuid().
105  */
106 
107 #ifdef HAVE_LINUXTHREADS
108 static pid_t mainpid = 0;
109 #endif
110 
111 static struct passwd *runas_pw = NULL;
112 static bool done_setuid = false;
113 static int dfd[2] = { -1, -1 };
114 
115 #ifdef HAVE_LINUX_CAPABILITY_H
116 
117 static bool non_root = false;
118 static bool non_root_caps = false;
119 
120 #ifdef HAVE_SYS_CAPABILITY_H
121 #include <sys/capability.h>
122 #else
123 #ifdef HAVE_LINUX_TYPES_H
124 #include <linux/types.h>
125 #endif
126 /*%
127  * We define _LINUX_FS_H to prevent it from being included.  We don't need
128  * anything from it, and the files it includes cause warnings with 2.2
129  * kernels, and compilation failures (due to conflicts between <linux/string.h>
130  * and <string.h>) on 2.3 kernels.
131  */
132 #define _LINUX_FS_H
133 #include <linux/capability.h>
134 #include <syscall.h>
135 #ifndef SYS_capset
136 #ifndef __NR_capset
137 #include <asm/unistd.h> /* Slackware 4.0 needs this. */
138 #endif /* __NR_capset */
139 #define SYS_capset __NR_capset
140 #endif /* SYS_capset */
141 #endif /* HAVE_SYS_CAPABILITY_H */
142 
143 #ifdef HAVE_SYS_PRCTL_H
144 #include <sys/prctl.h>		/* Required for prctl(). */
145 
146 /*
147  * If the value of PR_SET_KEEPCAPS is not in <sys/prctl.h>, define it
148  * here.  This allows setuid() to work on systems running a new enough
149  * kernel but with /usr/include/linux pointing to "standard" kernel
150  * headers.
151  */
152 #ifndef PR_SET_KEEPCAPS
153 #define PR_SET_KEEPCAPS 8
154 #endif
155 
156 #endif /* HAVE_SYS_PRCTL_H */
157 
158 #ifdef HAVE_LIBCAP
159 #define SETCAPS_FUNC "cap_set_proc "
160 #else
161 typedef unsigned int cap_t;
162 #define SETCAPS_FUNC "syscall(capset) "
163 #endif /* HAVE_LIBCAP */
164 
165 static void
linux_setcaps(cap_t caps)166 linux_setcaps(cap_t caps) {
167 #ifndef HAVE_LIBCAP
168 	struct __user_cap_header_struct caphead;
169 	struct __user_cap_data_struct cap;
170 #endif
171 	char strbuf[ISC_STRERRORSIZE];
172 
173 	if ((getuid() != 0 && !non_root_caps) || non_root)
174 		return;
175 #ifndef HAVE_LIBCAP
176 	memset(&caphead, 0, sizeof(caphead));
177 	caphead.version = _LINUX_CAPABILITY_VERSION;
178 	caphead.pid = 0;
179 	memset(&cap, 0, sizeof(cap));
180 	cap.effective = caps;
181 	cap.permitted = caps;
182 	cap.inheritable = 0;
183 #endif
184 #ifdef HAVE_LIBCAP
185 	if (cap_set_proc(caps) < 0) {
186 #else
187 	if (syscall(SYS_capset, &caphead, &cap) < 0) {
188 #endif
189 		isc__strerror(errno, strbuf, sizeof(strbuf));
190 		ns_main_earlyfatal(SETCAPS_FUNC "failed: %s:"
191 				   " please ensure that the capset kernel"
192 				   " module is loaded.  see insmod(8)",
193 				   strbuf);
194 	}
195 }
196 
197 #ifdef HAVE_LIBCAP
198 #define SET_CAP(flag) \
199 	do { \
200 		cap_flag_value_t curval; \
201 		capval = (flag); \
202 		err = cap_get_flag(curcaps, capval, CAP_PERMITTED, &curval); \
203 		if (err != -1 && curval) { \
204 			err = cap_set_flag(caps, CAP_EFFECTIVE, 1, &capval, CAP_SET); \
205 			if (err == -1) { \
206 				isc__strerror(errno, strbuf, sizeof(strbuf)); \
207 				ns_main_earlyfatal("cap_set_proc failed: %s", strbuf); \
208 			} \
209 			\
210 			err = cap_set_flag(caps, CAP_PERMITTED, 1, &capval, CAP_SET); \
211 			if (err == -1) { \
212 				isc__strerror(errno, strbuf, sizeof(strbuf)); \
213 				ns_main_earlyfatal("cap_set_proc failed: %s", strbuf); \
214 			} \
215 		} \
216 	} while (0)
217 #define INIT_CAP \
218 	do { \
219 		caps = cap_init(); \
220 		if (caps == NULL) { \
221 			isc__strerror(errno, strbuf, sizeof(strbuf)); \
222 			ns_main_earlyfatal("cap_init failed: %s", strbuf); \
223 		} \
224 		curcaps = cap_get_proc(); \
225 		if (curcaps == NULL) { \
226 			isc__strerror(errno, strbuf, sizeof(strbuf)); \
227 			ns_main_earlyfatal("cap_get_proc failed: %s", strbuf); \
228 		} \
229 	} while (0)
230 #define FREE_CAP \
231 	{ \
232 		cap_free(caps); \
233 		cap_free(curcaps); \
234 	} while (0)
235 #else
236 #define SET_CAP(flag) \
237 	do { \
238 		if (curcaps & (1 << (flag))) { \
239 			caps |= (1 << (flag)); \
240 		} \
241 	} while (0)
242 #define INIT_CAP do { caps = 0; } while (0)
243 #endif /* HAVE_LIBCAP */
244 
245 #ifndef HAVE_LIBCAP
246 /*%
247  * Store the bitmask representing the permitted capability set in 'capsp'.  To
248  * match libcap-enabled behavior, capget() syscall errors are not reported,
249  * they just cause 'capsp' to be set to 0, which effectively prevents any
250  * capability from being subsequently requested.
251  */
252 static void
253 linux_getpermittedcaps(cap_t *capsp) {
254 	struct __user_cap_header_struct caphead;
255 	struct __user_cap_data_struct curcaps;
256 
257 	memset(&caphead, 0, sizeof(caphead));
258 	caphead.version = _LINUX_CAPABILITY_VERSION;
259 	caphead.pid = 0;
260 	memset(&curcaps, 0, sizeof(curcaps));
261 	syscall(SYS_capget, &caphead, &curcaps);
262 
263 	*capsp = curcaps.permitted;
264 }
265 #endif /* HAVE_LIBCAP */
266 
267 static void
268 linux_initialprivs(void) {
269 	cap_t curcaps;
270 	cap_t caps;
271 #ifdef HAVE_LIBCAP
272 	cap_value_t capval;
273 	char strbuf[ISC_STRERRORSIZE];
274 	int err;
275 #else
276 	linux_getpermittedcaps(&curcaps);
277 #endif
278 
279 	/*%
280 	 * We don't need most privileges, so we drop them right away.
281 	 * Later on linux_minprivs() will be called, which will drop our
282 	 * capabilities to the minimum needed to run the server.
283 	 */
284 	INIT_CAP;
285 
286 	/*
287 	 * We need to be able to bind() to privileged ports, notably port 53!
288 	 */
289 	SET_CAP(CAP_NET_BIND_SERVICE);
290 
291 	/*
292 	 * We need chroot() initially too.
293 	 */
294 	SET_CAP(CAP_SYS_CHROOT);
295 
296 #if defined(HAVE_SYS_PRCTL_H) || !defined(HAVE_LINUXTHREADS)
297 	/*
298 	 * We can setuid() only if either the kernel supports keeping
299 	 * capabilities after setuid() (which we don't know until we've
300 	 * tried) or we're not using threads.  If either of these is
301 	 * true, we want the setuid capability.
302 	 */
303 	SET_CAP(CAP_SETUID);
304 #endif
305 
306 	/*
307 	 * Since we call initgroups, we need this.
308 	 */
309 	SET_CAP(CAP_SETGID);
310 
311 	/*
312 	 * Without this, we run into problems reading a configuration file
313 	 * owned by a non-root user and non-world-readable on startup.
314 	 */
315 	SET_CAP(CAP_DAC_READ_SEARCH);
316 
317 	/*
318 	 * XXX  We might want to add CAP_SYS_RESOURCE, though it's not
319 	 *      clear it would work right given the way linuxthreads work.
320 	 * XXXDCL But since we need to be able to set the maximum number
321 	 * of files, the stack size, data size, and core dump size to
322 	 * support named.conf options, this is now being added to test.
323 	 */
324 	SET_CAP(CAP_SYS_RESOURCE);
325 
326 	/*
327 	 * We need to be able to set the ownership of the containing
328 	 * directory of the pid file when we create it.
329 	 */
330 	SET_CAP(CAP_CHOWN);
331 
332 	linux_setcaps(caps);
333 
334 #ifdef HAVE_LIBCAP
335 	FREE_CAP;
336 #endif
337 }
338 
339 static void
340 linux_minprivs(void) {
341 	cap_t curcaps;
342 	cap_t caps;
343 #ifdef HAVE_LIBCAP
344 	cap_value_t capval;
345 	char strbuf[ISC_STRERRORSIZE];
346 	int err;
347 #else
348 	linux_getpermittedcaps(&curcaps);
349 #endif
350 
351 	INIT_CAP;
352 	/*%
353 	 * Drop all privileges except the ability to bind() to privileged
354 	 * ports.
355 	 *
356 	 * It's important that we drop CAP_SYS_CHROOT.  If we didn't, it
357 	 * chroot() could be used to escape from the chrooted area.
358 	 */
359 
360 	SET_CAP(CAP_NET_BIND_SERVICE);
361 
362 	/*
363 	 * XXX  We might want to add CAP_SYS_RESOURCE, though it's not
364 	 *      clear it would work right given the way linuxthreads work.
365 	 * XXXDCL But since we need to be able to set the maximum number
366 	 * of files, the stack size, data size, and core dump size to
367 	 * support named.conf options, this is now being added to test.
368 	 */
369 	SET_CAP(CAP_SYS_RESOURCE);
370 
371 	linux_setcaps(caps);
372 
373 #ifdef HAVE_LIBCAP
374 	FREE_CAP;
375 #endif
376 }
377 
378 #ifdef HAVE_SYS_PRCTL_H
379 static void
380 linux_keepcaps(void) {
381 	char strbuf[ISC_STRERRORSIZE];
382 	/*%
383 	 * Ask the kernel to allow us to keep our capabilities after we
384 	 * setuid().
385 	 */
386 
387 	if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) < 0) {
388 		if (errno != EINVAL) {
389 			isc__strerror(errno, strbuf, sizeof(strbuf));
390 			ns_main_earlyfatal("prctl() failed: %s", strbuf);
391 		}
392 	} else {
393 		non_root_caps = true;
394 		if (getuid() != 0)
395 			non_root = true;
396 	}
397 }
398 #endif
399 
400 #endif	/* HAVE_LINUX_CAPABILITY_H */
401 
402 
403 static void
404 setup_syslog(const char *progname) {
405 	int options;
406 
407 	options = LOG_PID;
408 #ifdef LOG_NDELAY
409 	options |= LOG_NDELAY;
410 #endif
411 	openlog(isc_file_basename(progname), options, ISC_FACILITY);
412 }
413 
414 void
415 ns_os_init(const char *progname) {
416 	setup_syslog(progname);
417 #ifdef HAVE_LINUX_CAPABILITY_H
418 	linux_initialprivs();
419 #endif
420 #ifdef HAVE_LINUXTHREADS
421 	mainpid = getpid();
422 #endif
423 #ifdef SIGXFSZ
424 	signal(SIGXFSZ, SIG_IGN);
425 #endif
426 }
427 
428 void
429 ns_os_daemonize(void) {
430 	pid_t pid;
431 	char strbuf[ISC_STRERRORSIZE];
432 
433 	if (pipe(dfd) == -1) {
434 		isc__strerror(errno, strbuf, sizeof(strbuf));
435 		ns_main_earlyfatal("pipe(): %s", strbuf);
436 	}
437 
438 	pid = fork();
439 	if (pid == -1) {
440 		isc__strerror(errno, strbuf, sizeof(strbuf));
441 		ns_main_earlyfatal("fork(): %s", strbuf);
442 	}
443 	if (pid != 0) {
444 		int n;
445 		/*
446 		 * Wait for the child to finish loading for the first time.
447 		 * This would be so much simpler if fork() worked once we
448 		 * were multi-threaded.
449 		 */
450 		(void)close(dfd[1]);
451 		do {
452 			char buf;
453 			n = read(dfd[0], &buf, 1);
454 			if (n == 1)
455 				_exit(0);
456 		} while (n == -1 && errno == EINTR);
457 		_exit(1);
458 	}
459 	(void)close(dfd[0]);
460 
461 	/*
462 	 * We're the child.
463 	 */
464 
465 #ifdef HAVE_LINUXTHREADS
466 	mainpid = getpid();
467 #endif
468 
469 	if (setsid() == -1) {
470 		isc__strerror(errno, strbuf, sizeof(strbuf));
471 		ns_main_earlyfatal("setsid(): %s", strbuf);
472 	}
473 
474 	/*
475 	 * Try to set stdin, stdout, and stderr to /dev/null, but press
476 	 * on even if it fails.
477 	 *
478 	 * XXXMLG The close() calls here are unneeded on all but NetBSD, but
479 	 * are harmless to include everywhere.  dup2() is supposed to close
480 	 * the FD if it is in use, but unproven-pthreads-0.16 is broken
481 	 * and will end up closing the wrong FD.  This will be fixed eventually,
482 	 * and these calls will be removed.
483 	 */
484 	if (devnullfd != -1) {
485 		if (devnullfd != STDIN_FILENO) {
486 			(void)close(STDIN_FILENO);
487 			(void)dup2(devnullfd, STDIN_FILENO);
488 		}
489 		if (devnullfd != STDOUT_FILENO) {
490 			(void)close(STDOUT_FILENO);
491 			(void)dup2(devnullfd, STDOUT_FILENO);
492 		}
493 		if (devnullfd != STDERR_FILENO && !ns_g_keepstderr) {
494 			(void)close(STDERR_FILENO);
495 			(void)dup2(devnullfd, STDERR_FILENO);
496 		}
497 	}
498 }
499 
500 void
501 ns_os_started(void) {
502 	char buf = 0;
503 
504 	/*
505 	 * Signal to the parent that we started successfully.
506 	 */
507 	if (dfd[0] != -1 && dfd[1] != -1) {
508 		if (write(dfd[1], &buf, 1) != 1)
509 			ns_main_earlyfatal("unable to signal parent that we "
510 					   "otherwise started successfully.");
511 		close(dfd[1]);
512 		dfd[0] = dfd[1] = -1;
513 	}
514 }
515 
516 void
517 ns_os_opendevnull(void) {
518 	devnullfd = open("/dev/null", O_RDWR, 0);
519 }
520 
521 void
522 ns_os_closedevnull(void) {
523 	if (devnullfd != STDIN_FILENO &&
524 	    devnullfd != STDOUT_FILENO &&
525 	    devnullfd != STDERR_FILENO) {
526 		close(devnullfd);
527 		devnullfd = -1;
528 	}
529 }
530 
531 static bool
532 all_digits(const char *s) {
533 	if (*s == '\0')
534 		return (false);
535 	while (*s != '\0') {
536 		if (!isdigit((*s)&0xff))
537 			return (false);
538 		s++;
539 	}
540 	return (true);
541 }
542 
543 void
544 ns_os_chroot(const char *root) {
545 	char strbuf[ISC_STRERRORSIZE];
546 #ifdef HAVE_LIBSCF
547 	ns_smf_chroot = 0;
548 #endif
549 	if (root != NULL) {
550 #ifdef HAVE_CHROOT
551 		if (chroot(root) < 0) {
552 			isc__strerror(errno, strbuf, sizeof(strbuf));
553 			ns_main_earlyfatal("chroot(): %s", strbuf);
554 		}
555 #else
556 		ns_main_earlyfatal("chroot(): disabled");
557 #endif
558 		if (chdir("/") < 0) {
559 			isc__strerror(errno, strbuf, sizeof(strbuf));
560 			ns_main_earlyfatal("chdir(/): %s", strbuf);
561 		}
562 #ifdef HAVE_LIBSCF
563 		/* Set ns_smf_chroot flag on successful chroot. */
564 		ns_smf_chroot = 1;
565 #endif
566 	}
567 }
568 
569 void
570 ns_os_inituserinfo(const char *username) {
571 	char strbuf[ISC_STRERRORSIZE];
572 	if (username == NULL)
573 		return;
574 
575 	if (all_digits(username))
576 		runas_pw = getpwuid((uid_t)atoi(username));
577 	else
578 		runas_pw = getpwnam(username);
579 	endpwent();
580 
581 	if (runas_pw == NULL)
582 		ns_main_earlyfatal("user '%s' unknown", username);
583 
584 	if (getuid() == 0) {
585 		if (initgroups(runas_pw->pw_name, runas_pw->pw_gid) < 0) {
586 			isc__strerror(errno, strbuf, sizeof(strbuf));
587 			ns_main_earlyfatal("initgroups(): %s", strbuf);
588 		}
589 	}
590 
591 }
592 
593 void
594 ns_os_changeuser(void) {
595 	char strbuf[ISC_STRERRORSIZE];
596 	if (runas_pw == NULL || done_setuid)
597 		return;
598 
599 	done_setuid = true;
600 
601 #ifdef HAVE_LINUXTHREADS
602 #ifdef HAVE_LINUX_CAPABILITY_H
603 	if (!non_root_caps)
604 		ns_main_earlyfatal("-u with Linux threads not supported: "
605 				   "requires kernel support for "
606 				   "prctl(PR_SET_KEEPCAPS)");
607 #else
608 	ns_main_earlyfatal("-u with Linux threads not supported: "
609 			   "no capabilities support or capabilities "
610 			   "disabled at build time");
611 #endif
612 #endif
613 
614 	if (setgid(runas_pw->pw_gid) < 0) {
615 		isc__strerror(errno, strbuf, sizeof(strbuf));
616 		ns_main_earlyfatal("setgid(): %s", strbuf);
617 	}
618 
619 	if (setuid(runas_pw->pw_uid) < 0) {
620 		isc__strerror(errno, strbuf, sizeof(strbuf));
621 		ns_main_earlyfatal("setuid(): %s", strbuf);
622 	}
623 
624 #if defined(HAVE_SYS_PRCTL_H) && defined(PR_SET_DUMPABLE)
625 	/*
626 	 * Restore the ability of named to drop core after the setuid()
627 	 * call has disabled it.
628 	 */
629 	if (prctl(PR_SET_DUMPABLE,1,0,0,0) < 0) {
630 		isc__strerror(errno, strbuf, sizeof(strbuf));
631 		ns_main_earlywarning("prctl(PR_SET_DUMPABLE) failed: %s",
632 				     strbuf);
633 	}
634 #endif
635 #if defined(HAVE_LINUX_CAPABILITY_H) && !defined(HAVE_LINUXTHREADS)
636 	linux_minprivs();
637 #endif
638 }
639 
640 uid_t
641 ns_os_uid(void) {
642 	if (runas_pw == NULL)
643 		return (0);
644 	return (runas_pw->pw_uid);
645 }
646 
647 void
648 ns_os_adjustnofile(void) {
649 #ifdef HAVE_LINUXTHREADS
650 	isc_result_t result;
651 	isc_resourcevalue_t newvalue;
652 
653 	/*
654 	 * Linux: max number of open files specified by one thread doesn't seem
655 	 * to apply to other threads on Linux.
656 	 */
657 	newvalue = ISC_RESOURCE_UNLIMITED;
658 
659 	result = isc_resource_setlimit(isc_resource_openfiles, newvalue);
660 	if (result != ISC_R_SUCCESS)
661 		ns_main_earlywarning("couldn't adjust limit on open files");
662 #endif
663 }
664 
665 void
666 ns_os_minprivs(void) {
667 #ifdef HAVE_SYS_PRCTL_H
668 	linux_keepcaps();
669 #endif
670 
671 #ifdef HAVE_LINUXTHREADS
672 	ns_os_changeuser(); /* Call setuid() before threads are started */
673 #endif
674 
675 #if defined(HAVE_LINUX_CAPABILITY_H) && defined(HAVE_LINUXTHREADS)
676 	linux_minprivs();
677 #endif
678 }
679 
680 static int
681 safe_open(const char *filename, mode_t mode, bool append) {
682 	int fd;
683 	struct stat sb;
684 
685 	if (stat(filename, &sb) == -1) {
686 		if (errno != ENOENT)
687 			return (-1);
688 	} else if ((sb.st_mode & S_IFREG) == 0) {
689 		errno = EOPNOTSUPP;
690 		return (-1);
691 	}
692 
693 	if (append)
694 		fd = open(filename, O_WRONLY|O_CREAT|O_APPEND, mode);
695 	else {
696 		if (unlink(filename) < 0 && errno != ENOENT)
697 			return (-1);
698 		fd = open(filename, O_WRONLY|O_CREAT|O_EXCL, mode);
699 	}
700 	return (fd);
701 }
702 
703 static void
704 cleanup_pidfile(void) {
705 	int n;
706 	if (pidfile != NULL) {
707 		n = unlink(pidfile);
708 		if (n == -1 && errno != ENOENT)
709 			ns_main_earlywarning("unlink '%s': failed", pidfile);
710 		free(pidfile);
711 	}
712 	pidfile = NULL;
713 }
714 
715 static void
716 cleanup_lockfile(void) {
717 	if (singletonfd != -1) {
718 		close(singletonfd);
719 		singletonfd = -1;
720 	}
721 
722 	if (lockfile != NULL) {
723 		int n = unlink(lockfile);
724 		if (n == -1 && errno != ENOENT)
725 			ns_main_earlywarning("unlink '%s': failed", lockfile);
726 		free(lockfile);
727 		lockfile = NULL;
728 	}
729 }
730 
731 /*
732  * Ensure that a directory exists.
733  * NOTE: This function overwrites the '/' characters in 'filename' with
734  * nulls. The caller should copy the filename to a fresh buffer first.
735  */
736 static int
737 mkdirpath(char *filename, void (*report)(const char *, ...)) {
738 	char *slash = strrchr(filename, '/');
739 	char strbuf[ISC_STRERRORSIZE];
740 	unsigned int mode;
741 
742 	if (slash != NULL && slash != filename) {
743 		struct stat sb;
744 		*slash = '\0';
745 
746 		if (stat(filename, &sb) == -1) {
747 			if (errno != ENOENT) {
748 				isc__strerror(errno, strbuf, sizeof(strbuf));
749 				(*report)("couldn't stat '%s': %s", filename,
750 					  strbuf);
751 				goto error;
752 			}
753 			if (mkdirpath(filename, report) == -1)
754 				goto error;
755 			/*
756 			 * Handle "//", "/./" and "/../" in path.
757 			 */
758 			if (!strcmp(slash + 1, "") ||
759 			    !strcmp(slash + 1, ".") ||
760 			    !strcmp(slash + 1, "..")) {
761 				*slash = '/';
762 				return (0);
763 			}
764 			mode = S_IRUSR | S_IWUSR | S_IXUSR;	/* u=rwx */
765 			mode |= S_IRGRP | S_IXGRP;		/* g=rx */
766 			mode |= S_IROTH | S_IXOTH;		/* o=rx */
767 			if (mkdir(filename, mode) == -1) {
768 				isc__strerror(errno, strbuf, sizeof(strbuf));
769 				(*report)("couldn't mkdir '%s': %s", filename,
770 					  strbuf);
771 				goto error;
772 			}
773 			if (runas_pw != NULL &&
774 			    chown(filename, runas_pw->pw_uid,
775 				  runas_pw->pw_gid) == -1) {
776 				isc__strerror(errno, strbuf, sizeof(strbuf));
777 				(*report)("couldn't chown '%s': %s", filename,
778 					  strbuf);
779 			}
780 		}
781 		*slash = '/';
782 	}
783 	return (0);
784 
785  error:
786 	*slash = '/';
787 	return (-1);
788 }
789 
790 #ifndef HAVE_LINUXTHREADS
791 static void
792 setperms(uid_t uid, gid_t gid) {
793 #if defined(HAVE_SETEGID) || defined(HAVE_SETRESGID)
794 	char strbuf[ISC_STRERRORSIZE];
795 #endif
796 #if !defined(HAVE_SETEGID) && defined(HAVE_SETRESGID)
797 	gid_t oldgid, tmpg;
798 #endif
799 #if !defined(HAVE_SETEUID) && defined(HAVE_SETRESUID)
800 	uid_t olduid, tmpu;
801 #endif
802 #if defined(HAVE_SETEGID)
803 	if (getegid() != gid && setegid(gid) == -1) {
804 		isc__strerror(errno, strbuf, sizeof(strbuf));
805 		ns_main_earlywarning("unable to set effective gid to %ld: %s",
806 				     (long)gid, strbuf);
807 	}
808 #elif defined(HAVE_SETRESGID)
809 	if (getresgid(&tmpg, &oldgid, &tmpg) == -1 || oldgid != gid) {
810 		if (setresgid(-1, gid, -1) == -1) {
811 			isc__strerror(errno, strbuf, sizeof(strbuf));
812 			ns_main_earlywarning("unable to set effective "
813 					     "gid to %d: %s", gid, strbuf);
814 		}
815 	}
816 #endif
817 
818 #if defined(HAVE_SETEUID)
819 	if (geteuid() != uid && seteuid(uid) == -1) {
820 		isc__strerror(errno, strbuf, sizeof(strbuf));
821 		ns_main_earlywarning("unable to set effective uid to %ld: %s",
822 				     (long)uid, strbuf);
823 	}
824 #elif defined(HAVE_SETRESUID)
825 	if (getresuid(&tmpu, &olduid, &tmpu) == -1 || olduid != uid) {
826 		if (setresuid(-1, uid, -1) == -1) {
827 			isc__strerror(errno, strbuf, sizeof(strbuf));
828 			ns_main_earlywarning("unable to set effective "
829 					     "uid to %d: %s", uid, strbuf);
830 		}
831 	}
832 #endif
833 }
834 #endif /* !HAVE_LINUXTHREADS */
835 
836 FILE *
837 ns_os_openfile(const char *filename, mode_t mode, bool switch_user) {
838 	char strbuf[ISC_STRERRORSIZE], *f;
839 	FILE *fp;
840 	int fd;
841 
842 	/*
843 	 * Make the containing directory if it doesn't exist.
844 	 */
845 	f = strdup(filename);
846 	if (f == NULL) {
847 		isc__strerror(errno, strbuf, sizeof(strbuf));
848 		ns_main_earlywarning("couldn't strdup() '%s': %s",
849 				     filename, strbuf);
850 		return (NULL);
851 	}
852 	if (mkdirpath(f, ns_main_earlywarning) == -1) {
853 		free(f);
854 		return (NULL);
855 	}
856 	free(f);
857 
858 	if (switch_user && runas_pw != NULL) {
859 		uid_t olduid = getuid();
860 		gid_t oldgid = getgid();
861 #ifdef HAVE_LINUXTHREADS
862 		REQUIRE(olduid == runas_pw->pw_uid);
863 		REQUIRE(oldgid == runas_pw->pw_gid);
864 #else
865 		/* Set UID/GID to the one we'll be running with eventually */
866 		setperms(runas_pw->pw_uid, runas_pw->pw_gid);
867 #endif
868 		fd = safe_open(filename, mode, false);
869 
870 #ifndef HAVE_LINUXTHREADS
871 		/* Restore UID/GID to root */
872 		setperms(olduid, oldgid);
873 #endif /* HAVE_LINUXTHREADS */
874 
875 		if (fd == -1) {
876 #ifndef HAVE_LINUXTHREADS
877 			fd = safe_open(filename, mode, false);
878 			if (fd != -1) {
879 				ns_main_earlywarning("Required root "
880 						     "permissions to open "
881 						     "'%s'.", filename);
882 			} else {
883 				ns_main_earlywarning("Could not open "
884 						     "'%s'.", filename);
885 			}
886 			ns_main_earlywarning("Please check file and "
887 					     "directory permissions "
888 					     "or reconfigure the filename.");
889 #else /* HAVE_LINUXTHREADS */
890 			ns_main_earlywarning("Could not open "
891 					     "'%s'.", filename);
892 			ns_main_earlywarning("Please check file and "
893 					     "directory permissions "
894 					     "or reconfigure the filename.");
895 #endif /* HAVE_LINUXTHREADS */
896 		}
897 	} else {
898 		fd = safe_open(filename, mode, false);
899 	}
900 
901 	if (fd < 0) {
902 		isc__strerror(errno, strbuf, sizeof(strbuf));
903 		ns_main_earlywarning("could not open file '%s': %s",
904 				     filename, strbuf);
905 		return (NULL);
906 	}
907 
908 	fp = fdopen(fd, "w");
909 	if (fp == NULL) {
910 		isc__strerror(errno, strbuf, sizeof(strbuf));
911 		ns_main_earlywarning("could not fdopen() file '%s': %s",
912 				     filename, strbuf);
913 	}
914 
915 	return (fp);
916 }
917 
918 void
919 ns_os_writepidfile(const char *filename, bool first_time) {
920 	FILE *fh;
921 	pid_t pid;
922 	char strbuf[ISC_STRERRORSIZE];
923 	void (*report)(const char *, ...);
924 
925 	/*
926 	 * The caller must ensure any required synchronization.
927 	 */
928 
929 	report = first_time ? ns_main_earlyfatal : ns_main_earlywarning;
930 
931 	cleanup_pidfile();
932 
933 	if (filename == NULL)
934 		return;
935 
936 	pidfile = strdup(filename);
937 	if (pidfile == NULL) {
938 		isc__strerror(errno, strbuf, sizeof(strbuf));
939 		(*report)("couldn't strdup() '%s': %s", filename, strbuf);
940 		return;
941 	}
942 
943 	fh = ns_os_openfile(filename, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH,
944 				  first_time);
945 	if (fh == NULL) {
946 		cleanup_pidfile();
947 		return;
948 	}
949 #ifdef HAVE_LINUXTHREADS
950 	pid = mainpid;
951 #else
952 	pid = getpid();
953 #endif
954 	if (fprintf(fh, "%ld\n", (long)pid) < 0) {
955 		(*report)("fprintf() to pid file '%s' failed", filename);
956 		(void)fclose(fh);
957 		cleanup_pidfile();
958 		return;
959 	}
960 	if (fflush(fh) == EOF) {
961 		(*report)("fflush() to pid file '%s' failed", filename);
962 		(void)fclose(fh);
963 		cleanup_pidfile();
964 		return;
965 	}
966 	(void)fclose(fh);
967 }
968 
969 bool
970 ns_os_issingleton(const char *filename) {
971 	char strbuf[ISC_STRERRORSIZE];
972 	struct flock lock;
973 
974 	if (singletonfd != -1)
975 		return (true);
976 
977 	if (strcasecmp(filename, "none") == 0)
978 		return (true);
979 
980 	/*
981 	 * Make the containing directory if it doesn't exist.
982 	 */
983 	lockfile = strdup(filename);
984 	if (lockfile == NULL) {
985 		isc__strerror(errno, strbuf, sizeof(strbuf));
986 		ns_main_earlyfatal("couldn't allocate memory for '%s': %s",
987 				   filename, strbuf);
988 	} else {
989 		int ret = mkdirpath(lockfile, ns_main_earlywarning);
990 		if (ret == -1) {
991 			ns_main_earlywarning("couldn't create '%s'", filename);
992 			cleanup_lockfile();
993 			return (false);
994 		}
995 	}
996 
997 	/*
998 	 * ns_os_openfile() uses safeopen() which removes any existing
999 	 * files. We can't use that here.
1000 	 */
1001 	singletonfd = open(filename, O_WRONLY | O_CREAT,
1002 			   S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
1003 	if (singletonfd == -1) {
1004 		cleanup_lockfile();
1005 		return (false);
1006 	}
1007 
1008 	memset(&lock, 0, sizeof(lock));
1009 	lock.l_type = F_WRLCK;
1010 	lock.l_whence = SEEK_SET;
1011 	lock.l_start = 0;
1012 	lock.l_len = 1;
1013 
1014 	/* Non-blocking (does not wait for lock) */
1015 	if (fcntl(singletonfd, F_SETLK, &lock) == -1) {
1016 		close(singletonfd);
1017 		singletonfd = -1;
1018 		return (false);
1019 	}
1020 
1021 	return (true);
1022 }
1023 
1024 void
1025 ns_os_shutdown(void) {
1026 	closelog();
1027 	cleanup_pidfile();
1028 	cleanup_lockfile();
1029 }
1030 
1031 isc_result_t
1032 ns_os_gethostname(char *buf, size_t len) {
1033 	int n;
1034 
1035 	n = gethostname(buf, len);
1036 	return ((n == 0) ? ISC_R_SUCCESS : ISC_R_FAILURE);
1037 }
1038 
1039 static char *
1040 next_token(char **stringp, const char *delim) {
1041 	char *res;
1042 
1043 	do {
1044 		res = strsep(stringp, delim);
1045 		if (res == NULL)
1046 			break;
1047 	} while (*res == '\0');
1048 	return (res);
1049 }
1050 
1051 void
1052 ns_os_shutdownmsg(char *command, isc_buffer_t *text) {
1053 	char *input, *ptr;
1054 	unsigned int n;
1055 	pid_t pid;
1056 
1057 	input = command;
1058 
1059 	/* Skip the command name. */
1060 	ptr = next_token(&input, " \t");
1061 	if (ptr == NULL)
1062 		return;
1063 
1064 	ptr = next_token(&input, " \t");
1065 	if (ptr == NULL)
1066 		return;
1067 
1068 	if (strcmp(ptr, "-p") != 0)
1069 		return;
1070 
1071 #ifdef HAVE_LINUXTHREADS
1072 	pid = mainpid;
1073 #else
1074 	pid = getpid();
1075 #endif
1076 
1077 	n = snprintf((char *)isc_buffer_used(text),
1078 		     isc_buffer_availablelength(text),
1079 		     "pid: %ld", (long)pid);
1080 	/* Only send a message if it is complete. */
1081 	if (n > 0 && n < isc_buffer_availablelength(text))
1082 		isc_buffer_add(text, n);
1083 }
1084 
1085 void
1086 ns_os_tzset(void) {
1087 #ifdef HAVE_TZSET
1088 	tzset();
1089 #endif
1090 }
1091 
1092 #ifdef HAVE_UNAME
1093 static char unamebuf[sizeof(struct utsname)];
1094 #else
1095 static const char unamebuf[] = { "unknown architecture" };
1096 #endif
1097 static const char *unamep = NULL;
1098 
1099 static void
1100 getuname(void) {
1101 #ifdef HAVE_UNAME
1102 	struct utsname uts;
1103 
1104 	memset(&uts, 0, sizeof(uts));
1105 	if (uname(&uts) < 0) {
1106 		snprintf(unamebuf, sizeof(unamebuf), "unknown architecture");
1107 		return;
1108 	}
1109 
1110 	snprintf(unamebuf, sizeof(unamebuf), "%s %s %s %s", uts.sysname,
1111 		 uts.machine, uts.release, uts.version);
1112 #endif /* ifdef HAVE_UNAME */
1113 	unamep = unamebuf;
1114 }
1115 
1116 const char *
1117 ns_os_uname(void) {
1118 	if (unamep == NULL) {
1119 		getuname();
1120 	}
1121 	return (unamep);
1122 }
1123