os.c revision 165071
1/*
2 * Copyright (C) 2004-2006  Internet Systems Consortium, Inc. ("ISC")
3 * Copyright (C) 1999-2002  Internet Software Consortium.
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
10 * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11 * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
12 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
14 * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15 * PERFORMANCE OF THIS SOFTWARE.
16 */
17
18/* $Id: os.c,v 1.46.2.4.8.24 2006/02/03 23:51:37 marka Exp $ */
19
20#include <config.h>
21#include <stdarg.h>
22
23#include <sys/types.h>	/* dev_t FreeBSD 2.1 */
24#include <sys/stat.h>
25
26#include <ctype.h>
27#include <errno.h>
28#include <fcntl.h>
29#include <grp.h>		/* Required for initgroups() on IRIX. */
30#include <pwd.h>
31#include <stdio.h>
32#include <stdlib.h>
33#include <signal.h>
34#include <syslog.h>
35#ifdef HAVE_TZSET
36#include <time.h>
37#endif
38#include <unistd.h>
39
40#include <isc/buffer.h>
41#include <isc/file.h>
42#include <isc/print.h>
43#include <isc/result.h>
44#include <isc/strerror.h>
45#include <isc/string.h>
46
47#include <named/main.h>
48#include <named/os.h>
49#ifdef HAVE_LIBSCF
50#include <named/ns_smf_globals.h>
51#endif
52
53static char *pidfile = NULL;
54static int devnullfd = -1;
55
56#ifndef ISC_FACILITY
57#define ISC_FACILITY LOG_DAEMON
58#endif
59
60/*
61 * If there's no <linux/capability.h>, we don't care about <sys/prctl.h>
62 */
63#ifndef HAVE_LINUX_CAPABILITY_H
64#undef HAVE_SYS_PRCTL_H
65#endif
66
67/*
68 * Linux defines:
69 *	(T) HAVE_LINUXTHREADS
70 *	(C) HAVE_LINUX_CAPABILITY_H
71 *	(P) HAVE_SYS_PRCTL_H
72 * The possible cases are:
73 *	none:	setuid() normally
74 *	T:	no setuid()
75 *	C:	setuid() normally, drop caps (keep CAP_SETUID)
76 *	T+C:	no setuid(), drop caps (don't keep CAP_SETUID)
77 *	T+C+P:	setuid() early, drop caps (keep CAP_SETUID)
78 *	C+P:	setuid() normally, drop caps (keep CAP_SETUID)
79 *	P:	not possible
80 *	T+P:	not possible
81 *
82 * if (C)
83 *	caps = BIND_SERVICE + CHROOT + SETGID
84 *	if ((T && C && P) || !T)
85 *		caps += SETUID
86 *	endif
87 *	capset(caps)
88 * endif
89 * if (T && C && P && -u)
90 *	setuid()
91 * else if (T && -u)
92 *	fail
93 * --> start threads
94 * if (!T && -u)
95 *	setuid()
96 * if (C && (P || !-u))
97 *	caps = BIND_SERVICE
98 *	capset(caps)
99 * endif
100 *
101 * It will be nice when Linux threads work properly with setuid().
102 */
103
104#ifdef HAVE_LINUXTHREADS
105static pid_t mainpid = 0;
106#endif
107
108static struct passwd *runas_pw = NULL;
109static isc_boolean_t done_setuid = ISC_FALSE;
110static int dfd[2] = { -1, -1 };
111
112#ifdef HAVE_LINUX_CAPABILITY_H
113
114static isc_boolean_t non_root = ISC_FALSE;
115static isc_boolean_t non_root_caps = ISC_FALSE;
116
117/*
118 * We define _LINUX_FS_H to prevent it from being included.  We don't need
119 * anything from it, and the files it includes cause warnings with 2.2
120 * kernels, and compilation failures (due to conflicts between <linux/string.h>
121 * and <string.h>) on 2.3 kernels.
122 */
123#define _LINUX_FS_H
124
125#include <sys/syscall.h>	/* Required for syscall(). */
126#include <linux/capability.h>	/* Required for _LINUX_CAPABILITY_VERSION. */
127
128#ifdef HAVE_SYS_PRCTL_H
129#include <sys/prctl.h>		/* Required for prctl(). */
130
131/*
132 * If the value of PR_SET_KEEPCAPS is not in <sys/prctl.h>, define it
133 * here.  This allows setuid() to work on systems running a new enough
134 * kernel but with /usr/include/linux pointing to "standard" kernel
135 * headers.
136 */
137#ifndef PR_SET_KEEPCAPS
138#define PR_SET_KEEPCAPS 8
139#endif
140
141#endif /* HAVE_SYS_PRCTL_H */
142
143#ifndef SYS_capset
144#ifndef __NR_capset
145#include <asm/unistd.h> /* Slackware 4.0 needs this. */
146#endif
147#define SYS_capset __NR_capset
148#endif
149
150static void
151linux_setcaps(unsigned int caps) {
152	struct __user_cap_header_struct caphead;
153	struct __user_cap_data_struct cap;
154	char strbuf[ISC_STRERRORSIZE];
155
156	if ((getuid() != 0 && !non_root_caps) || non_root)
157		return;
158
159	memset(&caphead, 0, sizeof(caphead));
160	caphead.version = _LINUX_CAPABILITY_VERSION;
161	caphead.pid = 0;
162	memset(&cap, 0, sizeof(cap));
163	cap.effective = caps;
164	cap.permitted = caps;
165	cap.inheritable = 0;
166	if (syscall(SYS_capset, &caphead, &cap) < 0) {
167		isc__strerror(errno, strbuf, sizeof(strbuf));
168		ns_main_earlyfatal("capset failed: %s:"
169				   " please ensure that the capset kernel"
170				   " module is loaded.  see insmod(8)",
171				   strbuf);
172	}
173}
174
175static void
176linux_initialprivs(void) {
177	unsigned int caps;
178
179	/*
180	 * We don't need most privileges, so we drop them right away.
181	 * Later on linux_minprivs() will be called, which will drop our
182	 * capabilities to the minimum needed to run the server.
183	 */
184
185	caps = 0;
186
187	/*
188	 * We need to be able to bind() to privileged ports, notably port 53!
189	 */
190	caps |= (1 << CAP_NET_BIND_SERVICE);
191
192	/*
193	 * We need chroot() initially too.
194	 */
195	caps |= (1 << CAP_SYS_CHROOT);
196
197#if defined(HAVE_SYS_PRCTL_H) || !defined(HAVE_LINUXTHREADS)
198	/*
199	 * We can setuid() only if either the kernel supports keeping
200	 * capabilities after setuid() (which we don't know until we've
201	 * tried) or we're not using threads.  If either of these is
202	 * true, we want the setuid capability.
203	 */
204	caps |= (1 << CAP_SETUID);
205#endif
206
207	/*
208	 * Since we call initgroups, we need this.
209	 */
210	caps |= (1 << CAP_SETGID);
211
212	/*
213	 * Without this, we run into problems reading a configuration file
214	 * owned by a non-root user and non-world-readable on startup.
215	 */
216	caps |= (1 << CAP_DAC_READ_SEARCH);
217
218	/*
219	 * XXX  We might want to add CAP_SYS_RESOURCE, though it's not
220	 *      clear it would work right given the way linuxthreads work.
221	 * XXXDCL But since we need to be able to set the maximum number
222	 * of files, the stack size, data size, and core dump size to
223	 * support named.conf options, this is now being added to test.
224	 */
225	caps |= (1 << CAP_SYS_RESOURCE);
226
227	linux_setcaps(caps);
228}
229
230static void
231linux_minprivs(void) {
232	unsigned int caps;
233
234	/*
235	 * Drop all privileges except the ability to bind() to privileged
236	 * ports.
237	 *
238	 * It's important that we drop CAP_SYS_CHROOT.  If we didn't, it
239	 * chroot() could be used to escape from the chrooted area.
240	 */
241
242	caps = 0;
243	caps |= (1 << CAP_NET_BIND_SERVICE);
244
245	/*
246	 * XXX  We might want to add CAP_SYS_RESOURCE, though it's not
247	 *      clear it would work right given the way linuxthreads work.
248	 * XXXDCL But since we need to be able to set the maximum number
249	 * of files, the stack size, data size, and core dump size to
250	 * support named.conf options, this is now being added to test.
251	 */
252	caps |= (1 << CAP_SYS_RESOURCE);
253
254	linux_setcaps(caps);
255}
256
257#ifdef HAVE_SYS_PRCTL_H
258static void
259linux_keepcaps(void) {
260	char strbuf[ISC_STRERRORSIZE];
261	/*
262	 * Ask the kernel to allow us to keep our capabilities after we
263	 * setuid().
264	 */
265
266	if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) < 0) {
267		if (errno != EINVAL) {
268			isc__strerror(errno, strbuf, sizeof(strbuf));
269			ns_main_earlyfatal("prctl() failed: %s", strbuf);
270		}
271	} else {
272		non_root_caps = ISC_TRUE;
273		if (getuid() != 0)
274			non_root = ISC_TRUE;
275	}
276}
277#endif
278
279#endif	/* HAVE_LINUX_CAPABILITY_H */
280
281
282static void
283setup_syslog(const char *progname) {
284	int options;
285
286	options = LOG_PID;
287#ifdef LOG_NDELAY
288	options |= LOG_NDELAY;
289#endif
290	openlog(isc_file_basename(progname), options, ISC_FACILITY);
291}
292
293void
294ns_os_init(const char *progname) {
295	setup_syslog(progname);
296#ifdef HAVE_LINUX_CAPABILITY_H
297	linux_initialprivs();
298#endif
299#ifdef HAVE_LINUXTHREADS
300	mainpid = getpid();
301#endif
302#ifdef SIGXFSZ
303	signal(SIGXFSZ, SIG_IGN);
304#endif
305}
306
307void
308ns_os_daemonize(void) {
309	pid_t pid;
310	char strbuf[ISC_STRERRORSIZE];
311
312	if (pipe(dfd) == -1) {
313		isc__strerror(errno, strbuf, sizeof(strbuf));
314		ns_main_earlyfatal("pipe(): %s", strbuf);
315	}
316
317	pid = fork();
318	if (pid == -1) {
319		isc__strerror(errno, strbuf, sizeof(strbuf));
320		ns_main_earlyfatal("fork(): %s", strbuf);
321	}
322	if (pid != 0) {
323		int n;
324		/*
325		 * Wait for the child to finish loading for the first time.
326		 * This would be so much simpler if fork() worked once we
327	         * were multi-threaded.
328		 */
329		(void)close(dfd[1]);
330		do {
331			char buf;
332			n = read(dfd[0], &buf, 1);
333			if (n == 1)
334				_exit(0);
335		} while (n == -1 && errno == EINTR);
336		_exit(1);
337	}
338	(void)close(dfd[0]);
339
340	/*
341	 * We're the child.
342	 */
343
344#ifdef HAVE_LINUXTHREADS
345	mainpid = getpid();
346#endif
347
348	if (setsid() == -1) {
349		isc__strerror(errno, strbuf, sizeof(strbuf));
350		ns_main_earlyfatal("setsid(): %s", strbuf);
351	}
352
353	/*
354	 * Try to set stdin, stdout, and stderr to /dev/null, but press
355	 * on even if it fails.
356	 *
357	 * XXXMLG The close() calls here are unneeded on all but NetBSD, but
358	 * are harmless to include everywhere.  dup2() is supposed to close
359	 * the FD if it is in use, but unproven-pthreads-0.16 is broken
360	 * and will end up closing the wrong FD.  This will be fixed eventually,
361	 * and these calls will be removed.
362	 */
363	if (devnullfd != -1) {
364		if (devnullfd != STDIN_FILENO) {
365			(void)close(STDIN_FILENO);
366			(void)dup2(devnullfd, STDIN_FILENO);
367		}
368		if (devnullfd != STDOUT_FILENO) {
369			(void)close(STDOUT_FILENO);
370			(void)dup2(devnullfd, STDOUT_FILENO);
371		}
372		if (devnullfd != STDERR_FILENO) {
373			(void)close(STDERR_FILENO);
374			(void)dup2(devnullfd, STDERR_FILENO);
375		}
376	}
377}
378
379void
380ns_os_started(void) {
381	char buf = 0;
382
383	/*
384	 * Signal to the parent that we stated successfully.
385	 */
386	if (dfd[0] != -1 && dfd[1] != -1) {
387		write(dfd[1], &buf, 1);
388		close(dfd[1]);
389		dfd[0] = dfd[1] = -1;
390	}
391}
392
393void
394ns_os_opendevnull(void) {
395	devnullfd = open("/dev/null", O_RDWR, 0);
396}
397
398void
399ns_os_closedevnull(void) {
400	if (devnullfd != STDIN_FILENO &&
401	    devnullfd != STDOUT_FILENO &&
402	    devnullfd != STDERR_FILENO) {
403		close(devnullfd);
404		devnullfd = -1;
405	}
406}
407
408static isc_boolean_t
409all_digits(const char *s) {
410	if (*s == '\0')
411		return (ISC_FALSE);
412	while (*s != '\0') {
413		if (!isdigit((*s)&0xff))
414			return (ISC_FALSE);
415		s++;
416	}
417	return (ISC_TRUE);
418}
419
420void
421ns_os_chroot(const char *root) {
422	char strbuf[ISC_STRERRORSIZE];
423#ifdef HAVE_LIBSCF
424	ns_smf_chroot = 0;
425#endif
426	if (root != NULL) {
427		if (chroot(root) < 0) {
428			isc__strerror(errno, strbuf, sizeof(strbuf));
429			ns_main_earlyfatal("chroot(): %s", strbuf);
430		}
431		if (chdir("/") < 0) {
432			isc__strerror(errno, strbuf, sizeof(strbuf));
433			ns_main_earlyfatal("chdir(/): %s", strbuf);
434		}
435#ifdef HAVE_LIBSCF
436		/* Set ns_smf_chroot flag on successful chroot. */
437		ns_smf_chroot = 1;
438#endif
439	}
440}
441
442void
443ns_os_inituserinfo(const char *username) {
444	char strbuf[ISC_STRERRORSIZE];
445	if (username == NULL)
446		return;
447
448	if (all_digits(username))
449		runas_pw = getpwuid((uid_t)atoi(username));
450	else
451		runas_pw = getpwnam(username);
452	endpwent();
453
454	if (runas_pw == NULL)
455		ns_main_earlyfatal("user '%s' unknown", username);
456
457	if (getuid() == 0) {
458		if (initgroups(runas_pw->pw_name, runas_pw->pw_gid) < 0) {
459			isc__strerror(errno, strbuf, sizeof(strbuf));
460			ns_main_earlyfatal("initgroups(): %s", strbuf);
461		}
462	}
463
464}
465
466void
467ns_os_changeuser(void) {
468	char strbuf[ISC_STRERRORSIZE];
469	if (runas_pw == NULL || done_setuid)
470		return;
471
472	done_setuid = ISC_TRUE;
473
474#ifdef HAVE_LINUXTHREADS
475#ifdef HAVE_LINUX_CAPABILITY_H
476	if (!non_root_caps)
477		ns_main_earlyfatal("-u with Linux threads not supported: "
478				   "requires kernel support for "
479				   "prctl(PR_SET_KEEPCAPS)");
480#else
481	ns_main_earlyfatal("-u with Linux threads not supported: "
482			   "no capabilities support or capabilities "
483			   "disabled at build time");
484#endif
485#endif
486
487	if (setgid(runas_pw->pw_gid) < 0) {
488		isc__strerror(errno, strbuf, sizeof(strbuf));
489		ns_main_earlyfatal("setgid(): %s", strbuf);
490	}
491
492	if (setuid(runas_pw->pw_uid) < 0) {
493		isc__strerror(errno, strbuf, sizeof(strbuf));
494		ns_main_earlyfatal("setuid(): %s", strbuf);
495	}
496
497#if defined(HAVE_LINUX_CAPABILITY_H) && !defined(HAVE_LINUXTHREADS)
498	linux_minprivs();
499#endif
500#if defined(HAVE_SYS_PRCTL_H) && defined(PR_SET_DUMPABLE)
501	/*
502	 * Restore the ability of named to drop core after the setuid()
503	 * call has disabled it.
504	 */
505	prctl(PR_SET_DUMPABLE,1,0,0,0);
506#endif
507}
508
509void
510ns_os_minprivs(void) {
511#ifdef HAVE_SYS_PRCTL_H
512	linux_keepcaps();
513#endif
514
515#ifdef HAVE_LINUXTHREADS
516	ns_os_changeuser(); /* Call setuid() before threads are started */
517#endif
518
519#if defined(HAVE_LINUX_CAPABILITY_H) && defined(HAVE_LINUXTHREADS)
520	linux_minprivs();
521#endif
522}
523
524static int
525safe_open(const char *filename, isc_boolean_t append) {
526	int fd;
527	struct stat sb;
528
529	if (stat(filename, &sb) == -1) {
530		if (errno != ENOENT)
531			return (-1);
532	} else if ((sb.st_mode & S_IFREG) == 0) {
533		errno = EOPNOTSUPP;
534		return (-1);
535	}
536
537	if (append)
538		fd = open(filename, O_WRONLY|O_CREAT|O_APPEND,
539			  S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
540	else {
541		(void)unlink(filename);
542		fd = open(filename, O_WRONLY|O_CREAT|O_EXCL,
543			  S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
544	}
545	return (fd);
546}
547
548static void
549cleanup_pidfile(void) {
550	if (pidfile != NULL) {
551		(void)unlink(pidfile);
552		free(pidfile);
553	}
554	pidfile = NULL;
555}
556
557void
558ns_os_writepidfile(const char *filename, isc_boolean_t first_time) {
559	int fd;
560	FILE *lockfile;
561	size_t len;
562	pid_t pid;
563	char strbuf[ISC_STRERRORSIZE];
564	void (*report)(const char *, ...);
565
566	/*
567	 * The caller must ensure any required synchronization.
568	 */
569
570	report = first_time ? ns_main_earlyfatal : ns_main_earlywarning;
571
572	cleanup_pidfile();
573
574	if (filename == NULL)
575		return;
576
577	len = strlen(filename);
578	pidfile = malloc(len + 1);
579	if (pidfile == NULL) {
580		isc__strerror(errno, strbuf, sizeof(strbuf));
581		(*report)("couldn't malloc '%s': %s", filename, strbuf);
582		return;
583	}
584	/* This is safe. */
585	strcpy(pidfile, filename);
586
587	fd = safe_open(filename, ISC_FALSE);
588	if (fd < 0) {
589		isc__strerror(errno, strbuf, sizeof(strbuf));
590		(*report)("couldn't open pid file '%s': %s", filename, strbuf);
591		free(pidfile);
592		pidfile = NULL;
593		return;
594	}
595	lockfile = fdopen(fd, "w");
596	if (lockfile == NULL) {
597		isc__strerror(errno, strbuf, sizeof(strbuf));
598		(*report)("could not fdopen() pid file '%s': %s",
599			  filename, strbuf);
600		(void)close(fd);
601		cleanup_pidfile();
602		return;
603	}
604#ifdef HAVE_LINUXTHREADS
605	pid = mainpid;
606#else
607	pid = getpid();
608#endif
609	if (fprintf(lockfile, "%ld\n", (long)pid) < 0) {
610		(*report)("fprintf() to pid file '%s' failed", filename);
611		(void)fclose(lockfile);
612		cleanup_pidfile();
613		return;
614	}
615	if (fflush(lockfile) == EOF) {
616		(*report)("fflush() to pid file '%s' failed", filename);
617		(void)fclose(lockfile);
618		cleanup_pidfile();
619		return;
620	}
621	(void)fclose(lockfile);
622}
623
624void
625ns_os_shutdown(void) {
626	closelog();
627	cleanup_pidfile();
628}
629
630isc_result_t
631ns_os_gethostname(char *buf, size_t len) {
632	int n;
633
634	n = gethostname(buf, len);
635	return ((n == 0) ? ISC_R_SUCCESS : ISC_R_FAILURE);
636}
637
638static char *
639next_token(char **stringp, const char *delim) {
640	char *res;
641
642	do {
643		res = strsep(stringp, delim);
644		if (res == NULL)
645			break;
646	} while (*res == '\0');
647	return (res);
648}
649
650void
651ns_os_shutdownmsg(char *command, isc_buffer_t *text) {
652	char *input, *ptr;
653	unsigned int n;
654	pid_t pid;
655
656	input = command;
657
658	/* Skip the command name. */
659	ptr = next_token(&input, " \t");
660	if (ptr == NULL)
661		return;
662
663	ptr = next_token(&input, " \t");
664	if (ptr == NULL)
665		return;
666
667	if (strcmp(ptr, "-p") != 0)
668		return;
669
670#ifdef HAVE_LINUXTHREADS
671	pid = mainpid;
672#else
673	pid = getpid();
674#endif
675
676	n = snprintf((char *)isc_buffer_used(text),
677		     isc_buffer_availablelength(text),
678		     "pid: %ld", (long)pid);
679	/* Only send a message if it is complete. */
680	if (n < isc_buffer_availablelength(text))
681		isc_buffer_add(text, n);
682}
683
684void
685ns_os_tzset(void) {
686#ifdef HAVE_TZSET
687	tzset();
688#endif
689}
690