os.c 12.3 KB
Newer Older
Bob Halley's avatar
add  
Bob Halley committed
1
/*
Brian Wellington's avatar
Brian Wellington committed
2
 * Copyright (C) 1999-2001  Internet Software Consortium.
3
 *
Bob Halley's avatar
add  
Bob Halley committed
4
5
6
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
7
 *
8
9
10
11
12
13
14
15
 * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM
 * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
 * INTERNET SOFTWARE CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
 * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
Bob Halley's avatar
add  
Bob Halley committed
16
17
 */

18
/* $Id: os.c,v 1.52 2001/10/12 05:40:39 marka Exp $ */
David Lawrence's avatar
David Lawrence committed
19

Bob Halley's avatar
add  
Bob Halley committed
20
#include <config.h>
21
#include <stdarg.h>
Bob Halley's avatar
add  
Bob Halley committed
22

23
#include <sys/types.h>  /* dev_t FreeBSD 2.1 */
Bob Halley's avatar
Bob Halley committed
24
#include <sys/stat.h>
Bob Halley's avatar
add  
Bob Halley committed
25

26
#include <ctype.h>
Bob Halley's avatar
add  
Bob Halley committed
27
#include <errno.h>
Bob Halley's avatar
Bob Halley committed
28
#include <fcntl.h>
29
#include <grp.h>		/* Required for initgroups() on IRIX. */
30
#include <pwd.h>
31
32
33
34
#include <stdio.h>
#include <stdlib.h>
#include <syslog.h>
#include <unistd.h>
Bob Halley's avatar
add  
Bob Halley committed
35

36
#include <isc/file.h>
37
#include <isc/print.h>
38
#include <isc/result.h>
39
#include <isc/strerror.h>
40
#include <isc/string.h>
Bob Halley's avatar
add  
Bob Halley committed
41
42
43
44

#include <named/main.h>
#include <named/os.h>

Bob Halley's avatar
Bob Halley committed
45
static char *pidfile = NULL;
46
47

/*
48
 * If there's no <linux/capability.h>, we don't care about <sys/prctl.h>
49
50
 */
#ifndef HAVE_LINUX_CAPABILITY_H
51
#undef HAVE_SYS_PRCTL_H
52
53
54
55
56
57
#endif

/*
 * Linux defines:
 * 	(T) HAVE_LINUXTHREADS
 * 	(C) HAVE_LINUX_CAPABILITY_H
58
 * 	(P) HAVE_SYS_PRCTL_H
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
 * The possible cases are:
 * 	none:	setuid() normally
 * 	T:	no setuid()
 * 	C:	setuid() normally, drop caps (keep CAP_SETUID)
 * 	T+C:	no setuid(), drop caps (don't keep CAP_SETUID)
 * 	T+C+P:	setuid() early, drop caps (keep CAP_SETUID)
 * 	C+P:	setuid() normally, drop caps (keep CAP_SETUID)
 *	P:	not possible
 *	T+P:	not possible
 *
 * if (C)
 * 	caps = BIND_SERVICE + CHROOT + SETGID
 * 	if ((T && C && P) || !T)
 * 		caps += SETUID
 * 	endif
 * 	capset(caps)
 * endif
 * if (T && C && P && -u)
 * 	setuid()
 * else if (T && -u)
 * 	fail
 * --> start threads
 * if (!T && -u)
 * 	setuid()
 * if (C && (P || !-u))
 * 	caps = BIND_SERVICE
 * 	capset(caps)
 * endif
 *
 * It will be nice when Linux threads work properly with setuid().
 */

Bob Halley's avatar
Bob Halley committed
91
92
93
#ifdef HAVE_LINUXTHREADS
static pid_t mainpid = 0;
#endif
Bob Halley's avatar
add  
Bob Halley committed
94

95
static struct passwd *runas_pw = NULL;
96
static isc_boolean_t done_setuid = ISC_FALSE;
97

Bob Halley's avatar
add  
Bob Halley committed
98
#ifdef HAVE_LINUX_CAPABILITY_H
99

100
101
102
static isc_boolean_t non_root = ISC_FALSE;
static isc_boolean_t non_root_caps = ISC_FALSE;

103
104
105
106
107
108
109
110
/*
 * We define _LINUX_FS_H to prevent it from being included.  We don't need
 * anything from it, and the files it includes cause warnings with 2.2
 * kernels, and compilation failures (due to conflicts between <linux/string.h>
 * and <string.h>) on 2.3 kernels.
 */
#define _LINUX_FS_H

111
112
#include <sys/syscall.h>	/* Required for syscall(). */
#include <linux/capability.h>	/* Required for _LINUX_CAPABILITY_VERSION. */
Bob Halley's avatar
add  
Bob Halley committed
113

114
#ifdef HAVE_SYS_PRCTL_H
115
#include <sys/prctl.h>		/* Required for prctl(). */
116
117

/*
118
 * If the value of PR_SET_KEEPCAPS is not in <sys/prctl.h>, define it
119
120
121
122
 * here.  This allows setuid() to work on systems running a new enough
 * kernel but with /usr/include/linux pointing to "standard" kernel
 * headers.
 */
123
124
#ifndef PR_SET_KEEPCAPS
#define PR_SET_KEEPCAPS 8
Bob Halley's avatar
Bob Halley committed
125
#endif
126

127
#endif /* HAVE_SYS_PRCTL_H */
Bob Halley's avatar
Bob Halley committed
128

Bob Halley's avatar
add  
Bob Halley committed
129
130
131
132
133
#ifndef SYS_capset
#define SYS_capset __NR_capset
#endif

static void
134
linux_setcaps(unsigned int caps) {
Bob Halley's avatar
add  
Bob Halley committed
135
136
	struct __user_cap_header_struct caphead;
	struct __user_cap_data_struct cap;
137
	char strbuf[ISC_STRERRORSIZE];
Bob Halley's avatar
add  
Bob Halley committed
138

139
	if ((getuid() != 0 && !non_root_caps) || non_root)
Bob Halley's avatar
add  
Bob Halley committed
140
141
		return;

Andreas Gustafsson's avatar
Andreas Gustafsson committed
142
	memset(&caphead, 0, sizeof(caphead));
Bob Halley's avatar
add  
Bob Halley committed
143
144
	caphead.version = _LINUX_CAPABILITY_VERSION;
	caphead.pid = 0;
Andreas Gustafsson's avatar
Andreas Gustafsson committed
145
	memset(&cap, 0, sizeof(cap));
Bob Halley's avatar
add  
Bob Halley committed
146
147
148
	cap.effective = caps;
	cap.permitted = caps;
	cap.inheritable = caps;
149
150
151
152
	if (syscall(SYS_capset, &caphead, &cap) < 0) {
		isc__strerror(errno, strbuf, sizeof(strbuf));
		ns_main_earlyfatal("capset failed: %s", strbuf);
	}
Bob Halley's avatar
add  
Bob Halley committed
153
}
154
155
156
157
158
159

static void
linux_initialprivs(void) {
	unsigned int caps;

	/*
Bob Halley's avatar
Bob Halley committed
160
161
162
	 * We don't need most privileges, so we drop them right away.
	 * Later on linux_minprivs() will be called, which will drop our
	 * capabilities to the minimum needed to run the server.
163
164
165
	 */

	caps = 0;
Bob Halley's avatar
Bob Halley committed
166
167
168
169

	/*
	 * We need to be able to bind() to privileged ports, notably port 53!
	 */
170
	caps |= (1 << CAP_NET_BIND_SERVICE);
Bob Halley's avatar
Bob Halley committed
171
172
173
174

	/*
	 * We need chroot() initially too.
	 */
175
	caps |= (1 << CAP_SYS_CHROOT);
Bob Halley's avatar
Bob Halley committed
176

177
#if defined(HAVE_SYS_PRCTL_H) || !defined(HAVE_LINUXTHREADS)
Bob Halley's avatar
Bob Halley committed
178
	/*
179
180
181
182
	 * We can setuid() only if either the kernel supports keeping
	 * capabilities after setuid() (which we don't know until we've
	 * tried) or we're not using threads.  If either of these is
	 * true, we want the setuid capability.
Bob Halley's avatar
Bob Halley committed
183
184
185
186
	 */
	caps |= (1 << CAP_SETUID);
#endif

187
188
189
190
191
	/*
	 * Since we call initgroups, we need this.
	 */
	caps |= (1 << CAP_SETGID);

192
193
194
195
196
197
	/*
	 * Without this, we run into problems reading a configuration file
	 * owned by a non-root user and non-world-readable on startup.
	 */
	caps |= (1 << CAP_DAC_READ_SEARCH);

Bob Halley's avatar
Bob Halley committed
198
199
200
	/*
	 * XXX  We might want to add CAP_SYS_RESOURCE, though it's not
	 *      clear it would work right given the way linuxthreads work.
201
202
203
	 * XXXDCL But since we need to be able to set the maximum number
	 * of files, the stack size, data size, and core dump size to
	 * support named.conf options, this is now being added to test.
Bob Halley's avatar
Bob Halley committed
204
	 */
205
	caps |= (1 << CAP_SYS_RESOURCE);
Bob Halley's avatar
Bob Halley committed
206

207
208
209
210
211
212
213
214
	linux_setcaps(caps);
}

static void
linux_minprivs(void) {
	unsigned int caps;

	/*
Bob Halley's avatar
Bob Halley committed
215
	 * Drop all privileges except the ability to bind() to privileged
Bob Halley's avatar
Bob Halley committed
216
	 * ports.
Bob Halley's avatar
Bob Halley committed
217
218
219
	 *
	 * It's important that we drop CAP_SYS_CHROOT.  If we didn't, it
	 * chroot() could be used to escape from the chrooted area.
220
221
222
223
224
	 */

	caps = 0;
	caps |= (1 << CAP_NET_BIND_SERVICE);

Mark Andrews's avatar
Mark Andrews committed
225
226
227
228
229
230
231
232
233
	/*
	 * XXX  We might want to add CAP_SYS_RESOURCE, though it's not
	 *      clear it would work right given the way linuxthreads work.
	 * XXXDCL But since we need to be able to set the maximum number
	 * of files, the stack size, data size, and core dump size to
	 * support named.conf options, this is now being added to test.
	 */
	caps |= (1 << CAP_SYS_RESOURCE);

234
235
236
	linux_setcaps(caps);
}

237
#ifdef HAVE_SYS_PRCTL_H
Bob Halley's avatar
Bob Halley committed
238
239
static void
linux_keepcaps(void) {
240
	char strbuf[ISC_STRERRORSIZE];
Bob Halley's avatar
Bob Halley committed
241
242
243
244
245
246
	/*
	 * Ask the kernel to allow us to keep our capabilities after we
	 * setuid().
	 */

	if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) < 0) {
247
248
249
250
		if (errno != EINVAL) {
			isc__strerror(errno, strbuf, sizeof(strbuf));
			ns_main_earlyfatal("prctl() failed: %s", strbuf);
		}
251
	} else {
Bob Halley's avatar
Bob Halley committed
252
		non_root_caps = ISC_TRUE;
253
254
255
		if (getuid() != 0)
			non_root = ISC_TRUE;
	}
Bob Halley's avatar
Bob Halley committed
256
257
258
}
#endif

259
260
#endif	/* HAVE_LINUX_CAPABILITY_H */

Bob Halley's avatar
add  
Bob Halley committed
261

Bob Halley's avatar
Bob Halley committed
262
static void
263
setup_syslog(const char *progname) {
Bob Halley's avatar
Bob Halley committed
264
265
266
267
268
269
270
	int options;

	options = LOG_PID;
#ifdef LOG_NDELAY
	options |= LOG_NDELAY;
#endif

271
	openlog(isc_file_basename(progname), options, LOG_DAEMON);
Bob Halley's avatar
Bob Halley committed
272
}
Bob Halley's avatar
add  
Bob Halley committed
273

274
void
275
276
ns_os_init(const char *progname) {
	setup_syslog(progname);
Bob Halley's avatar
add  
Bob Halley committed
277
#ifdef HAVE_LINUX_CAPABILITY_H
278
	linux_initialprivs();
Bob Halley's avatar
add  
Bob Halley committed
279
#endif
Bob Halley's avatar
Bob Halley committed
280
281
282
#ifdef HAVE_LINUXTHREADS
	mainpid = getpid();
#endif
Bob Halley's avatar
add  
Bob Halley committed
283
284
}

285
void
Bob Halley's avatar
Bob Halley committed
286
287
288
ns_os_daemonize(void) {
	pid_t pid;
	int fd;
289
	char strbuf[ISC_STRERRORSIZE];
Bob Halley's avatar
Bob Halley committed
290
291

	pid = fork();
292
293
294
295
	if (pid == -1) {
		isc__strerror(errno, strbuf, sizeof(strbuf));
		ns_main_earlyfatal("fork(): %s", strbuf);
	}
Bob Halley's avatar
Bob Halley committed
296
	if (pid != 0)
297
		_exit(0);
Bob Halley's avatar
Bob Halley committed
298
299
300
301
302

	/*
	 * We're the child.
	 */

Bob Halley's avatar
Bob Halley committed
303
304
305
306
#ifdef HAVE_LINUXTHREADS
	mainpid = getpid();
#endif

307
308
309
310
        if (setsid() == -1) {
		isc__strerror(errno, strbuf, sizeof(strbuf));
		ns_main_earlyfatal("setsid(): %s", strbuf);
	}
Bob Halley's avatar
Bob Halley committed
311
312
313
314

	/*
	 * Try to set stdin, stdout, and stderr to /dev/null, but press
	 * on even if it fails.
315
316
317
318
319
320
	 *
	 * XXXMLG The close() calls here are unneeded on all but NetBSD, but
	 * are harmless to include everywhere.  dup2() is supposed to close
	 * the FD if it is in use, but unproven-pthreads-0.16 is broken
	 * and will end up closing the wrong FD.  This will be fixed eventually,
	 * and these calls will be removed.
Bob Halley's avatar
Bob Halley committed
321
322
323
	 */
	fd = open("/dev/null", O_RDWR, 0);
	if (fd != -1) {
324
		close(STDIN_FILENO);
Bob Halley's avatar
Bob Halley committed
325
		(void)dup2(fd, STDIN_FILENO);
326
		close(STDOUT_FILENO);
Bob Halley's avatar
Bob Halley committed
327
		(void)dup2(fd, STDOUT_FILENO);
328
		close(STDERR_FILENO);
Bob Halley's avatar
Bob Halley committed
329
		(void)dup2(fd, STDERR_FILENO);
330
331
332
333
		if (fd != STDIN_FILENO &&
		    fd != STDOUT_FILENO &&
		    fd != STDERR_FILENO)
			(void)close(fd);
Bob Halley's avatar
Bob Halley committed
334
335
336
	}
}

337
338
339
340
341
static isc_boolean_t
all_digits(const char *s) {
	if (*s == '\0')
		return (ISC_FALSE);
	while (*s != '\0') {
Mark Andrews's avatar
lint    
Mark Andrews committed
342
		if (!isdigit((*s)&0xff))
343
344
345
346
347
348
349
350
			return (ISC_FALSE);
		s++;
	}
	return (ISC_TRUE);
}

void
ns_os_chroot(const char *root) {
351
	char strbuf[ISC_STRERRORSIZE];
352
	if (root != NULL) {
353
354
355
356
357
358
359
360
		if (chroot(root) < 0) {
			isc__strerror(errno, strbuf, sizeof(strbuf));
			ns_main_earlyfatal("chroot(): %s", strbuf);
		}
		if (chdir("/") < 0) {
			isc__strerror(errno, strbuf, sizeof(strbuf));
			ns_main_earlyfatal("chdir(/): %s", strbuf);
		}
361
362
363
364
	}
}

void
365
ns_os_inituserinfo(const char *username) {
366
	char strbuf[ISC_STRERRORSIZE];
367
	if (username == NULL)
368
369
370
		return;

	if (all_digits(username))
371
		runas_pw = getpwuid((uid_t)atoi(username));
372
	else
373
		runas_pw = getpwnam(username);
374
	endpwent();
375

376
	if (runas_pw == NULL)
377
		ns_main_earlyfatal("user '%s' unknown", username);
378
379

	if (getuid() == 0) {
380
381
382
383
		if (initgroups(runas_pw->pw_name, runas_pw->pw_gid) < 0) {
			isc__strerror(errno, strbuf, sizeof(strbuf));
			ns_main_earlyfatal("initgroups(): %s", strbuf);
		}
384
385
	}

386
387
388
389
}

void
ns_os_changeuser(void) {
390
	char strbuf[ISC_STRERRORSIZE];
391
	if (runas_pw == NULL || done_setuid)
392
393
		return;

394
395
	done_setuid = ISC_TRUE;

396
#ifdef HAVE_LINUXTHREADS
397
#ifdef HAVE_LINUX_CAPABILITY_H
398
	if (!non_root_caps)
399
#endif
400
		ns_main_earlyfatal(
401
		   "-u not supported on Linux kernels older than "
402
		   "2.3.99-pre3 or 2.2.18 when using threads");
403
#endif
404

405
406
407
408
	if (setgid(runas_pw->pw_gid) < 0) {
		isc__strerror(errno, strbuf, sizeof(strbuf));
		ns_main_earlyfatal("setgid(): %s", strbuf);
	}
409

410
411
412
413
	if (setuid(runas_pw->pw_uid) < 0) {
		isc__strerror(errno, strbuf, sizeof(strbuf));
		ns_main_earlyfatal("setuid(): %s", strbuf);
	}
414
415
416
417

#if defined(HAVE_LINUX_CAPABILITY_H) && !defined(HAVE_LINUXTHREADS)
	linux_minprivs();
#endif
418
}
Bob Halley's avatar
Bob Halley committed
419

Bob Halley's avatar
Bob Halley committed
420
void
421
ns_os_minprivs(void) {
422
#ifdef HAVE_SYS_PRCTL_H
423
	linux_keepcaps();
Bob Halley's avatar
Bob Halley committed
424
#endif
425

426
427
428
#ifdef HAVE_LINUXTHREADS
	ns_os_changeuser(); /* Call setuid() before threads are started */
#endif
429

430
431
432
#if defined(HAVE_LINUX_CAPABILITY_H) && defined(HAVE_LINUXTHREADS)
	linux_minprivs();
#endif
Bob Halley's avatar
Bob Halley committed
433
434
}

Bob Halley's avatar
Bob Halley committed
435
static int
436
437
safe_open(const char *filename, isc_boolean_t append) {
	int fd;
Bob Halley's avatar
Bob Halley committed
438
439
440
441
442
        struct stat sb;

        if (stat(filename, &sb) == -1) {
                if (errno != ENOENT)
			return (-1);
443
444
        } else if ((sb.st_mode & S_IFREG) == 0) {
		errno = EOPNOTSUPP;
Bob Halley's avatar
Bob Halley committed
445
		return (-1);
446
	}
Bob Halley's avatar
Bob Halley committed
447

448
449
450
451
452
453
454
455
456
	if (append)
		fd = open(filename, O_WRONLY|O_CREAT|O_APPEND,
		     S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
	else {
		(void)unlink(filename);
		fd = open(filename, O_WRONLY|O_CREAT|O_EXCL,
		     S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
	}
	return (fd);
Bob Halley's avatar
Bob Halley committed
457
458
459
460
}

static void
cleanup_pidfile(void) {
461
	if (pidfile != NULL) {
Bob Halley's avatar
Bob Halley committed
462
		(void)unlink(pidfile);
463
464
		free(pidfile);
	}
Bob Halley's avatar
Bob Halley committed
465
466
467
468
469
470
471
472
473
	pidfile = NULL;
}

void
ns_os_writepidfile(const char *filename) {
        int fd;
	FILE *lockfile;
	size_t len;
	pid_t pid;
474
	char strbuf[ISC_STRERRORSIZE];
Bob Halley's avatar
Bob Halley committed
475
476
477
478
479
480
481

	/*
	 * The caller must ensure any required synchronization.
	 */

	cleanup_pidfile();

482
483
	if (strcmp(pidfile, "none") == 0)
		return;
Bob Halley's avatar
Bob Halley committed
484
485
	len = strlen(filename);
	pidfile = malloc(len + 1);
486
487
	if (pidfile == NULL) {
		isc__strerror(errno, strbuf, sizeof(strbuf));
Bob Halley's avatar
Bob Halley committed
488
                ns_main_earlyfatal("couldn't malloc '%s': %s",
489
490
				   filename, strbuf);
	}
Bob Halley's avatar
Bob Halley committed
491
492
493
	/* This is safe. */
	strcpy(pidfile, filename);

494
        fd = safe_open(filename, ISC_FALSE);
495
496
        if (fd < 0) {
		isc__strerror(errno, strbuf, sizeof(strbuf));
Bob Halley's avatar
Bob Halley committed
497
                ns_main_earlyfatal("couldn't open pid file '%s': %s",
498
499
				   filename, strbuf);
	}
Bob Halley's avatar
Bob Halley committed
500
        lockfile = fdopen(fd, "w");
501
502
        if (lockfile == NULL) {
		isc__strerror(errno, strbuf, sizeof(strbuf));
Bob Halley's avatar
Bob Halley committed
503
		ns_main_earlyfatal("could not fdopen() pid file '%s': %s",
504
505
				   filename, strbuf);
	}
Bob Halley's avatar
Bob Halley committed
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
#ifdef HAVE_LINUXTHREADS
	pid = mainpid;
#else
	pid = getpid();
#endif
        if (fprintf(lockfile, "%ld\n", (long)pid) < 0)
                ns_main_earlyfatal("fprintf() to pid file '%s' failed",
				   filename);
        if (fflush(lockfile) == EOF)
                ns_main_earlyfatal("fflush() to pid file '%s' failed",
				   filename);
	(void)fclose(lockfile);
}

void
ns_os_shutdown(void) {
	closelog();
	cleanup_pidfile();
}
525
526
527
528
529
530
531
532
533

isc_result_t
ns_os_gethostname(char *buf, size_t len) {
        int n;

	n = gethostname(buf, len);
	return ((n == 0) ? ISC_R_SUCCESS : ISC_R_FAILURE);
}