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