xref: /netbsd-src/external/mpl/bind/dist/bin/named/os.c (revision b2c35e17b976cf7ccd7250c86c6f5e95090ed636)
1 /*	$NetBSD: os.c,v 1.2 2024/02/21 22:51:05 christos Exp $	*/
2 
3 /*
4  * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
5  *
6  * SPDX-License-Identifier: MPL-2.0
7  *
8  * This Source Code Form is subject to the terms of the Mozilla Public
9  * License, v. 2.0. If a copy of the MPL was not distributed with this
10  * file, you can obtain one at https://mozilla.org/MPL/2.0/.
11  *
12  * See the COPYRIGHT file distributed with this work for additional
13  * information regarding copyright ownership.
14  */
15 
16 /*! \file */
17 #include <stdarg.h>
18 #include <stdbool.h>
19 #include <sys/stat.h>
20 #include <sys/types.h> /* dev_t FreeBSD 2.1 */
21 #ifdef HAVE_UNAME
22 #include <sys/utsname.h>
23 #endif /* ifdef HAVE_UNAME */
24 
25 #include <ctype.h>
26 #include <errno.h>
27 #include <fcntl.h>
28 #include <grp.h>
29 #include <pwd.h>
30 #include <signal.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <syslog.h>
34 #ifdef HAVE_TZSET
35 #include <time.h>
36 #endif /* ifdef HAVE_TZSET */
37 #include <unistd.h>
38 
39 #include <isc/buffer.h>
40 #include <isc/file.h>
41 #include <isc/print.h>
42 #include <isc/resource.h>
43 #include <isc/result.h>
44 #include <isc/strerr.h>
45 #include <isc/string.h>
46 #include <isc/util.h>
47 
48 #include <named/globals.h>
49 #include <named/main.h>
50 #include <named/os.h>
51 #ifdef HAVE_LIBSCF
52 #include <named/smf_globals.h>
53 #endif /* ifdef HAVE_LIBSCF */
54 
55 static char *pidfile = NULL;
56 static char *lockfile = NULL;
57 static int devnullfd = -1;
58 static int singletonfd = -1;
59 
60 #ifndef ISC_FACILITY
61 #define ISC_FACILITY LOG_DAEMON
62 #endif /* ifndef ISC_FACILITY */
63 
64 static struct passwd *runas_pw = NULL;
65 static bool done_setuid = false;
66 static int dfd[2] = { -1, -1 };
67 
68 #ifdef HAVE_SYS_CAPABILITY_H
69 
70 static bool non_root = false;
71 static bool non_root_caps = false;
72 
73 #include <sys/capability.h>
74 #include <sys/prctl.h>
75 
76 static void
77 linux_setcaps(cap_t caps) {
78 	char strbuf[ISC_STRERRORSIZE];
79 
80 	if ((getuid() != 0 && !non_root_caps) || non_root) {
81 		return;
82 	}
83 	if (cap_set_proc(caps) < 0) {
84 		strerror_r(errno, strbuf, sizeof(strbuf));
85 		named_main_earlyfatal("cap_set_proc() failed: %s:"
86 				      " please ensure that the capset kernel"
87 				      " module is loaded.  see insmod(8)",
88 				      strbuf);
89 	}
90 }
91 
92 #define SET_CAP(flag)                                                         \
93 	do {                                                                  \
94 		cap_flag_value_t curval;                                      \
95 		capval = (flag);                                              \
96 		err = cap_get_flag(curcaps, capval, CAP_PERMITTED, &curval);  \
97 		if (err != -1 && curval) {                                    \
98 			err = cap_set_flag(caps, CAP_EFFECTIVE, 1, &capval,   \
99 					   CAP_SET);                          \
100 			if (err == -1) {                                      \
101 				strerror_r(errno, strbuf, sizeof(strbuf));    \
102 				named_main_earlyfatal("cap_set_proc failed: " \
103 						      "%s",                   \
104 						      strbuf);                \
105 			}                                                     \
106                                                                               \
107 			err = cap_set_flag(caps, CAP_PERMITTED, 1, &capval,   \
108 					   CAP_SET);                          \
109 			if (err == -1) {                                      \
110 				strerror_r(errno, strbuf, sizeof(strbuf));    \
111 				named_main_earlyfatal("cap_set_proc failed: " \
112 						      "%s",                   \
113 						      strbuf);                \
114 			}                                                     \
115 		}                                                             \
116 	} while (0)
117 #define INIT_CAP                                                              \
118 	do {                                                                  \
119 		caps = cap_init();                                            \
120 		if (caps == NULL) {                                           \
121 			strerror_r(errno, strbuf, sizeof(strbuf));            \
122 			named_main_earlyfatal("cap_init failed: %s", strbuf); \
123 		}                                                             \
124 		curcaps = cap_get_proc();                                     \
125 		if (curcaps == NULL) {                                        \
126 			strerror_r(errno, strbuf, sizeof(strbuf));            \
127 			named_main_earlyfatal("cap_get_proc failed: %s",      \
128 					      strbuf);                        \
129 		}                                                             \
130 	} while (0)
131 #define FREE_CAP                   \
132 	{                          \
133 		cap_free(caps);    \
134 		cap_free(curcaps); \
135 	}                          \
136 	while (0)
137 
138 static void
139 linux_initialprivs(void) {
140 	cap_t caps;
141 	cap_t curcaps;
142 	cap_value_t capval;
143 	char strbuf[ISC_STRERRORSIZE];
144 	int err;
145 
146 	/*%
147 	 * We don't need most privileges, so we drop them right away.
148 	 * Later on linux_minprivs() will be called, which will drop our
149 	 * capabilities to the minimum needed to run the server.
150 	 */
151 	INIT_CAP;
152 
153 	/*
154 	 * We need to be able to bind() to privileged ports, notably port 53!
155 	 */
156 	SET_CAP(CAP_NET_BIND_SERVICE);
157 
158 	/*
159 	 * We need chroot() initially too.
160 	 */
161 	SET_CAP(CAP_SYS_CHROOT);
162 
163 	/*
164 	 * We need setuid() as the kernel supports keeping capabilities after
165 	 * setuid().
166 	 */
167 	SET_CAP(CAP_SETUID);
168 
169 	/*
170 	 * Since we call initgroups, we need this.
171 	 */
172 	SET_CAP(CAP_SETGID);
173 
174 	/*
175 	 * Without this, we run into problems reading a configuration file
176 	 * owned by a non-root user and non-world-readable on startup.
177 	 */
178 	SET_CAP(CAP_DAC_READ_SEARCH);
179 
180 	/*
181 	 * XXX  We might want to add CAP_SYS_RESOURCE, though it's not
182 	 *      clear it would work right given the way linuxthreads work.
183 	 * XXXDCL But since we need to be able to set the maximum number
184 	 * of files, the stack size, data size, and core dump size to
185 	 * support named.conf options, this is now being added to test.
186 	 */
187 	SET_CAP(CAP_SYS_RESOURCE);
188 
189 	/*
190 	 * We need to be able to set the ownership of the containing
191 	 * directory of the pid file when we create it.
192 	 */
193 	SET_CAP(CAP_CHOWN);
194 
195 	linux_setcaps(caps);
196 
197 	FREE_CAP;
198 }
199 
200 static void
201 linux_minprivs(void) {
202 	cap_t caps;
203 	cap_t curcaps;
204 	cap_value_t capval;
205 	char strbuf[ISC_STRERRORSIZE];
206 	int err;
207 
208 	INIT_CAP;
209 	/*%
210 	 * Drop all privileges except the ability to bind() to privileged
211 	 * ports.
212 	 *
213 	 * It's important that we drop CAP_SYS_CHROOT.  If we didn't, it
214 	 * chroot() could be used to escape from the chrooted area.
215 	 */
216 
217 	SET_CAP(CAP_NET_BIND_SERVICE);
218 
219 	/*
220 	 * XXX  We might want to add CAP_SYS_RESOURCE, though it's not
221 	 *      clear it would work right given the way linuxthreads work.
222 	 * XXXDCL But since we need to be able to set the maximum number
223 	 * of files, the stack size, data size, and core dump size to
224 	 * support named.conf options, this is now being added to test.
225 	 */
226 	SET_CAP(CAP_SYS_RESOURCE);
227 
228 	linux_setcaps(caps);
229 
230 	FREE_CAP;
231 }
232 
233 static void
234 linux_keepcaps(void) {
235 	char strbuf[ISC_STRERRORSIZE];
236 	/*%
237 	 * Ask the kernel to allow us to keep our capabilities after we
238 	 * setuid().
239 	 */
240 
241 	if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) < 0) {
242 		if (errno != EINVAL) {
243 			strerror_r(errno, strbuf, sizeof(strbuf));
244 			named_main_earlyfatal("prctl() failed: %s", strbuf);
245 		}
246 	} else {
247 		non_root_caps = true;
248 		if (getuid() != 0) {
249 			non_root = true;
250 		}
251 	}
252 }
253 
254 #endif /* HAVE_SYS_CAPABILITY_H */
255 
256 static void
257 setup_syslog(const char *progname) {
258 	int options;
259 
260 	options = LOG_PID;
261 #ifdef LOG_NDELAY
262 	options |= LOG_NDELAY;
263 #endif /* ifdef LOG_NDELAY */
264 	openlog(isc_file_basename(progname), options, ISC_FACILITY);
265 }
266 
267 void
268 named_os_init(const char *progname) {
269 	setup_syslog(progname);
270 #ifdef HAVE_SYS_CAPABILITY_H
271 	linux_initialprivs();
272 #endif /* ifdef HAVE_SYS_CAPABILITY_H */
273 #ifdef SIGXFSZ
274 	signal(SIGXFSZ, SIG_IGN);
275 #endif /* ifdef SIGXFSZ */
276 }
277 
278 void
279 named_os_daemonize(void) {
280 	pid_t pid;
281 	char strbuf[ISC_STRERRORSIZE];
282 
283 	if (pipe(dfd) == -1) {
284 		strerror_r(errno, strbuf, sizeof(strbuf));
285 		named_main_earlyfatal("pipe(): %s", strbuf);
286 	}
287 
288 	pid = fork();
289 	if (pid == -1) {
290 		strerror_r(errno, strbuf, sizeof(strbuf));
291 		named_main_earlyfatal("fork(): %s", strbuf);
292 	}
293 	if (pid != 0) {
294 		int n;
295 		/*
296 		 * Wait for the child to finish loading for the first time.
297 		 * This would be so much simpler if fork() worked once we
298 		 * were multi-threaded.
299 		 */
300 		(void)close(dfd[1]);
301 		do {
302 			char buf;
303 			n = read(dfd[0], &buf, 1);
304 			if (n == 1) {
305 				_exit(0);
306 			}
307 		} while (n == -1 && errno == EINTR);
308 		_exit(1);
309 	}
310 	(void)close(dfd[0]);
311 
312 	/*
313 	 * We're the child.
314 	 */
315 
316 	if (setsid() == -1) {
317 		strerror_r(errno, strbuf, sizeof(strbuf));
318 		named_main_earlyfatal("setsid(): %s", strbuf);
319 	}
320 
321 	/*
322 	 * Try to set stdin, stdout, and stderr to /dev/null, but press
323 	 * on even if it fails.
324 	 *
325 	 * XXXMLG The close() calls here are unneeded on all but NetBSD, but
326 	 * are harmless to include everywhere.  dup2() is supposed to close
327 	 * the FD if it is in use, but unproven-pthreads-0.16 is broken
328 	 * and will end up closing the wrong FD.  This will be fixed eventually,
329 	 * and these calls will be removed.
330 	 */
331 	if (devnullfd != -1) {
332 		if (devnullfd != STDIN_FILENO) {
333 			(void)close(STDIN_FILENO);
334 			(void)dup2(devnullfd, STDIN_FILENO);
335 		}
336 		if (devnullfd != STDOUT_FILENO) {
337 			(void)close(STDOUT_FILENO);
338 			(void)dup2(devnullfd, STDOUT_FILENO);
339 		}
340 		if (devnullfd != STDERR_FILENO && !named_g_keepstderr) {
341 			(void)close(STDERR_FILENO);
342 			(void)dup2(devnullfd, STDERR_FILENO);
343 		}
344 	}
345 }
346 
347 void
348 named_os_started(void) {
349 	char buf = 0;
350 
351 	/*
352 	 * Signal to the parent that we started successfully.
353 	 */
354 	if (dfd[0] != -1 && dfd[1] != -1) {
355 		if (write(dfd[1], &buf, 1) != 1) {
356 			named_main_earlyfatal("unable to signal parent that we "
357 					      "otherwise started "
358 					      "successfully.");
359 		}
360 		close(dfd[1]);
361 		dfd[0] = dfd[1] = -1;
362 	}
363 }
364 
365 void
366 named_os_opendevnull(void) {
367 	devnullfd = open("/dev/null", O_RDWR, 0);
368 }
369 
370 void
371 named_os_closedevnull(void) {
372 	if (devnullfd != STDIN_FILENO && devnullfd != STDOUT_FILENO &&
373 	    devnullfd != STDERR_FILENO)
374 	{
375 		close(devnullfd);
376 		devnullfd = -1;
377 	}
378 }
379 
380 static bool
381 all_digits(const char *s) {
382 	if (*s == '\0') {
383 		return (false);
384 	}
385 	while (*s != '\0') {
386 		if (!isdigit((unsigned char)(*s))) {
387 			return (false);
388 		}
389 		s++;
390 	}
391 	return (true);
392 }
393 
394 void
395 named_os_chroot(const char *root) {
396 	char strbuf[ISC_STRERRORSIZE];
397 #ifdef HAVE_LIBSCF
398 	named_smf_chroot = 0;
399 #endif /* ifdef HAVE_LIBSCF */
400 	if (root != NULL) {
401 #ifdef HAVE_CHROOT
402 		if (chroot(root) < 0) {
403 			strerror_r(errno, strbuf, sizeof(strbuf));
404 			named_main_earlyfatal("chroot(): %s", strbuf);
405 		}
406 #else  /* ifdef HAVE_CHROOT */
407 		named_main_earlyfatal("chroot(): disabled");
408 #endif /* ifdef HAVE_CHROOT */
409 		if (chdir("/") < 0) {
410 			strerror_r(errno, strbuf, sizeof(strbuf));
411 			named_main_earlyfatal("chdir(/): %s", strbuf);
412 		}
413 #ifdef HAVE_LIBSCF
414 		/* Set named_smf_chroot flag on successful chroot. */
415 		named_smf_chroot = 1;
416 #endif /* ifdef HAVE_LIBSCF */
417 	}
418 }
419 
420 void
421 named_os_inituserinfo(const char *username) {
422 	if (username == NULL) {
423 		return;
424 	}
425 
426 	if (all_digits(username)) {
427 		runas_pw = getpwuid((uid_t)atoi(username));
428 	} else {
429 		runas_pw = getpwnam(username);
430 	}
431 	endpwent();
432 
433 	if (runas_pw == NULL) {
434 		named_main_earlyfatal("user '%s' unknown", username);
435 	}
436 
437 	if (getuid() == 0) {
438 		char strbuf[ISC_STRERRORSIZE];
439 		if (initgroups(runas_pw->pw_name, runas_pw->pw_gid) < 0) {
440 			strerror_r(errno, strbuf, sizeof(strbuf));
441 			named_main_earlyfatal("initgroups(): %s", strbuf);
442 		}
443 	}
444 }
445 
446 void
447 named_os_changeuser(void) {
448 	char strbuf[ISC_STRERRORSIZE];
449 	if (runas_pw == NULL || done_setuid) {
450 		return;
451 	}
452 
453 	done_setuid = true;
454 
455 	if (setgid(runas_pw->pw_gid) < 0) {
456 		strerror_r(errno, strbuf, sizeof(strbuf));
457 		named_main_earlyfatal("setgid(): %s", strbuf);
458 	}
459 
460 	if (setuid(runas_pw->pw_uid) < 0) {
461 		strerror_r(errno, strbuf, sizeof(strbuf));
462 		named_main_earlyfatal("setuid(): %s", strbuf);
463 	}
464 
465 #if defined(HAVE_SYS_CAPABILITY_H)
466 	/*
467 	 * Restore the ability of named to drop core after the setuid()
468 	 * call has disabled it.
469 	 */
470 	if (prctl(PR_SET_DUMPABLE, 1, 0, 0, 0) < 0) {
471 		strerror_r(errno, strbuf, sizeof(strbuf));
472 		named_main_earlywarning("prctl(PR_SET_DUMPABLE) failed: %s",
473 					strbuf);
474 	}
475 
476 	linux_minprivs();
477 #endif /* if defined(HAVE_SYS_CAPABILITY_H) */
478 }
479 
480 uid_t
481 ns_os_uid(void) {
482 	if (runas_pw == NULL) {
483 		return (0);
484 	}
485 	return (runas_pw->pw_uid);
486 }
487 
488 void
489 named_os_adjustnofile(void) {
490 #if defined(__linux__) || defined(__sun)
491 	isc_result_t result;
492 	isc_resourcevalue_t newvalue;
493 
494 	/*
495 	 * Linux: max number of open files specified by one thread doesn't seem
496 	 * to apply to other threads on Linux.
497 	 * Sun: restriction needs to be removed sooner when hundreds of CPUs
498 	 * are available.
499 	 */
500 	newvalue = ISC_RESOURCE_UNLIMITED;
501 
502 	result = isc_resource_setlimit(isc_resource_openfiles, newvalue);
503 	if (result != ISC_R_SUCCESS) {
504 		named_main_earlywarning("couldn't adjust limit on open files");
505 	}
506 #endif /* if defined(__linux__) || defined(__sun) */
507 }
508 
509 void
510 named_os_minprivs(void) {
511 #if defined(HAVE_SYS_CAPABILITY_H)
512 	linux_keepcaps();
513 	named_os_changeuser();
514 	linux_minprivs();
515 #endif /* if defined(HAVE_SYS_CAPABILITY_H) */
516 }
517 
518 static int
519 safe_open(const char *filename, mode_t mode, bool append) {
520 	int fd;
521 	struct stat sb;
522 
523 	if (stat(filename, &sb) == -1) {
524 		if (errno != ENOENT) {
525 			return (-1);
526 		}
527 	} else if ((sb.st_mode & S_IFREG) == 0) {
528 		errno = EOPNOTSUPP;
529 		return (-1);
530 	}
531 
532 	if (append) {
533 		fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, mode);
534 	} else {
535 		if (unlink(filename) < 0 && errno != ENOENT) {
536 			return (-1);
537 		}
538 		fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, mode);
539 	}
540 	return (fd);
541 }
542 
543 static void
544 cleanup_pidfile(void) {
545 	int n;
546 	if (pidfile != NULL) {
547 		n = unlink(pidfile);
548 		if (n == -1 && errno != ENOENT) {
549 			named_main_earlywarning("unlink '%s': failed", pidfile);
550 		}
551 		free(pidfile);
552 	}
553 	pidfile = NULL;
554 }
555 
556 static void
557 cleanup_lockfile(bool unlink_lockfile) {
558 	if (singletonfd != -1) {
559 		close(singletonfd);
560 		singletonfd = -1;
561 	}
562 
563 	if (lockfile != NULL) {
564 		if (unlink_lockfile) {
565 			int n = unlink(lockfile);
566 			if (n == -1 && errno != ENOENT) {
567 				named_main_earlywarning("unlink '%s': failed",
568 							lockfile);
569 			}
570 		}
571 		free(lockfile);
572 		lockfile = NULL;
573 	}
574 }
575 
576 /*
577  * Ensure that a directory exists.
578  * NOTE: This function overwrites the '/' characters in 'filename' with
579  * nulls. The caller should copy the filename to a fresh buffer first.
580  */
581 static int
582 mkdirpath(char *filename, void (*report)(const char *, ...)) {
583 	char *slash = strrchr(filename, '/');
584 	char strbuf[ISC_STRERRORSIZE];
585 	unsigned int mode;
586 
587 	if (slash != NULL && slash != filename) {
588 		struct stat sb;
589 		*slash = '\0';
590 
591 		if (stat(filename, &sb) == -1) {
592 			if (errno != ENOENT) {
593 				strerror_r(errno, strbuf, sizeof(strbuf));
594 				(*report)("couldn't stat '%s': %s", filename,
595 					  strbuf);
596 				goto error;
597 			}
598 			if (mkdirpath(filename, report) == -1) {
599 				goto error;
600 			}
601 			/*
602 			 * Handle "//", "/./" and "/../" in path.
603 			 */
604 			if (!strcmp(slash + 1, "") || !strcmp(slash + 1, ".") ||
605 			    !strcmp(slash + 1, ".."))
606 			{
607 				*slash = '/';
608 				return (0);
609 			}
610 			mode = S_IRUSR | S_IWUSR | S_IXUSR; /* u=rwx */
611 			mode |= S_IRGRP | S_IXGRP;	    /* g=rx */
612 			mode |= S_IROTH | S_IXOTH;	    /* o=rx */
613 			if (mkdir(filename, mode) == -1) {
614 				strerror_r(errno, strbuf, sizeof(strbuf));
615 				(*report)("couldn't mkdir '%s': %s", filename,
616 					  strbuf);
617 				goto error;
618 			}
619 			if (runas_pw != NULL &&
620 			    chown(filename, runas_pw->pw_uid,
621 				  runas_pw->pw_gid) == -1)
622 			{
623 				strerror_r(errno, strbuf, sizeof(strbuf));
624 				(*report)("couldn't chown '%s': %s", filename,
625 					  strbuf);
626 			}
627 		}
628 		*slash = '/';
629 	}
630 	return (0);
631 
632 error:
633 	*slash = '/';
634 	return (-1);
635 }
636 
637 #if !HAVE_SYS_CAPABILITY_H
638 static void
639 setperms(uid_t uid, gid_t gid) {
640 #if defined(HAVE_SETEGID) || defined(HAVE_SETRESGID)
641 	char strbuf[ISC_STRERRORSIZE];
642 #endif /* if defined(HAVE_SETEGID) || defined(HAVE_SETRESGID) */
643 #if !defined(HAVE_SETEGID) && defined(HAVE_SETRESGID)
644 	gid_t oldgid, tmpg;
645 #endif /* if !defined(HAVE_SETEGID) && defined(HAVE_SETRESGID) */
646 #if !defined(HAVE_SETEUID) && defined(HAVE_SETRESUID)
647 	uid_t olduid, tmpu;
648 #endif /* if !defined(HAVE_SETEUID) && defined(HAVE_SETRESUID) */
649 #if defined(HAVE_SETEGID)
650 	if (getegid() != gid && setegid(gid) == -1) {
651 		strerror_r(errno, strbuf, sizeof(strbuf));
652 		named_main_earlywarning("unable to set effective "
653 					"gid to %ld: %s",
654 					(long)gid, strbuf);
655 	}
656 #elif defined(HAVE_SETRESGID)
657 	if (getresgid(&tmpg, &oldgid, &tmpg) == -1 || oldgid != gid) {
658 		if (setresgid(-1, gid, -1) == -1) {
659 			strerror_r(errno, strbuf, sizeof(strbuf));
660 			named_main_earlywarning("unable to set effective "
661 						"gid to %d: %s",
662 						gid, strbuf);
663 		}
664 	}
665 #endif /* if defined(HAVE_SETEGID) */
666 
667 #if defined(HAVE_SETEUID)
668 	if (geteuid() != uid && seteuid(uid) == -1) {
669 		strerror_r(errno, strbuf, sizeof(strbuf));
670 		named_main_earlywarning("unable to set effective "
671 					"uid to %ld: %s",
672 					(long)uid, strbuf);
673 	}
674 #elif defined(HAVE_SETRESUID)
675 	if (getresuid(&tmpu, &olduid, &tmpu) == -1 || olduid != uid) {
676 		if (setresuid(-1, uid, -1) == -1) {
677 			strerror_r(errno, strbuf, sizeof(strbuf));
678 			named_main_earlywarning("unable to set effective "
679 						"uid to %d: %s",
680 						uid, strbuf);
681 		}
682 	}
683 #endif /* if defined(HAVE_SETEUID) */
684 }
685 #endif /* !HAVE_SYS_CAPABILITY_H */
686 
687 FILE *
688 named_os_openfile(const char *filename, mode_t mode, bool switch_user) {
689 	char strbuf[ISC_STRERRORSIZE], *f;
690 	FILE *fp;
691 	int fd;
692 
693 	/*
694 	 * Make the containing directory if it doesn't exist.
695 	 */
696 	f = strdup(filename);
697 	if (f == NULL) {
698 		strerror_r(errno, strbuf, sizeof(strbuf));
699 		named_main_earlywarning("couldn't strdup() '%s': %s", filename,
700 					strbuf);
701 		return (NULL);
702 	}
703 	if (mkdirpath(f, named_main_earlywarning) == -1) {
704 		free(f);
705 		return (NULL);
706 	}
707 	free(f);
708 
709 	if (switch_user && runas_pw != NULL) {
710 		uid_t olduid = getuid();
711 		gid_t oldgid = getgid();
712 #if HAVE_SYS_CAPABILITY_H
713 		REQUIRE(olduid == runas_pw->pw_uid);
714 		REQUIRE(oldgid == runas_pw->pw_gid);
715 #else /* HAVE_SYS_CAPABILITY_H */
716 		/* Set UID/GID to the one we'll be running with eventually */
717 		setperms(runas_pw->pw_uid, runas_pw->pw_gid);
718 #endif
719 		fd = safe_open(filename, mode, false);
720 
721 #if !HAVE_SYS_CAPABILITY_H
722 		/* Restore UID/GID to previous uid/gid */
723 		setperms(olduid, oldgid);
724 #endif
725 
726 		if (fd == -1) {
727 			fd = safe_open(filename, mode, false);
728 			if (fd != -1) {
729 				named_main_earlywarning("Required root "
730 							"permissions to open "
731 							"'%s'.",
732 							filename);
733 			} else {
734 				named_main_earlywarning("Could not open "
735 							"'%s'.",
736 							filename);
737 			}
738 			named_main_earlywarning("Please check file and "
739 						"directory permissions "
740 						"or reconfigure the filename.");
741 		}
742 	} else {
743 		fd = safe_open(filename, mode, false);
744 	}
745 
746 	if (fd < 0) {
747 		strerror_r(errno, strbuf, sizeof(strbuf));
748 		named_main_earlywarning("could not open file '%s': %s",
749 					filename, strbuf);
750 		return (NULL);
751 	}
752 
753 	fp = fdopen(fd, "w");
754 	if (fp == NULL) {
755 		strerror_r(errno, strbuf, sizeof(strbuf));
756 		named_main_earlywarning("could not fdopen() file '%s': %s",
757 					filename, strbuf);
758 	}
759 
760 	return (fp);
761 }
762 
763 void
764 named_os_writepidfile(const char *filename, bool first_time) {
765 	FILE *fh;
766 	pid_t pid;
767 	char strbuf[ISC_STRERRORSIZE];
768 	void (*report)(const char *, ...);
769 
770 	/*
771 	 * The caller must ensure any required synchronization.
772 	 */
773 
774 	report = first_time ? named_main_earlyfatal : named_main_earlywarning;
775 
776 	cleanup_pidfile();
777 
778 	if (filename == NULL) {
779 		return;
780 	}
781 
782 	pidfile = strdup(filename);
783 	if (pidfile == NULL) {
784 		strerror_r(errno, strbuf, sizeof(strbuf));
785 		(*report)("couldn't strdup() '%s': %s", filename, strbuf);
786 		return;
787 	}
788 
789 	fh = named_os_openfile(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH,
790 			       first_time);
791 	if (fh == NULL) {
792 		cleanup_pidfile();
793 		return;
794 	}
795 	pid = getpid();
796 	if (fprintf(fh, "%ld\n", (long)pid) < 0) {
797 		(*report)("fprintf() to pid file '%s' failed", filename);
798 		(void)fclose(fh);
799 		cleanup_pidfile();
800 		return;
801 	}
802 	if (fflush(fh) == EOF) {
803 		(*report)("fflush() to pid file '%s' failed", filename);
804 		(void)fclose(fh);
805 		cleanup_pidfile();
806 		return;
807 	}
808 	(void)fclose(fh);
809 }
810 
811 bool
812 named_os_issingleton(const char *filename) {
813 	char strbuf[ISC_STRERRORSIZE];
814 	struct flock lock;
815 
816 	if (singletonfd != -1) {
817 		return (true);
818 	}
819 
820 	if (strcasecmp(filename, "none") == 0) {
821 		return (true);
822 	}
823 
824 	/*
825 	 * Make the containing directory if it doesn't exist.
826 	 */
827 	lockfile = strdup(filename);
828 	if (lockfile == NULL) {
829 		strerror_r(errno, strbuf, sizeof(strbuf));
830 		named_main_earlyfatal("couldn't allocate memory for '%s': %s",
831 				      filename, strbuf);
832 	} else {
833 		int ret = mkdirpath(lockfile, named_main_earlywarning);
834 		if (ret == -1) {
835 			named_main_earlywarning("couldn't create '%s'",
836 						filename);
837 			cleanup_lockfile(false);
838 			return (false);
839 		}
840 	}
841 
842 	/*
843 	 * named_os_openfile() uses safeopen() which removes any existing
844 	 * files. We can't use that here.
845 	 */
846 	singletonfd = open(filename, O_WRONLY | O_CREAT,
847 			   S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
848 	if (singletonfd == -1) {
849 		cleanup_lockfile(false);
850 		return (false);
851 	}
852 
853 	memset(&lock, 0, sizeof(lock));
854 	lock.l_type = F_WRLCK;
855 	lock.l_whence = SEEK_SET;
856 	lock.l_start = 0;
857 	lock.l_len = 1;
858 
859 	/* Non-blocking (does not wait for lock) */
860 	if (fcntl(singletonfd, F_SETLK, &lock) == -1) {
861 		cleanup_lockfile(false);
862 		return (false);
863 	}
864 
865 	return (true);
866 }
867 
868 void
869 named_os_shutdown(void) {
870 	closelog();
871 	cleanup_pidfile();
872 	cleanup_lockfile(true);
873 }
874 
875 void
876 named_os_shutdownmsg(char *command, isc_buffer_t *text) {
877 	char *last, *ptr;
878 	pid_t pid;
879 
880 	/* Skip the command name. */
881 	if (strtok_r(command, " \t", &last) == NULL) {
882 		return;
883 	}
884 
885 	if ((ptr = strtok_r(NULL, " \t", &last)) == NULL) {
886 		return;
887 	}
888 
889 	if (strcmp(ptr, "-p") != 0) {
890 		return;
891 	}
892 
893 	pid = getpid();
894 
895 	(void)isc_buffer_printf(text, "pid: %ld", (long)pid);
896 }
897 
898 void
899 named_os_tzset(void) {
900 #ifdef HAVE_TZSET
901 	tzset();
902 #endif /* ifdef HAVE_TZSET */
903 }
904 
905 #ifdef HAVE_UNAME
906 static char unamebuf[sizeof(struct utsname)];
907 #else
908 static const char unamebuf[] = { "unknown architecture" };
909 #endif
910 static const char *unamep = NULL;
911 
912 static void
913 getuname(void) {
914 #ifdef HAVE_UNAME
915 	struct utsname uts;
916 
917 	memset(&uts, 0, sizeof(uts));
918 	if (uname(&uts) < 0) {
919 		snprintf(unamebuf, sizeof(unamebuf), "unknown architecture");
920 		return;
921 	}
922 
923 	snprintf(unamebuf, sizeof(unamebuf), "%s %s %s %s", uts.sysname,
924 		 uts.machine, uts.release, uts.version);
925 #endif /* ifdef HAVE_UNAME */
926 	unamep = unamebuf;
927 }
928 
929 const char *
930 named_os_uname(void) {
931 	if (unamep == NULL) {
932 		getuname();
933 	}
934 	return (unamep);
935 }
936