hooks.c revision 229509
1231200Smm/*- 2231200Smm * Copyright (c) 2010 The FreeBSD Foundation 3231200Smm * Copyright (c) 2010 Pawel Jakub Dawidek <pjd@FreeBSD.org> 4231200Smm * All rights reserved. 5231200Smm * 6231200Smm * This software was developed by Pawel Jakub Dawidek under sponsorship from 7231200Smm * the FreeBSD Foundation. 8231200Smm * 9231200Smm * Redistribution and use in source and binary forms, with or without 10231200Smm * modification, are permitted provided that the following conditions 11231200Smm * are met: 12231200Smm * 1. Redistributions of source code must retain the above copyright 13231200Smm * notice, this list of conditions and the following disclaimer. 14231200Smm * 2. Redistributions in binary form must reproduce the above copyright 15231200Smm * notice, this list of conditions and the following disclaimer in the 16231200Smm * documentation and/or other materials provided with the distribution. 17231200Smm * 18231200Smm * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND 19231200Smm * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20231200Smm * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21231200Smm * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE 22231200Smm * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23231200Smm * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24231200Smm * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25231200Smm * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26231200Smm * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27231200Smm * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28231200Smm * SUCH DAMAGE. 29231200Smm */ 30231200Smm 31231200Smm#include <sys/cdefs.h> 32231200Smm__FBSDID("$FreeBSD: stable/9/sbin/hastd/hooks.c 229509 2012-01-04 17:22:10Z trociny $"); 33231200Smm 34231200Smm#include <sys/types.h> 35231200Smm#include <sys/sysctl.h> 36231200Smm#include <sys/wait.h> 37231200Smm 38231200Smm#include <errno.h> 39231200Smm#include <fcntl.h> 40231200Smm#include <libgen.h> 41231200Smm#include <paths.h> 42231200Smm#include <signal.h> 43231200Smm#include <stdbool.h> 44231200Smm#include <stdint.h> 45231200Smm#include <stdio.h> 46231200Smm#include <stdlib.h> 47231200Smm#include <string.h> 48231200Smm#include <syslog.h> 49231200Smm#include <unistd.h> 50231200Smm 51231200Smm#include <pjdlog.h> 52231200Smm 53231200Smm#include "hooks.h" 54231200Smm#include "subr.h" 55231200Smm#include "synch.h" 56231200Smm 57231200Smm/* Report processes that are running for too long not often than this value. */ 58231200Smm#define REPORT_INTERVAL 60 59231200Smm 60231200Smm/* Are we initialized? */ 61231200Smmstatic bool hooks_initialized = false; 62231200Smm 63231200Smm/* 64231200Smm * Keep all processes we forked on a global queue, so we can report nicely 65231200Smm * when they finish or report that they are running for a long time. 66231200Smm */ 67231200Smm#define HOOKPROC_MAGIC_ALLOCATED 0x80090ca 68231200Smm#define HOOKPROC_MAGIC_ONLIST 0x80090c0 69231200Smmstruct hookproc { 70231200Smm /* Magic. */ 71231200Smm int hp_magic; 72231200Smm /* PID of a forked child. */ 73231200Smm pid_t hp_pid; 74231200Smm /* When process were forked? */ 75231200Smm time_t hp_birthtime; 76231200Smm /* When we logged previous reported? */ 77231200Smm time_t hp_lastreport; 78231200Smm /* Path to executable and all the arguments we passed. */ 79231200Smm char hp_comm[PATH_MAX]; 80231200Smm TAILQ_ENTRY(hookproc) hp_next; 81231200Smm}; 82231200Smmstatic TAILQ_HEAD(, hookproc) hookprocs; 83231200Smmstatic pthread_mutex_t hookprocs_lock; 84231200Smm 85231200Smmstatic void hook_remove(struct hookproc *hp); 86231200Smmstatic void hook_free(struct hookproc *hp); 87231200Smm 88231200Smmstatic void 89231200Smmdescriptors(void) 90231200Smm{ 91231200Smm int fd; 92231200Smm 93231200Smm /* 94231200Smm * Close all (or almost all) descriptors. 95231200Smm */ 96231200Smm if (pjdlog_mode_get() == PJDLOG_MODE_STD) { 97231200Smm closefrom(MAX(MAX(STDIN_FILENO, STDOUT_FILENO), 98231200Smm STDERR_FILENO) + 1); 99231200Smm return; 100231200Smm } 101231200Smm 102231200Smm closefrom(0); 103231200Smm 104231200Smm /* 105231200Smm * Redirect stdin, stdout and stderr to /dev/null. 106231200Smm */ 107231200Smm fd = open(_PATH_DEVNULL, O_RDONLY); 108231200Smm if (fd < 0) { 109231200Smm pjdlog_errno(LOG_WARNING, "Unable to open %s for reading", 110231200Smm _PATH_DEVNULL); 111231200Smm } else if (fd != STDIN_FILENO) { 112231200Smm if (dup2(fd, STDIN_FILENO) < 0) { 113231200Smm pjdlog_errno(LOG_WARNING, 114231200Smm "Unable to duplicate descriptor for stdin"); 115231200Smm } 116231200Smm close(fd); 117231200Smm } 118231200Smm fd = open(_PATH_DEVNULL, O_WRONLY); 119231200Smm if (fd < 0) { 120231200Smm pjdlog_errno(LOG_WARNING, "Unable to open %s for writing", 121231200Smm _PATH_DEVNULL); 122231200Smm } else { 123231200Smm if (fd != STDOUT_FILENO && dup2(fd, STDOUT_FILENO) < 0) { 124231200Smm pjdlog_errno(LOG_WARNING, 125231200Smm "Unable to duplicate descriptor for stdout"); 126231200Smm } 127231200Smm if (fd != STDERR_FILENO && dup2(fd, STDERR_FILENO) < 0) { 128231200Smm pjdlog_errno(LOG_WARNING, 129231200Smm "Unable to duplicate descriptor for stderr"); 130231200Smm } 131231200Smm if (fd != STDOUT_FILENO && fd != STDERR_FILENO) 132231200Smm close(fd); 133231200Smm } 134231200Smm} 135231200Smm 136231200Smmvoid 137231200Smmhook_init(void) 138231200Smm{ 139231200Smm 140231200Smm PJDLOG_ASSERT(!hooks_initialized); 141231200Smm 142231200Smm mtx_init(&hookprocs_lock); 143231200Smm TAILQ_INIT(&hookprocs); 144231200Smm hooks_initialized = true; 145231200Smm} 146231200Smm 147231200Smmvoid 148231200Smmhook_fini(void) 149231200Smm{ 150231200Smm struct hookproc *hp; 151231200Smm 152231200Smm PJDLOG_ASSERT(hooks_initialized); 153231200Smm 154231200Smm mtx_lock(&hookprocs_lock); 155231200Smm while ((hp = TAILQ_FIRST(&hookprocs)) != NULL) { 156231200Smm PJDLOG_ASSERT(hp->hp_magic == HOOKPROC_MAGIC_ONLIST); 157231200Smm PJDLOG_ASSERT(hp->hp_pid > 0); 158231200Smm 159231200Smm hook_remove(hp); 160231200Smm hook_free(hp); 161231200Smm } 162231200Smm mtx_unlock(&hookprocs_lock); 163231200Smm 164231200Smm mtx_destroy(&hookprocs_lock); 165231200Smm TAILQ_INIT(&hookprocs); 166231200Smm hooks_initialized = false; 167231200Smm} 168231200Smm 169231200Smmstatic struct hookproc * 170231200Smmhook_alloc(const char *path, char **args) 171231200Smm{ 172231200Smm struct hookproc *hp; 173231200Smm unsigned int ii; 174231200Smm 175231200Smm hp = malloc(sizeof(*hp)); 176231200Smm if (hp == NULL) { 177231200Smm pjdlog_error("Unable to allocate %zu bytes of memory for a hook.", 178231200Smm sizeof(*hp)); 179231200Smm return (NULL); 180231200Smm } 181231200Smm 182231200Smm hp->hp_pid = 0; 183231200Smm hp->hp_birthtime = hp->hp_lastreport = time(NULL); 184231200Smm (void)strlcpy(hp->hp_comm, path, sizeof(hp->hp_comm)); 185231200Smm /* We start at 2nd argument as we don't want to have exec name twice. */ 186231200Smm for (ii = 1; args[ii] != NULL; ii++) { 187231200Smm (void)snprlcat(hp->hp_comm, sizeof(hp->hp_comm), " %s", 188231200Smm args[ii]); 189299529Smm } 190231200Smm if (strlen(hp->hp_comm) >= sizeof(hp->hp_comm) - 1) { 191231200Smm pjdlog_error("Exec path too long, correct configuration file."); 192231200Smm free(hp); 193231200Smm return (NULL); 194231200Smm } 195231200Smm hp->hp_magic = HOOKPROC_MAGIC_ALLOCATED; 196231200Smm return (hp); 197231200Smm} 198231200Smm 199231200Smmstatic void 200231200Smmhook_add(struct hookproc *hp, pid_t pid) 201231200Smm{ 202231200Smm 203248616Smm PJDLOG_ASSERT(hp->hp_magic == HOOKPROC_MAGIC_ALLOCATED); 204248616Smm PJDLOG_ASSERT(hp->hp_pid == 0); 205248616Smm 206248616Smm hp->hp_pid = pid; 207248616Smm mtx_lock(&hookprocs_lock); 208248616Smm hp->hp_magic = HOOKPROC_MAGIC_ONLIST; 209248616Smm TAILQ_INSERT_TAIL(&hookprocs, hp, hp_next); 210231200Smm mtx_unlock(&hookprocs_lock); 211231200Smm} 212231200Smm 213231200Smmstatic void 214231200Smmhook_remove(struct hookproc *hp) 215231200Smm{ 216231200Smm 217231200Smm PJDLOG_ASSERT(hp->hp_magic == HOOKPROC_MAGIC_ONLIST); 218231200Smm PJDLOG_ASSERT(hp->hp_pid > 0); 219231200Smm PJDLOG_ASSERT(mtx_owned(&hookprocs_lock)); 220231200Smm 221231200Smm TAILQ_REMOVE(&hookprocs, hp, hp_next); 222231200Smm hp->hp_magic = HOOKPROC_MAGIC_ALLOCATED; 223231200Smm} 224231200Smm 225231200Smmstatic void 226231200Smmhook_free(struct hookproc *hp) 227231200Smm{ 228248616Smm 229299529Smm PJDLOG_ASSERT(hp->hp_magic == HOOKPROC_MAGIC_ALLOCATED); 230231200Smm PJDLOG_ASSERT(hp->hp_pid > 0); 231231200Smm 232231200Smm hp->hp_magic = 0; 233231200Smm free(hp); 234231200Smm} 235231200Smm 236231200Smmstatic struct hookproc * 237231200Smmhook_find(pid_t pid) 238231200Smm{ 239231200Smm struct hookproc *hp; 240231200Smm 241231200Smm PJDLOG_ASSERT(mtx_owned(&hookprocs_lock)); 242231200Smm 243231200Smm TAILQ_FOREACH(hp, &hookprocs, hp_next) { 244231200Smm PJDLOG_ASSERT(hp->hp_magic == HOOKPROC_MAGIC_ONLIST); 245231200Smm PJDLOG_ASSERT(hp->hp_pid > 0); 246231200Smm 247248616Smm if (hp->hp_pid == pid) 248231200Smm break; 249231200Smm } 250231200Smm 251231200Smm return (hp); 252231200Smm} 253231200Smm 254231200Smmvoid 255231200Smmhook_check_one(pid_t pid, int status) 256231200Smm{ 257248616Smm struct hookproc *hp; 258248616Smm 259248616Smm mtx_lock(&hookprocs_lock); 260248616Smm hp = hook_find(pid); 261231200Smm if (hp == NULL) { 262231200Smm mtx_unlock(&hookprocs_lock); 263231200Smm pjdlog_debug(1, "Unknown process pid=%u", pid); 264231200Smm return; 265231200Smm } 266231200Smm hook_remove(hp); 267231200Smm mtx_unlock(&hookprocs_lock); 268231200Smm if (WIFEXITED(status) && WEXITSTATUS(status) == 0) { 269231200Smm pjdlog_debug(1, "Hook exited gracefully (pid=%u, cmd=[%s]).", 270231200Smm pid, hp->hp_comm); 271231200Smm } else if (WIFSIGNALED(status)) { 272231200Smm pjdlog_error("Hook was killed (pid=%u, signal=%d, cmd=[%s]).", 273231200Smm pid, WTERMSIG(status), hp->hp_comm); 274231200Smm } else { 275231200Smm pjdlog_error("Hook exited ungracefully (pid=%u, exitcode=%d, cmd=[%s]).", 276231200Smm pid, WIFEXITED(status) ? WEXITSTATUS(status) : -1, 277231200Smm hp->hp_comm); 278231200Smm } 279231200Smm hook_free(hp); 280231200Smm} 281231200Smm 282231200Smmvoid 283231200Smmhook_check(void) 284231200Smm{ 285231200Smm struct hookproc *hp, *hp2; 286231200Smm time_t now; 287231200Smm 288231200Smm PJDLOG_ASSERT(hooks_initialized); 289231200Smm 290231200Smm pjdlog_debug(2, "Checking hooks."); 291231200Smm 292231200Smm /* 293231200Smm * Report about processes that are running for a long time. 294231200Smm */ 295231200Smm now = time(NULL); 296231200Smm mtx_lock(&hookprocs_lock); 297231200Smm TAILQ_FOREACH_SAFE(hp, &hookprocs, hp_next, hp2) { 298231200Smm PJDLOG_ASSERT(hp->hp_magic == HOOKPROC_MAGIC_ONLIST); 299231200Smm PJDLOG_ASSERT(hp->hp_pid > 0); 300231200Smm 301231200Smm /* 302231200Smm * If process doesn't exists we somehow missed it. 303231200Smm * Not much can be done expect for logging this situation. 304231200Smm */ 305231200Smm if (kill(hp->hp_pid, 0) == -1 && errno == ESRCH) { 306231200Smm pjdlog_warning("Hook disappeared (pid=%u, cmd=[%s]).", 307231200Smm hp->hp_pid, hp->hp_comm); 308231200Smm hook_remove(hp); 309299529Smm hook_free(hp); 310299529Smm continue; 311299529Smm } 312299529Smm 313299529Smm /* 314231200Smm * Skip proccesses younger than 1 minute. 315231200Smm */ 316299529Smm if (now - hp->hp_lastreport < REPORT_INTERVAL) 317299529Smm continue; 318231200Smm 319231200Smm /* 320231200Smm * Hook is running for too long, report it. 321231200Smm */ 322231200Smm pjdlog_warning("Hook is running for %ju seconds (pid=%u, cmd=[%s]).", 323231200Smm (uintmax_t)(now - hp->hp_birthtime), hp->hp_pid, 324231200Smm hp->hp_comm); 325231200Smm hp->hp_lastreport = now; 326248616Smm } 327248616Smm mtx_unlock(&hookprocs_lock); 328231200Smm} 329231200Smm 330231200Smmvoid 331231200Smmhook_exec(const char *path, ...) 332232153Smm{ 333231200Smm va_list ap; 334231200Smm 335231200Smm va_start(ap, path); 336231200Smm hook_execv(path, ap); 337231200Smm va_end(ap); 338231200Smm} 339231200Smm 340231200Smmvoid 341231200Smmhook_execv(const char *path, va_list ap) 342231200Smm{ 343231200Smm struct hookproc *hp; 344231200Smm char *args[64]; 345231200Smm unsigned int ii; 346231200Smm sigset_t mask; 347231200Smm pid_t pid; 348231200Smm 349231200Smm PJDLOG_ASSERT(hooks_initialized); 350231200Smm 351231200Smm if (path == NULL || path[0] == '\0') 352231200Smm return; 353231200Smm 354231200Smm memset(args, 0, sizeof(args)); 355248616Smm args[0] = basename(path); 356231200Smm for (ii = 1; ii < sizeof(args) / sizeof(args[0]); ii++) { 357231200Smm args[ii] = va_arg(ap, char *); 358231200Smm if (args[ii] == NULL) 359231200Smm break; 360231200Smm } 361231200Smm PJDLOG_ASSERT(ii < sizeof(args) / sizeof(args[0])); 362231200Smm 363231200Smm hp = hook_alloc(path, args); 364231200Smm if (hp == NULL) 365231200Smm return; 366231200Smm 367231200Smm pjdlog_debug(1, "Executing hook: %s", hp->hp_comm); 368231200Smm 369231200Smm pid = fork(); 370231200Smm switch (pid) { 371231200Smm case -1: /* Error. */ 372231200Smm pjdlog_errno(LOG_ERR, "Unable to fork to execute %s", path); 373231200Smm hook_free(hp); 374231200Smm return; 375231200Smm case 0: /* Child. */ 376231200Smm descriptors(); 377231200Smm PJDLOG_VERIFY(sigemptyset(&mask) == 0); 378231200Smm PJDLOG_VERIFY(sigprocmask(SIG_SETMASK, &mask, NULL) == 0); 379231200Smm /* 380231200Smm * Dummy handler set for SIGCHLD in the parent will be restored 381231200Smm * to SIG_IGN on execv(3) below, so there is no need to do 382231200Smm * anything with it. 383231200Smm */ 384231200Smm execv(path, args); 385231200Smm pjdlog_errno(LOG_ERR, "Unable to execute %s", path); 386231200Smm exit(EX_SOFTWARE); 387231200Smm default: /* Parent. */ 388231200Smm hook_add(hp, pid); 389231200Smm break; 390231200Smm } 391231200Smm} 392231200Smm