/* $NetBSD: os.c,v 1.11 2022/09/23 12:15:22 christos Exp $ */
/*
* 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.
*/
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <io.h>
#include <process.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <syslog.h>
#include <isc/ntpaths.h>
#include <isc/print.h>
#include <isc/result.h>
#include <isc/string.h>
#include <isc/util.h>
#include <isc/win32os.h>
#include <named/globals.h>
#include <named/log.h>
#include <named/main.h>
#include <named/ntservice.h>
#include <named/os.h>
static char *lockfile = NULL;
static char *pidfile = NULL;
static int devnullfd = -1;
static int lockfilefd = -1;
static BOOL Initialized = FALSE;
static char *version_error = "named requires Windows 2000 Service Pack 2 or "
"later to run correctly";
void
named_paths_init(void) {
if (!Initialized) {
isc_ntpaths_init();
}
named_g_conffile = isc_ntpaths_get(NAMED_CONF_PATH);
named_g_defaultpidfile = isc_ntpaths_get(NAMED_PID_PATH);
named_g_defaultlockfile = isc_ntpaths_get(NAMED_LOCK_PATH);
named_g_keyfile = isc_ntpaths_get(RNDC_KEY_PATH);
named_g_defaultsessionkeyfile = isc_ntpaths_get(SESSION_KEY_PATH);
named_g_defaultbindkeys = isc_ntpaths_get(BIND_KEYS_PATH);
Initialized = TRUE;
}
/*
* Due to Knowledge base article Q263823 we need to make sure that
* Windows 2000 systems have Service Pack 2 or later installed and
* warn when it isn't.
*/
static void
version_check(const char *progname) {
if ((isc_win32os_versioncheck(4, 0, 0, 0) >= 0) &&
(isc_win32os_versioncheck(5, 0, 0, 0) < 0))
{
return; /* No problem with Version 4.0 */
}
if (isc_win32os_versioncheck(5, 0, 2, 0) < 0) {
if (ntservice_isservice()) {
NTReportError(progname, version_error);
} else {
fprintf(stderr, "%s\n", version_error);
}
}
}
static void
setup_syslog(const char *progname) {
int options;
options = LOG_PID;
#ifdef LOG_NDELAY
options |= LOG_NDELAY;
#endif /* ifdef LOG_NDELAY */
openlog(progname, options, LOG_DAEMON);
}
void
named_os_init(const char *progname) {
named_paths_init();
setup_syslog(progname);
/*
* XXXMPA. We may need to split ntservice_init() in two and
* just mark as running in named_os_started(). If we do that
* this is where the first part of ntservice_init() should be
* called from.
*
* XXX970 Remove comment if no problems by 9.7.0.
*
* ntservice_init();
*/
version_check(progname);
/*
* If running in a Cygwin environment, clear the SEM_NOGPFAULTERRORBOX
* bit in the process error mode to prevent Cygwin from concealing
* non-abort() crashes, giving Windows Error Reporting a chance to
* handle such crashes. This is done to ensure all crashes triggered
* by system tests can be detected.
*/
if (getenv("CYGWIN") != NULL) {
SetErrorMode(GetErrorMode() & ~SEM_NOGPFAULTERRORBOX);
}
}
void
named_os_daemonize(void) {
/*
* Try to set stdin, stdout, and stderr to /dev/null, but press
* on even if it fails.
*/
if (devnullfd != -1) {
if (devnullfd != _fileno(stdin)) {
close(_fileno(stdin));
(void)_dup2(devnullfd, _fileno(stdin));
}
if (devnullfd != _fileno(stdout)) {
close(_fileno(stdout));
(void)_dup2(devnullfd, _fileno(stdout));
}
if (devnullfd != _fileno(stderr)) {
close(_fileno(stderr));
(void)_dup2(devnullfd, _fileno(stderr));
}
}
}
void
named_os_opendevnull(void) {
devnullfd = open("NUL", O_RDWR, 0);
}
void
named_os_closedevnull(void) {
if (devnullfd != _fileno(stdin) && devnullfd != _fileno(stdout) &&
devnullfd != _fileno(stderr))
{
close(devnullfd);
devnullfd = -1;
}
}
void
named_os_chroot(const char *root) {
if (root != NULL) {
named_main_earlyfatal("chroot(): isn't supported by Win32 API");
}
}
void
named_os_inituserinfo(const char *username) {}
void
named_os_changeuser(void) {}
unsigned int
ns_os_uid(void) {
return (0);
}
void
named_os_adjustnofile(void) {}
void
named_os_minprivs(void) {}
static int
safe_open(const char *filename, int 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) {
return (-1);
}
if (append) {
fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, mode);
} else {
(void)unlink(filename);
fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, mode);
}
return (fd);
}
static void
cleanup_pidfile(void) {
if (pidfile != NULL) {
(void)unlink(pidfile);
free(pidfile);
}
pidfile = NULL;
}
static void
cleanup_lockfile(void) {
if (lockfilefd != -1) {
close(lockfilefd);
lockfilefd = -1;
}
if (lockfile != NULL) {
int n = unlink(lockfile);
if (n == -1 && errno != ENOENT) {
named_main_earlywarning("unlink '%s': failed",
lockfile);
}
free(lockfile);
lockfile = NULL;
}
}
FILE *
named_os_openfile(const char *filename, int mode, bool switch_user) {
char strbuf[ISC_STRERRORSIZE];
FILE *fp;
int fd;
UNUSED(switch_user);
fd = safe_open(filename, mode, false);
if (fd < 0) {
strerror_s(strbuf, sizeof(strbuf), errno);
named_main_earlywarning("could not open file '%s': %s",
filename, strbuf);
return (NULL);
}
fp = fdopen(fd, "w");
if (fp == NULL) {
strerror_s(strbuf, sizeof(strbuf), errno);
named_main_earlywarning("could not fdopen() file '%s': %s",
filename, strbuf);
close(fd);
}
return (fp);
}
void
named_os_writepidfile(const char *filename, bool first_time) {
FILE *pidlockfile;
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_s(strbuf, sizeof(strbuf), errno);
(*report)("couldn't strdup() '%s': %s", filename, strbuf);
return;
}
pidlockfile = named_os_openfile(
filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH, false);
if (pidlockfile == NULL) {
free(pidfile);
pidfile = NULL;
return;
}
pid = getpid();
if (fprintf(pidlockfile, "%ld\n", (long)pid) < 0) {
(*report)("fprintf() to pid file '%s' failed", filename);
(void)fclose(pidlockfile);
cleanup_pidfile();
return;
}
if (fflush(pidlockfile) == EOF) {
(*report)("fflush() to pid file '%s' failed", filename);
(void)fclose(pidlockfile);
cleanup_pidfile();
return;
}
(void)fclose(pidlockfile);
}
bool
named_os_issingleton(const char *filename) {
char strbuf[ISC_STRERRORSIZE];
OVERLAPPED o;
if (lockfilefd != -1) {
return (true);
}
if (strcasecmp(filename, "none") == 0) {
return (true);
}
lockfile = strdup(filename);
if (lockfile == NULL) {
strerror_s(strbuf, sizeof(strbuf), errno);
named_main_earlyfatal("couldn't allocate memory for '%s': %s",
filename, strbuf);
}
/*
* named_os_openfile() uses safeopen() which removes any existing
* files. We can't use that here.
*/
lockfilefd = open(filename, O_WRONLY | O_CREAT,
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
if (lockfilefd == -1) {
cleanup_lockfile();
return (false);
}
memset(&o, 0, sizeof(o));
/* Expect ERROR_LOCK_VIOLATION if already locked */
if (!LockFileEx((HANDLE)_get_osfhandle(lockfilefd),
LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY, 0,
0, 1, &o))
{
cleanup_lockfile();
return (false);
}
return (true);
}
void
named_os_shutdown(void) {
closelog();
cleanup_pidfile();
if (lockfilefd != -1) {
(void)UnlockFile((HANDLE)_get_osfhandle(lockfilefd), 0, 0, 0,
1);
}
cleanup_lockfile();
ntservice_shutdown(); /* This MUST be the last thing done */
}
isc_result_t
named_os_gethostname(char *buf, size_t len) {
int n;
n = gethostname(buf, (int)len);
return ((n == 0) ? ISC_R_SUCCESS : ISC_R_FAILURE);
}
void
named_os_shutdownmsg(char *command, isc_buffer_t *text) {
UNUSED(command);
UNUSED(text);
}
void
named_os_tzset(void) {
#ifdef HAVE_TZSET
tzset();
#endif /* ifdef HAVE_TZSET */
}
void
named_os_started(void) {
ntservice_init();
}
static char unamebuf[BUFSIZ];
static const char *unamep = NULL;
static void
getuname(void) {
DWORD fvilen;
char *fvi;
VS_FIXEDFILEINFO *ffi;
UINT ffilen;
SYSTEM_INFO sysinfo;
char *arch;
fvi = NULL;
fvilen = GetFileVersionInfoSize("kernel32.dll", 0);
if (fvilen == 0) {
goto err;
}
fvi = (char *)malloc(fvilen);
if (fvi == NULL) {
goto err;
}
memset(fvi, 0, fvilen);
if (GetFileVersionInfo("kernel32.dll", 0, fvilen, fvi) == 0) {
goto err;
}
ffi = NULL;
ffilen = 0;
if ((VerQueryValue(fvi, "\\", &ffi, &ffilen) == 0) || (ffi == NULL) ||
(ffilen == 0))
{
goto err;
}
memset(&sysinfo, 0, sizeof(sysinfo));
GetSystemInfo(&sysinfo);
switch (sysinfo.wProcessorArchitecture) {
case PROCESSOR_ARCHITECTURE_INTEL:
arch = "x86";
break;
case PROCESSOR_ARCHITECTURE_ARM:
arch = "arm";
break;
case PROCESSOR_ARCHITECTURE_IA64:
arch = "ia64";
break;
case PROCESSOR_ARCHITECTURE_AMD64:
arch = "x64";
break;
default:
arch = "unknown architecture";
break;
}
snprintf(unamebuf, sizeof(unamebuf),
"Windows %d %d build %d %d for %s\n",
(ffi->dwProductVersionMS >> 16) & 0xffff,
ffi->dwProductVersionMS & 0xffff,
(ffi->dwProductVersionLS >> 16) & 0xffff,
ffi->dwProductVersionLS & 0xffff, arch);
err:
if (fvi != NULL) {
free(fvi);
}
unamep = unamebuf;
}
/*
* GetVersionEx() returns 6.2 (aka Windows 8.1) since it was obsoleted
* so we had to switch to the recommended way to get the Windows version.
*/
const char *
named_os_uname(void) {
if (unamep == NULL) {
getuname();
}
return (unamep);
}