On systems where root access is needed to configure privileged ports, we don't want to fully relinquish root privileges until after the control channel (which typically runs on port 953) has been established. named_os_changeuser() now takes a boolean argument 'permanent'. This allows us to switch the effective userid temporarily with named_os_changeuser(false) and restore it with named_os_restoreuser(), before permanently dropping privileges with named_os_changeuser(true).
868 lines
20 KiB
C
868 lines
20 KiB
C
/*
|
|
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
|
|
*
|
|
* SPDX-License-Identifier: MPL-2.0
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
|
*
|
|
* See the COPYRIGHT file distributed with this work for additional
|
|
* information regarding copyright ownership.
|
|
*/
|
|
|
|
/*! \file */
|
|
#include <stdarg.h>
|
|
#include <stdbool.h>
|
|
#include <sys/resource.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h> /* dev_t FreeBSD 2.1 */
|
|
#ifdef HAVE_UNAME
|
|
#include <sys/utsname.h>
|
|
#endif /* ifdef HAVE_UNAME */
|
|
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <grp.h>
|
|
#include <pwd.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <syslog.h>
|
|
#ifdef HAVE_TZSET
|
|
#include <time.h>
|
|
#endif /* ifdef HAVE_TZSET */
|
|
#include <unistd.h>
|
|
|
|
#include <isc/buffer.h>
|
|
#include <isc/file.h>
|
|
#include <isc/result.h>
|
|
#include <isc/strerr.h>
|
|
#include <isc/string.h>
|
|
#include <isc/util.h>
|
|
|
|
#include <named/globals.h>
|
|
#include <named/log.h>
|
|
#include <named/main.h>
|
|
#include <named/os.h>
|
|
#ifdef HAVE_LIBSCF
|
|
#include <named/smf_globals.h>
|
|
#endif /* ifdef HAVE_LIBSCF */
|
|
|
|
static char *pidfile = NULL;
|
|
static int devnullfd = -1;
|
|
|
|
#ifndef ISC_FACILITY
|
|
#define ISC_FACILITY LOG_DAEMON
|
|
#endif /* ifndef ISC_FACILITY */
|
|
|
|
static struct passwd *runas_pw = NULL;
|
|
static bool done_setuid = false;
|
|
static int dfd[2] = { -1, -1 };
|
|
|
|
static uid_t saved_uid = (uid_t)-1;
|
|
static gid_t saved_gid = (gid_t)-1;
|
|
|
|
#if HAVE_LIBCAP
|
|
|
|
static bool non_root = false;
|
|
static bool non_root_caps = false;
|
|
|
|
#include <sys/capability.h>
|
|
#include <sys/prctl.h>
|
|
|
|
static void
|
|
linux_setcaps(cap_t caps) {
|
|
char strbuf[ISC_STRERRORSIZE];
|
|
|
|
if ((getuid() != 0 && !non_root_caps) || non_root) {
|
|
return;
|
|
}
|
|
if (cap_set_proc(caps) < 0) {
|
|
strerror_r(errno, strbuf, sizeof(strbuf));
|
|
named_main_earlyfatal("cap_set_proc() failed: %s:"
|
|
" please ensure that the capset kernel"
|
|
" module is loaded. see insmod(8)",
|
|
strbuf);
|
|
}
|
|
}
|
|
|
|
#define SET_CAP(flag) \
|
|
do { \
|
|
cap_flag_value_t curval; \
|
|
capval = (flag); \
|
|
err = cap_get_flag(curcaps, capval, CAP_PERMITTED, &curval); \
|
|
if (err != -1 && curval) { \
|
|
err = cap_set_flag(caps, CAP_EFFECTIVE, 1, &capval, \
|
|
CAP_SET); \
|
|
if (err == -1) { \
|
|
strerror_r(errno, strbuf, sizeof(strbuf)); \
|
|
named_main_earlyfatal("cap_set_proc failed: " \
|
|
"%s", \
|
|
strbuf); \
|
|
} \
|
|
\
|
|
err = cap_set_flag(caps, CAP_PERMITTED, 1, &capval, \
|
|
CAP_SET); \
|
|
if (err == -1) { \
|
|
strerror_r(errno, strbuf, sizeof(strbuf)); \
|
|
named_main_earlyfatal("cap_set_proc failed: " \
|
|
"%s", \
|
|
strbuf); \
|
|
} \
|
|
} \
|
|
} while (0)
|
|
#define INIT_CAP \
|
|
do { \
|
|
caps = cap_init(); \
|
|
if (caps == NULL) { \
|
|
strerror_r(errno, strbuf, sizeof(strbuf)); \
|
|
named_main_earlyfatal("cap_init failed: %s", strbuf); \
|
|
} \
|
|
curcaps = cap_get_proc(); \
|
|
if (curcaps == NULL) { \
|
|
strerror_r(errno, strbuf, sizeof(strbuf)); \
|
|
named_main_earlyfatal("cap_get_proc failed: %s", \
|
|
strbuf); \
|
|
} \
|
|
} while (0)
|
|
#define FREE_CAP \
|
|
{ \
|
|
cap_free(caps); \
|
|
cap_free(curcaps); \
|
|
} \
|
|
while (0)
|
|
|
|
static void
|
|
linux_initialprivs(void) {
|
|
cap_t caps;
|
|
cap_t curcaps;
|
|
cap_value_t capval;
|
|
char strbuf[ISC_STRERRORSIZE];
|
|
int err;
|
|
|
|
/*%
|
|
* 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.
|
|
*/
|
|
INIT_CAP;
|
|
|
|
/*
|
|
* We need to be able to bind() to privileged ports, notably port 53!
|
|
*/
|
|
SET_CAP(CAP_NET_BIND_SERVICE);
|
|
|
|
/*
|
|
* We need chroot() initially too.
|
|
*/
|
|
SET_CAP(CAP_SYS_CHROOT);
|
|
|
|
/*
|
|
* We need setuid() as the kernel supports keeping capabilities after
|
|
* setuid().
|
|
*/
|
|
SET_CAP(CAP_SETUID);
|
|
|
|
/*
|
|
* Since we call initgroups, we need this.
|
|
*/
|
|
SET_CAP(CAP_SETGID);
|
|
|
|
/*
|
|
* Without this, we run into problems reading a configuration file
|
|
* owned by a non-root user and non-world-readable on startup.
|
|
*/
|
|
SET_CAP(CAP_DAC_READ_SEARCH);
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
SET_CAP(CAP_SYS_RESOURCE);
|
|
|
|
/*
|
|
* We need to be able to set the ownership of the containing
|
|
* directory of the pid file when we create it.
|
|
*/
|
|
SET_CAP(CAP_CHOWN);
|
|
|
|
linux_setcaps(caps);
|
|
|
|
FREE_CAP;
|
|
}
|
|
|
|
static void
|
|
linux_minprivs(void) {
|
|
cap_t caps;
|
|
cap_t curcaps;
|
|
cap_value_t capval;
|
|
char strbuf[ISC_STRERRORSIZE];
|
|
int err;
|
|
|
|
INIT_CAP;
|
|
/*%
|
|
* Drop all privileges except the ability to bind() to privileged
|
|
* ports.
|
|
*
|
|
* It's important that we drop CAP_SYS_CHROOT. If we didn't, it
|
|
* chroot() could be used to escape from the chrooted area.
|
|
*/
|
|
|
|
SET_CAP(CAP_NET_BIND_SERVICE);
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
SET_CAP(CAP_SYS_RESOURCE);
|
|
|
|
linux_setcaps(caps);
|
|
|
|
FREE_CAP;
|
|
}
|
|
|
|
static void
|
|
linux_keepcaps(void) {
|
|
char strbuf[ISC_STRERRORSIZE];
|
|
/*%
|
|
* Ask the kernel to allow us to keep our capabilities after we
|
|
* setuid().
|
|
*/
|
|
|
|
if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) < 0) {
|
|
if (errno != EINVAL) {
|
|
strerror_r(errno, strbuf, sizeof(strbuf));
|
|
named_main_earlyfatal("prctl() failed: %s", strbuf);
|
|
}
|
|
} else {
|
|
non_root_caps = true;
|
|
if (getuid() != 0) {
|
|
non_root = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif /* HAVE_LIBCAP */
|
|
|
|
static void
|
|
setperms(uid_t uid, gid_t gid) {
|
|
char strbuf[ISC_STRERRORSIZE];
|
|
|
|
/*
|
|
* Drop the gid privilege first, because in some cases the gid privilege
|
|
* cannot be dropped after the uid privilege has been dropped.
|
|
*/
|
|
if (setegid(gid) == -1) {
|
|
strerror_r(errno, strbuf, sizeof(strbuf));
|
|
named_main_earlywarning("unable to set effective gid to %d: %s",
|
|
gid, strbuf);
|
|
}
|
|
|
|
if (seteuid(uid) == -1) {
|
|
strerror_r(errno, strbuf, sizeof(strbuf));
|
|
named_main_earlywarning("unable to set effective uid to %d: %s",
|
|
uid, strbuf);
|
|
}
|
|
}
|
|
|
|
static void
|
|
setup_syslog(const char *progname) {
|
|
int options;
|
|
|
|
options = LOG_PID;
|
|
#ifdef LOG_NDELAY
|
|
options |= LOG_NDELAY;
|
|
#endif /* ifdef LOG_NDELAY */
|
|
openlog(isc_file_basename(progname), options, ISC_FACILITY);
|
|
}
|
|
|
|
void
|
|
named_os_init(const char *progname) {
|
|
setup_syslog(progname);
|
|
#if HAVE_LIBCAP
|
|
linux_initialprivs();
|
|
#endif /* HAVE_LIBCAP */
|
|
#ifdef SIGXFSZ
|
|
signal(SIGXFSZ, SIG_IGN);
|
|
#endif /* ifdef SIGXFSZ */
|
|
}
|
|
|
|
void
|
|
named_os_daemonize(void) {
|
|
pid_t pid;
|
|
char strbuf[ISC_STRERRORSIZE];
|
|
|
|
if (pipe(dfd) == -1) {
|
|
strerror_r(errno, strbuf, sizeof(strbuf));
|
|
named_main_earlyfatal("pipe(): %s", strbuf);
|
|
}
|
|
|
|
pid = fork();
|
|
if (pid == -1) {
|
|
strerror_r(errno, strbuf, sizeof(strbuf));
|
|
named_main_earlyfatal("fork(): %s", strbuf);
|
|
}
|
|
if (pid != 0) {
|
|
int n;
|
|
/*
|
|
* Wait for the child to finish loading for the first time.
|
|
* This would be so much simpler if fork() worked once we
|
|
* were multi-threaded.
|
|
*/
|
|
(void)close(dfd[1]);
|
|
do {
|
|
char buf;
|
|
n = read(dfd[0], &buf, 1);
|
|
if (n == 1) {
|
|
_exit(EXIT_SUCCESS);
|
|
}
|
|
} while (n == -1 && errno == EINTR);
|
|
_exit(EXIT_FAILURE);
|
|
}
|
|
(void)close(dfd[0]);
|
|
|
|
/*
|
|
* We're the child.
|
|
*/
|
|
|
|
if (setsid() == -1) {
|
|
strerror_r(errno, strbuf, sizeof(strbuf));
|
|
named_main_earlyfatal("setsid(): %s", strbuf);
|
|
}
|
|
|
|
/*
|
|
* Try to set stdin, stdout, and stderr to /dev/null, but press
|
|
* on even if it fails.
|
|
*
|
|
* 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.
|
|
*/
|
|
if (devnullfd != -1) {
|
|
if (devnullfd != STDIN_FILENO) {
|
|
(void)close(STDIN_FILENO);
|
|
(void)dup2(devnullfd, STDIN_FILENO);
|
|
}
|
|
if (devnullfd != STDOUT_FILENO) {
|
|
(void)close(STDOUT_FILENO);
|
|
(void)dup2(devnullfd, STDOUT_FILENO);
|
|
}
|
|
if (devnullfd != STDERR_FILENO && !named_g_keepstderr) {
|
|
(void)close(STDERR_FILENO);
|
|
(void)dup2(devnullfd, STDERR_FILENO);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
named_os_started(void) {
|
|
char buf = 0;
|
|
|
|
/*
|
|
* Signal to the parent that we started successfully.
|
|
*/
|
|
if (dfd[0] != -1 && dfd[1] != -1) {
|
|
if (write(dfd[1], &buf, 1) != 1) {
|
|
named_main_earlyfatal("unable to signal parent that we "
|
|
"otherwise started "
|
|
"successfully.");
|
|
}
|
|
close(dfd[1]);
|
|
dfd[0] = dfd[1] = -1;
|
|
}
|
|
}
|
|
|
|
void
|
|
named_os_opendevnull(void) {
|
|
devnullfd = open("/dev/null", O_RDWR, 0);
|
|
}
|
|
|
|
void
|
|
named_os_closedevnull(void) {
|
|
if (devnullfd != STDIN_FILENO && devnullfd != STDOUT_FILENO &&
|
|
devnullfd != STDERR_FILENO)
|
|
{
|
|
close(devnullfd);
|
|
devnullfd = -1;
|
|
}
|
|
}
|
|
|
|
static bool
|
|
all_digits(const char *s) {
|
|
if (*s == '\0') {
|
|
return (false);
|
|
}
|
|
while (*s != '\0') {
|
|
if (!isdigit((unsigned char)(*s))) {
|
|
return (false);
|
|
}
|
|
s++;
|
|
}
|
|
return (true);
|
|
}
|
|
|
|
void
|
|
named_os_chroot(const char *root) {
|
|
char strbuf[ISC_STRERRORSIZE];
|
|
#ifdef HAVE_LIBSCF
|
|
named_smf_chroot = 0;
|
|
#endif /* ifdef HAVE_LIBSCF */
|
|
if (root != NULL) {
|
|
#ifdef HAVE_CHROOT
|
|
if (chroot(root) < 0) {
|
|
strerror_r(errno, strbuf, sizeof(strbuf));
|
|
named_main_earlyfatal("chroot(): %s", strbuf);
|
|
}
|
|
#else /* ifdef HAVE_CHROOT */
|
|
named_main_earlyfatal("chroot(): disabled");
|
|
#endif /* ifdef HAVE_CHROOT */
|
|
if (chdir("/") < 0) {
|
|
strerror_r(errno, strbuf, sizeof(strbuf));
|
|
named_main_earlyfatal("chdir(/): %s", strbuf);
|
|
}
|
|
#ifdef HAVE_LIBSCF
|
|
/* Set named_smf_chroot flag on successful chroot. */
|
|
named_smf_chroot = 1;
|
|
#endif /* ifdef HAVE_LIBSCF */
|
|
}
|
|
}
|
|
|
|
void
|
|
named_os_inituserinfo(const char *username) {
|
|
if (username == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (all_digits(username)) {
|
|
runas_pw = getpwuid((uid_t)atoi(username));
|
|
} else {
|
|
runas_pw = getpwnam(username);
|
|
}
|
|
endpwent();
|
|
|
|
if (runas_pw == NULL) {
|
|
named_main_earlyfatal("user '%s' unknown", username);
|
|
}
|
|
|
|
if (getuid() == 0) {
|
|
char strbuf[ISC_STRERRORSIZE];
|
|
if (initgroups(runas_pw->pw_name, runas_pw->pw_gid) < 0) {
|
|
strerror_r(errno, strbuf, sizeof(strbuf));
|
|
named_main_earlyfatal("initgroups(): %s", strbuf);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
named_os_restoreuser(void) {
|
|
if (runas_pw == NULL || done_setuid) {
|
|
return;
|
|
}
|
|
|
|
REQUIRE(saved_uid != (uid_t)-1);
|
|
REQUIRE(saved_gid != (gid_t)-1);
|
|
|
|
setperms(saved_uid, saved_gid);
|
|
}
|
|
|
|
void
|
|
named_os_changeuser(bool permanent) {
|
|
char strbuf[ISC_STRERRORSIZE];
|
|
if (runas_pw == NULL || done_setuid) {
|
|
return;
|
|
}
|
|
|
|
if (!permanent) {
|
|
saved_uid = getuid();
|
|
saved_gid = getgid();
|
|
|
|
setperms(runas_pw->pw_uid, runas_pw->pw_gid);
|
|
|
|
return;
|
|
}
|
|
|
|
done_setuid = true;
|
|
|
|
if (setgid(runas_pw->pw_gid) == -1) {
|
|
strerror_r(errno, strbuf, sizeof(strbuf));
|
|
named_main_earlyfatal("setgid(): %s", strbuf);
|
|
}
|
|
|
|
if (setuid(runas_pw->pw_uid) == -1) {
|
|
strerror_r(errno, strbuf, sizeof(strbuf));
|
|
named_main_earlyfatal("setuid(): %s", strbuf);
|
|
}
|
|
|
|
#if HAVE_LIBCAP
|
|
/*
|
|
* Restore the ability of named to drop core after the setuid()
|
|
* call has disabled it.
|
|
*/
|
|
if (prctl(PR_SET_DUMPABLE, 1, 0, 0, 0) < 0) {
|
|
strerror_r(errno, strbuf, sizeof(strbuf));
|
|
named_main_earlywarning("prctl(PR_SET_DUMPABLE) failed: %s",
|
|
strbuf);
|
|
}
|
|
|
|
linux_minprivs();
|
|
#endif /* HAVE_LIBCAP */
|
|
}
|
|
|
|
uid_t
|
|
named_os_uid(void) {
|
|
if (runas_pw == NULL) {
|
|
return (0);
|
|
}
|
|
return (runas_pw->pw_uid);
|
|
}
|
|
|
|
void
|
|
named_os_adjustnofile(void) {
|
|
int r;
|
|
struct rlimit rl;
|
|
rlim_t rlim_old;
|
|
char strbuf[ISC_STRERRORSIZE];
|
|
|
|
r = getrlimit(RLIMIT_NOFILE, &rl);
|
|
if (r != 0) {
|
|
goto fail;
|
|
}
|
|
|
|
rlim_old = rl.rlim_cur;
|
|
|
|
if (rl.rlim_cur == rl.rlim_max) {
|
|
isc_log_write(NAMED_LOGCATEGORY_GENERAL, NAMED_LOGMODULE_MAIN,
|
|
ISC_LOG_NOTICE,
|
|
"the limit on open files is already at the "
|
|
"maximum allowed value: "
|
|
"%" PRIu64,
|
|
(uint64_t)rl.rlim_max);
|
|
return;
|
|
}
|
|
|
|
rl.rlim_cur = rl.rlim_max;
|
|
r = setrlimit(RLIMIT_NOFILE, &rl);
|
|
if (r != 0) {
|
|
goto fail;
|
|
}
|
|
|
|
isc_log_write(NAMED_LOGCATEGORY_GENERAL, NAMED_LOGMODULE_MAIN,
|
|
ISC_LOG_NOTICE,
|
|
"adjusted limit on open files from "
|
|
"%" PRIu64 " to "
|
|
"%" PRIu64,
|
|
(uint64_t)rlim_old, (uint64_t)rl.rlim_cur);
|
|
return;
|
|
|
|
fail:
|
|
strerror_r(errno, strbuf, sizeof(strbuf));
|
|
named_main_earlywarning("adjusting limit on open files failed: %s",
|
|
strbuf);
|
|
return;
|
|
}
|
|
|
|
void
|
|
named_os_minprivs(void) {
|
|
#if HAVE_LIBCAP
|
|
linux_keepcaps();
|
|
named_os_changeuser(true);
|
|
linux_minprivs();
|
|
#endif /* HAVE_LIBCAP */
|
|
}
|
|
|
|
static int
|
|
safe_open(const char *filename, mode_t mode, bool append) {
|
|
int fd;
|
|
struct stat sb;
|
|
|
|
if (stat(filename, &sb) == -1) {
|
|
if (errno != ENOENT) {
|
|
return (-1);
|
|
}
|
|
} else if ((sb.st_mode & S_IFREG) == 0) {
|
|
errno = EOPNOTSUPP;
|
|
return (-1);
|
|
}
|
|
|
|
if (append) {
|
|
fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, mode);
|
|
} else {
|
|
if (unlink(filename) < 0 && errno != ENOENT) {
|
|
return (-1);
|
|
}
|
|
fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, mode);
|
|
}
|
|
return (fd);
|
|
}
|
|
|
|
static void
|
|
cleanup_pidfile(void) {
|
|
int n;
|
|
if (pidfile != NULL) {
|
|
n = unlink(pidfile);
|
|
if (n == -1 && errno != ENOENT) {
|
|
named_main_earlywarning("unlink '%s': failed", pidfile);
|
|
}
|
|
free(pidfile);
|
|
}
|
|
pidfile = NULL;
|
|
}
|
|
|
|
/*
|
|
* Ensure that a directory exists.
|
|
* NOTE: This function overwrites the '/' characters in 'filename' with
|
|
* nulls. The caller should copy the filename to a fresh buffer first.
|
|
*/
|
|
static int
|
|
mkdirpath(char *filename, void (*report)(const char *, ...)) {
|
|
char *slash = strrchr(filename, '/');
|
|
char strbuf[ISC_STRERRORSIZE];
|
|
unsigned int mode;
|
|
|
|
if (slash != NULL && slash != filename) {
|
|
struct stat sb;
|
|
*slash = '\0';
|
|
|
|
if (stat(filename, &sb) == -1) {
|
|
if (errno != ENOENT) {
|
|
strerror_r(errno, strbuf, sizeof(strbuf));
|
|
(*report)("couldn't stat '%s': %s", filename,
|
|
strbuf);
|
|
goto error;
|
|
}
|
|
if (mkdirpath(filename, report) == -1) {
|
|
goto error;
|
|
}
|
|
/*
|
|
* Handle "//", "/./" and "/../" in path.
|
|
*/
|
|
if (!strcmp(slash + 1, "") || !strcmp(slash + 1, ".") ||
|
|
!strcmp(slash + 1, ".."))
|
|
{
|
|
*slash = '/';
|
|
return (0);
|
|
}
|
|
mode = S_IRUSR | S_IWUSR | S_IXUSR; /* u=rwx */
|
|
mode |= S_IRGRP | S_IXGRP; /* g=rx */
|
|
mode |= S_IROTH | S_IXOTH; /* o=rx */
|
|
if (mkdir(filename, mode) == -1) {
|
|
strerror_r(errno, strbuf, sizeof(strbuf));
|
|
(*report)("couldn't mkdir '%s': %s", filename,
|
|
strbuf);
|
|
goto error;
|
|
}
|
|
if (runas_pw != NULL &&
|
|
chown(filename, runas_pw->pw_uid,
|
|
runas_pw->pw_gid) == -1)
|
|
{
|
|
strerror_r(errno, strbuf, sizeof(strbuf));
|
|
(*report)("couldn't chown '%s': %s", filename,
|
|
strbuf);
|
|
}
|
|
}
|
|
*slash = '/';
|
|
}
|
|
return (0);
|
|
|
|
error:
|
|
*slash = '/';
|
|
return (-1);
|
|
}
|
|
|
|
FILE *
|
|
named_os_openfile(const char *filename, mode_t mode, bool switch_user) {
|
|
char strbuf[ISC_STRERRORSIZE], *f;
|
|
FILE *fp;
|
|
int fd;
|
|
|
|
/*
|
|
* Make the containing directory if it doesn't exist.
|
|
*/
|
|
f = strdup(filename);
|
|
if (f == NULL) {
|
|
strerror_r(errno, strbuf, sizeof(strbuf));
|
|
named_main_earlywarning("couldn't strdup() '%s': %s", filename,
|
|
strbuf);
|
|
return (NULL);
|
|
}
|
|
if (mkdirpath(f, named_main_earlywarning) == -1) {
|
|
free(f);
|
|
return (NULL);
|
|
}
|
|
free(f);
|
|
|
|
if (switch_user && runas_pw != NULL) {
|
|
/*
|
|
* Temporarily set UID/GID to the one we'll be running with
|
|
* eventually.
|
|
*/
|
|
named_os_changeuser(false);
|
|
|
|
fd = safe_open(filename, mode, false);
|
|
|
|
/* Restore UID/GID to previous uid/gid */
|
|
named_os_restoreuser();
|
|
|
|
if (fd == -1) {
|
|
fd = safe_open(filename, mode, false);
|
|
if (fd != -1) {
|
|
named_main_earlywarning("Required root "
|
|
"permissions to open "
|
|
"'%s'.",
|
|
filename);
|
|
} else {
|
|
named_main_earlywarning("Could not open "
|
|
"'%s'.",
|
|
filename);
|
|
}
|
|
named_main_earlywarning("Please check file and "
|
|
"directory permissions "
|
|
"or reconfigure the filename.");
|
|
}
|
|
} else {
|
|
fd = safe_open(filename, mode, false);
|
|
}
|
|
|
|
if (fd < 0) {
|
|
strerror_r(errno, strbuf, sizeof(strbuf));
|
|
named_main_earlywarning("could not open file '%s': %s",
|
|
filename, strbuf);
|
|
return (NULL);
|
|
}
|
|
|
|
fp = fdopen(fd, "w");
|
|
if (fp == NULL) {
|
|
strerror_r(errno, strbuf, sizeof(strbuf));
|
|
named_main_earlywarning("could not fdopen() file '%s': %s",
|
|
filename, strbuf);
|
|
}
|
|
|
|
return (fp);
|
|
}
|
|
|
|
void
|
|
named_os_writepidfile(const char *filename, bool first_time) {
|
|
FILE *fh;
|
|
pid_t pid;
|
|
char strbuf[ISC_STRERRORSIZE];
|
|
void (*report)(const char *, ...);
|
|
|
|
/*
|
|
* The caller must ensure any required synchronization.
|
|
*/
|
|
|
|
report = first_time ? named_main_earlyfatal : named_main_earlywarning;
|
|
|
|
cleanup_pidfile();
|
|
|
|
if (filename == NULL) {
|
|
return;
|
|
}
|
|
|
|
pidfile = strdup(filename);
|
|
if (pidfile == NULL) {
|
|
strerror_r(errno, strbuf, sizeof(strbuf));
|
|
(*report)("couldn't strdup() '%s': %s", filename, strbuf);
|
|
return;
|
|
}
|
|
|
|
fh = named_os_openfile(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH,
|
|
first_time);
|
|
if (fh == NULL) {
|
|
cleanup_pidfile();
|
|
return;
|
|
}
|
|
pid = getpid();
|
|
if (fprintf(fh, "%ld\n", (long)pid) < 0) {
|
|
(*report)("fprintf() to pid file '%s' failed", filename);
|
|
(void)fclose(fh);
|
|
cleanup_pidfile();
|
|
return;
|
|
}
|
|
if (fflush(fh) == EOF) {
|
|
(*report)("fflush() to pid file '%s' failed", filename);
|
|
(void)fclose(fh);
|
|
cleanup_pidfile();
|
|
return;
|
|
}
|
|
(void)fclose(fh);
|
|
}
|
|
|
|
void
|
|
named_os_shutdown(void) {
|
|
closelog();
|
|
cleanup_pidfile();
|
|
}
|
|
|
|
void
|
|
named_os_shutdownmsg(char *command, isc_buffer_t *text) {
|
|
char *last, *ptr;
|
|
pid_t pid;
|
|
|
|
/* Skip the command name. */
|
|
if (strtok_r(command, " \t", &last) == NULL) {
|
|
return;
|
|
}
|
|
|
|
if ((ptr = strtok_r(NULL, " \t", &last)) == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (strcmp(ptr, "-p") != 0) {
|
|
return;
|
|
}
|
|
|
|
pid = getpid();
|
|
|
|
(void)isc_buffer_printf(text, "pid: %ld", (long)pid);
|
|
}
|
|
|
|
void
|
|
named_os_tzset(void) {
|
|
#ifdef HAVE_TZSET
|
|
tzset();
|
|
#endif /* ifdef HAVE_TZSET */
|
|
}
|
|
|
|
#ifdef HAVE_UNAME
|
|
static char unamebuf[sizeof(struct utsname)];
|
|
#else
|
|
static const char unamebuf[] = { "unknown architecture" };
|
|
#endif
|
|
static const char *unamep = NULL;
|
|
|
|
static void
|
|
getuname(void) {
|
|
#ifdef HAVE_UNAME
|
|
struct utsname uts;
|
|
|
|
memset(&uts, 0, sizeof(uts));
|
|
if (uname(&uts) < 0) {
|
|
snprintf(unamebuf, sizeof(unamebuf), "unknown architecture");
|
|
return;
|
|
}
|
|
|
|
snprintf(unamebuf, sizeof(unamebuf), "%s %s %s %s", uts.sysname,
|
|
uts.machine, uts.release, uts.version);
|
|
#endif /* ifdef HAVE_UNAME */
|
|
unamep = unamebuf;
|
|
}
|
|
|
|
const char *
|
|
named_os_uname(void) {
|
|
if (unamep == NULL) {
|
|
getuname();
|
|
}
|
|
return (unamep);
|
|
}
|