hooks.c revision 219817
1204076Spjd/*- 2204076Spjd * Copyright (c) 2010 The FreeBSD Foundation 3211885Spjd * Copyright (c) 2010 Pawel Jakub Dawidek <pjd@FreeBSD.org> 4204076Spjd * All rights reserved. 5204076Spjd * 6204076Spjd * This software was developed by Pawel Jakub Dawidek under sponsorship from 7204076Spjd * the FreeBSD Foundation. 8204076Spjd * 9204076Spjd * Redistribution and use in source and binary forms, with or without 10204076Spjd * modification, are permitted provided that the following conditions 11204076Spjd * are met: 12204076Spjd * 1. Redistributions of source code must retain the above copyright 13204076Spjd * notice, this list of conditions and the following disclaimer. 14204076Spjd * 2. Redistributions in binary form must reproduce the above copyright 15204076Spjd * notice, this list of conditions and the following disclaimer in the 16204076Spjd * documentation and/or other materials provided with the distribution. 17204076Spjd * 18204076Spjd * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND 19204076Spjd * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20204076Spjd * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21204076Spjd * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE 22204076Spjd * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23204076Spjd * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24204076Spjd * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25204076Spjd * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26204076Spjd * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27204076Spjd * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28204076Spjd * SUCH DAMAGE. 29204076Spjd */ 30204076Spjd 31204076Spjd#include <sys/cdefs.h> 32204076Spjd__FBSDID("$FreeBSD: head/sbin/hastd/hooks.c 219817 2011-03-21 08:38:24Z pjd $"); 33204076Spjd 34204076Spjd#include <sys/types.h> 35211885Spjd#include <sys/sysctl.h> 36204076Spjd#include <sys/wait.h> 37204076Spjd 38204076Spjd#include <assert.h> 39211885Spjd#include <errno.h> 40204076Spjd#include <fcntl.h> 41211885Spjd#include <libgen.h> 42211885Spjd#include <paths.h> 43211885Spjd#include <signal.h> 44211885Spjd#include <stdbool.h> 45211885Spjd#include <stdint.h> 46204076Spjd#include <stdio.h> 47204076Spjd#include <stdlib.h> 48204076Spjd#include <string.h> 49204076Spjd#include <syslog.h> 50211885Spjd#include <unistd.h> 51204076Spjd 52204076Spjd#include <pjdlog.h> 53204076Spjd 54204076Spjd#include "hooks.h" 55219816Spjd#include "subr.h" 56211885Spjd#include "synch.h" 57204076Spjd 58211885Spjd/* Report processes that are running for too long not often than this value. */ 59211885Spjd#define REPORT_INTERVAL 60 60211885Spjd 61211885Spjd/* Are we initialized? */ 62211885Spjdstatic bool hooks_initialized = false; 63211885Spjd 64211885Spjd/* 65211885Spjd * Keep all processes we forked on a global queue, so we can report nicely 66211885Spjd * when they finish or report that they are running for a long time. 67211885Spjd */ 68211885Spjd#define HOOKPROC_MAGIC_ALLOCATED 0x80090ca 69211885Spjd#define HOOKPROC_MAGIC_ONLIST 0x80090c0 70211885Spjdstruct hookproc { 71211885Spjd /* Magic. */ 72211885Spjd int hp_magic; 73211885Spjd /* PID of a forked child. */ 74211885Spjd pid_t hp_pid; 75211885Spjd /* When process were forked? */ 76211885Spjd time_t hp_birthtime; 77211885Spjd /* When we logged previous reported? */ 78211885Spjd time_t hp_lastreport; 79211885Spjd /* Path to executable and all the arguments we passed. */ 80211885Spjd char hp_comm[PATH_MAX]; 81211885Spjd TAILQ_ENTRY(hookproc) hp_next; 82211885Spjd}; 83211885Spjdstatic TAILQ_HEAD(, hookproc) hookprocs; 84211885Spjdstatic pthread_mutex_t hookprocs_lock; 85211885Spjd 86211976Spjdstatic void hook_remove(struct hookproc *hp); 87211976Spjdstatic void hook_free(struct hookproc *hp); 88211976Spjd 89204076Spjdstatic void 90204076Spjddescriptors(void) 91204076Spjd{ 92204076Spjd int fd; 93204076Spjd 94204076Spjd /* 95214119Spjd * Close all (or almost all) descriptors. 96204076Spjd */ 97214119Spjd if (pjdlog_mode_get() == PJDLOG_MODE_STD) { 98214119Spjd closefrom(MAX(MAX(STDIN_FILENO, STDOUT_FILENO), 99214119Spjd STDERR_FILENO) + 1); 100214119Spjd return; 101204076Spjd } 102214119Spjd 103214119Spjd closefrom(0); 104214119Spjd 105204076Spjd /* 106204076Spjd * Redirect stdin, stdout and stderr to /dev/null. 107204076Spjd */ 108204076Spjd fd = open(_PATH_DEVNULL, O_RDONLY); 109204076Spjd if (fd < 0) { 110204076Spjd pjdlog_errno(LOG_WARNING, "Unable to open %s for reading", 111204076Spjd _PATH_DEVNULL); 112204076Spjd } else if (fd != STDIN_FILENO) { 113204076Spjd if (dup2(fd, STDIN_FILENO) < 0) { 114204076Spjd pjdlog_errno(LOG_WARNING, 115204076Spjd "Unable to duplicate descriptor for stdin"); 116204076Spjd } 117204076Spjd close(fd); 118204076Spjd } 119204076Spjd fd = open(_PATH_DEVNULL, O_WRONLY); 120204076Spjd if (fd < 0) { 121204076Spjd pjdlog_errno(LOG_WARNING, "Unable to open %s for writing", 122204076Spjd _PATH_DEVNULL); 123204076Spjd } else { 124204076Spjd if (fd != STDOUT_FILENO && dup2(fd, STDOUT_FILENO) < 0) { 125204076Spjd pjdlog_errno(LOG_WARNING, 126204076Spjd "Unable to duplicate descriptor for stdout"); 127204076Spjd } 128204076Spjd if (fd != STDERR_FILENO && dup2(fd, STDERR_FILENO) < 0) { 129204076Spjd pjdlog_errno(LOG_WARNING, 130204076Spjd "Unable to duplicate descriptor for stderr"); 131204076Spjd } 132204076Spjd if (fd != STDOUT_FILENO && fd != STDERR_FILENO) 133204076Spjd close(fd); 134204076Spjd } 135204076Spjd} 136204076Spjd 137211885Spjdvoid 138211885Spjdhook_init(void) 139211885Spjd{ 140211885Spjd 141211976Spjd assert(!hooks_initialized); 142211976Spjd 143211885Spjd mtx_init(&hookprocs_lock); 144211885Spjd TAILQ_INIT(&hookprocs); 145211885Spjd hooks_initialized = true; 146211885Spjd} 147211885Spjd 148211976Spjdvoid 149211976Spjdhook_fini(void) 150211976Spjd{ 151211976Spjd struct hookproc *hp; 152211976Spjd 153211976Spjd assert(hooks_initialized); 154211976Spjd 155211976Spjd mtx_lock(&hookprocs_lock); 156211976Spjd while ((hp = TAILQ_FIRST(&hookprocs)) != NULL) { 157211976Spjd assert(hp->hp_magic == HOOKPROC_MAGIC_ONLIST); 158211976Spjd assert(hp->hp_pid > 0); 159211976Spjd 160211976Spjd hook_remove(hp); 161211976Spjd hook_free(hp); 162211976Spjd } 163211976Spjd mtx_unlock(&hookprocs_lock); 164211976Spjd 165211976Spjd mtx_destroy(&hookprocs_lock); 166211976Spjd TAILQ_INIT(&hookprocs); 167211976Spjd hooks_initialized = false; 168211976Spjd} 169211976Spjd 170211885Spjdstatic struct hookproc * 171211885Spjdhook_alloc(const char *path, char **args) 172211885Spjd{ 173211885Spjd struct hookproc *hp; 174211885Spjd unsigned int ii; 175211885Spjd 176211885Spjd hp = malloc(sizeof(*hp)); 177211885Spjd if (hp == NULL) { 178211885Spjd pjdlog_error("Unable to allocate %zu bytes of memory for a hook.", 179211885Spjd sizeof(*hp)); 180211885Spjd return (NULL); 181211885Spjd } 182211885Spjd 183211885Spjd hp->hp_pid = 0; 184211885Spjd hp->hp_birthtime = hp->hp_lastreport = time(NULL); 185211885Spjd (void)strlcpy(hp->hp_comm, path, sizeof(hp->hp_comm)); 186211885Spjd /* We start at 2nd argument as we don't want to have exec name twice. */ 187211885Spjd for (ii = 1; args[ii] != NULL; ii++) { 188219816Spjd (void)snprlcat(hp->hp_comm, sizeof(hp->hp_comm), " %s", 189219816Spjd args[ii]); 190211885Spjd } 191211885Spjd if (strlen(hp->hp_comm) >= sizeof(hp->hp_comm) - 1) { 192211885Spjd pjdlog_error("Exec path too long, correct configuration file."); 193211885Spjd free(hp); 194211885Spjd return (NULL); 195211885Spjd } 196211885Spjd hp->hp_magic = HOOKPROC_MAGIC_ALLOCATED; 197211885Spjd return (hp); 198211885Spjd} 199211885Spjd 200211885Spjdstatic void 201211885Spjdhook_add(struct hookproc *hp, pid_t pid) 202211885Spjd{ 203211885Spjd 204211885Spjd assert(hp->hp_magic == HOOKPROC_MAGIC_ALLOCATED); 205211885Spjd assert(hp->hp_pid == 0); 206211885Spjd 207211885Spjd hp->hp_pid = pid; 208211885Spjd mtx_lock(&hookprocs_lock); 209211885Spjd hp->hp_magic = HOOKPROC_MAGIC_ONLIST; 210211885Spjd TAILQ_INSERT_TAIL(&hookprocs, hp, hp_next); 211211885Spjd mtx_unlock(&hookprocs_lock); 212211885Spjd} 213211885Spjd 214211885Spjdstatic void 215211885Spjdhook_remove(struct hookproc *hp) 216211885Spjd{ 217211885Spjd 218211885Spjd assert(hp->hp_magic == HOOKPROC_MAGIC_ONLIST); 219211885Spjd assert(hp->hp_pid > 0); 220211885Spjd assert(mtx_owned(&hookprocs_lock)); 221211885Spjd 222211885Spjd TAILQ_REMOVE(&hookprocs, hp, hp_next); 223211885Spjd hp->hp_magic = HOOKPROC_MAGIC_ALLOCATED; 224211885Spjd} 225211885Spjd 226211885Spjdstatic void 227211885Spjdhook_free(struct hookproc *hp) 228211885Spjd{ 229211885Spjd 230211885Spjd assert(hp->hp_magic == HOOKPROC_MAGIC_ALLOCATED); 231211885Spjd assert(hp->hp_pid > 0); 232211885Spjd 233211885Spjd hp->hp_magic = 0; 234211885Spjd free(hp); 235211885Spjd} 236211885Spjd 237211885Spjdstatic struct hookproc * 238211885Spjdhook_find(pid_t pid) 239211885Spjd{ 240211885Spjd struct hookproc *hp; 241211885Spjd 242211885Spjd assert(mtx_owned(&hookprocs_lock)); 243211885Spjd 244211885Spjd TAILQ_FOREACH(hp, &hookprocs, hp_next) { 245211885Spjd assert(hp->hp_magic == HOOKPROC_MAGIC_ONLIST); 246211885Spjd assert(hp->hp_pid > 0); 247211885Spjd 248211885Spjd if (hp->hp_pid == pid) 249211885Spjd break; 250211885Spjd } 251211885Spjd 252211885Spjd return (hp); 253211885Spjd} 254211885Spjd 255211885Spjdvoid 256211976Spjdhook_check_one(pid_t pid, int status) 257211976Spjd{ 258211976Spjd struct hookproc *hp; 259211976Spjd 260211976Spjd mtx_lock(&hookprocs_lock); 261211976Spjd hp = hook_find(pid); 262211976Spjd if (hp == NULL) { 263211976Spjd mtx_unlock(&hookprocs_lock); 264211976Spjd pjdlog_debug(1, "Unknown process pid=%u", pid); 265211976Spjd return; 266211976Spjd } 267211976Spjd hook_remove(hp); 268211976Spjd mtx_unlock(&hookprocs_lock); 269211976Spjd if (WIFEXITED(status) && WEXITSTATUS(status) == 0) { 270211976Spjd pjdlog_debug(1, "Hook exited gracefully (pid=%u, cmd=[%s]).", 271211976Spjd pid, hp->hp_comm); 272211976Spjd } else if (WIFSIGNALED(status)) { 273211976Spjd pjdlog_error("Hook was killed (pid=%u, signal=%d, cmd=[%s]).", 274211976Spjd pid, WTERMSIG(status), hp->hp_comm); 275211976Spjd } else { 276211976Spjd pjdlog_error("Hook exited ungracefully (pid=%u, exitcode=%d, cmd=[%s]).", 277211976Spjd pid, WIFEXITED(status) ? WEXITSTATUS(status) : -1, 278211976Spjd hp->hp_comm); 279211976Spjd } 280211976Spjd hook_free(hp); 281211976Spjd} 282211976Spjd 283211976Spjdvoid 284213429Spjdhook_check(void) 285211885Spjd{ 286211885Spjd struct hookproc *hp, *hp2; 287211885Spjd time_t now; 288211885Spjd 289211885Spjd assert(hooks_initialized); 290211885Spjd 291219817Spjd pjdlog_debug(1, "Checking hooks."); 292219817Spjd 293211885Spjd /* 294211885Spjd * Report about processes that are running for a long time. 295211885Spjd */ 296211885Spjd now = time(NULL); 297211885Spjd mtx_lock(&hookprocs_lock); 298211885Spjd TAILQ_FOREACH_SAFE(hp, &hookprocs, hp_next, hp2) { 299211885Spjd assert(hp->hp_magic == HOOKPROC_MAGIC_ONLIST); 300211885Spjd assert(hp->hp_pid > 0); 301211885Spjd 302211885Spjd /* 303211885Spjd * If process doesn't exists we somehow missed it. 304211885Spjd * Not much can be done expect for logging this situation. 305211885Spjd */ 306211885Spjd if (kill(hp->hp_pid, 0) == -1 && errno == ESRCH) { 307211885Spjd pjdlog_warning("Hook disappeared (pid=%u, cmd=[%s]).", 308211885Spjd hp->hp_pid, hp->hp_comm); 309211885Spjd hook_remove(hp); 310211885Spjd hook_free(hp); 311211885Spjd continue; 312211885Spjd } 313211885Spjd 314211885Spjd /* 315211885Spjd * Skip proccesses younger than 1 minute. 316211885Spjd */ 317211885Spjd if (now - hp->hp_lastreport < REPORT_INTERVAL) 318211885Spjd continue; 319211885Spjd 320211885Spjd /* 321211885Spjd * Hook is running for too long, report it. 322211885Spjd */ 323211885Spjd pjdlog_warning("Hook is running for %ju seconds (pid=%u, cmd=[%s]).", 324211885Spjd (uintmax_t)(now - hp->hp_birthtime), hp->hp_pid, 325211885Spjd hp->hp_comm); 326211885Spjd hp->hp_lastreport = now; 327211885Spjd } 328211885Spjd mtx_unlock(&hookprocs_lock); 329211885Spjd} 330211885Spjd 331211885Spjdvoid 332204076Spjdhook_exec(const char *path, ...) 333204076Spjd{ 334204076Spjd va_list ap; 335204076Spjd 336204076Spjd va_start(ap, path); 337211885Spjd hook_execv(path, ap); 338204076Spjd va_end(ap); 339204076Spjd} 340204076Spjd 341211885Spjdvoid 342204076Spjdhook_execv(const char *path, va_list ap) 343204076Spjd{ 344211885Spjd struct hookproc *hp; 345204076Spjd char *args[64]; 346204076Spjd unsigned int ii; 347213938Spjd sigset_t mask; 348211885Spjd pid_t pid; 349204076Spjd 350211885Spjd assert(hooks_initialized); 351211885Spjd 352204076Spjd if (path == NULL || path[0] == '\0') 353211885Spjd return; 354204076Spjd 355204076Spjd memset(args, 0, sizeof(args)); 356204076Spjd args[0] = basename(path); 357204076Spjd for (ii = 1; ii < sizeof(args) / sizeof(args[0]); ii++) { 358204076Spjd args[ii] = va_arg(ap, char *); 359204076Spjd if (args[ii] == NULL) 360204076Spjd break; 361204076Spjd } 362204076Spjd assert(ii < sizeof(args) / sizeof(args[0])); 363204076Spjd 364211885Spjd hp = hook_alloc(path, args); 365211885Spjd if (hp == NULL) 366211885Spjd return; 367211885Spjd 368219817Spjd pjdlog_debug(1, "Executing hook: %s", hp->hp_comm); 369219817Spjd 370204076Spjd pid = fork(); 371204076Spjd switch (pid) { 372204076Spjd case -1: /* Error. */ 373211885Spjd pjdlog_errno(LOG_ERR, "Unable to fork to execute %s", path); 374213183Spjd hook_free(hp); 375211885Spjd return; 376204076Spjd case 0: /* Child. */ 377204076Spjd descriptors(); 378213938Spjd PJDLOG_VERIFY(sigemptyset(&mask) == 0); 379213938Spjd PJDLOG_VERIFY(sigprocmask(SIG_SETMASK, &mask, NULL) == 0); 380217308Spjd /* 381217312Spjd * Dummy handler set for SIGCHLD in the parent will be restored 382217312Spjd * to SIG_IGN on execv(3) below, so there is no need to do 383217312Spjd * anything with it. 384217308Spjd */ 385204076Spjd execv(path, args); 386204076Spjd pjdlog_errno(LOG_ERR, "Unable to execute %s", path); 387204076Spjd exit(EX_SOFTWARE); 388204076Spjd default: /* Parent. */ 389211885Spjd hook_add(hp, pid); 390204076Spjd break; 391204076Spjd } 392204076Spjd} 393