1 /*- 2 * Copyright (c) 2010 The FreeBSD Foundation 3 * Copyright (c) 2010 Pawel Jakub Dawidek <pjd@FreeBSD.org> 4 * All rights reserved. 5 * 6 * This software was developed by Pawel Jakub Dawidek under sponsorship from 7 * the FreeBSD Foundation. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND 19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE 22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28 * SUCH DAMAGE. 29 */ 30 31 #include <sys/cdefs.h> 32 __FBSDID("$FreeBSD$"); 33 34 #include <sys/types.h> 35 #include <sys/sysctl.h> 36 #include <sys/wait.h> 37 38 #include <assert.h> 39 #include <errno.h> 40 #include <fcntl.h> 41 #include <libgen.h> 42 #include <paths.h> 43 #include <signal.h> 44 #include <stdbool.h> 45 #include <stdint.h> 46 #include <stdio.h> 47 #include <stdlib.h> 48 #include <string.h> 49 #include <syslog.h> 50 #include <unistd.h> 51 52 #include <pjdlog.h> 53 54 #include "hooks.h" 55 #include "synch.h" 56 57 /* Report processes that are running for too long not often than this value. */ 58 #define REPORT_INTERVAL 60 59 60 /* Are we initialized? */ 61 static bool hooks_initialized = false; 62 63 /* 64 * Keep all processes we forked on a global queue, so we can report nicely 65 * when they finish or report that they are running for a long time. 66 */ 67 #define HOOKPROC_MAGIC_ALLOCATED 0x80090ca 68 #define HOOKPROC_MAGIC_ONLIST 0x80090c0 69 struct hookproc { 70 /* Magic. */ 71 int hp_magic; 72 /* PID of a forked child. */ 73 pid_t hp_pid; 74 /* When process were forked? */ 75 time_t hp_birthtime; 76 /* When we logged previous reported? */ 77 time_t hp_lastreport; 78 /* Path to executable and all the arguments we passed. */ 79 char hp_comm[PATH_MAX]; 80 TAILQ_ENTRY(hookproc) hp_next; 81 }; 82 static TAILQ_HEAD(, hookproc) hookprocs; 83 static pthread_mutex_t hookprocs_lock; 84 85 static void hook_remove(struct hookproc *hp); 86 static void hook_free(struct hookproc *hp); 87 88 static void 89 descriptors(void) 90 { 91 long maxfd; 92 int fd; 93 94 /* 95 * Close all descriptors. 96 */ 97 maxfd = sysconf(_SC_OPEN_MAX); 98 if (maxfd < 0) { 99 pjdlog_errno(LOG_WARNING, "sysconf(_SC_OPEN_MAX) failed"); 100 maxfd = 1024; 101 } 102 for (fd = 0; fd <= maxfd; fd++) { 103 switch (fd) { 104 case STDIN_FILENO: 105 case STDOUT_FILENO: 106 case STDERR_FILENO: 107 if (pjdlog_mode_get() == PJDLOG_MODE_STD) 108 break; 109 /* FALLTHROUGH */ 110 default: 111 close(fd); 112 break; 113 } 114 } 115 if (pjdlog_mode_get() == PJDLOG_MODE_STD) 116 return; 117 /* 118 * Redirect stdin, stdout and stderr to /dev/null. 119 */ 120 fd = open(_PATH_DEVNULL, O_RDONLY); 121 if (fd < 0) { 122 pjdlog_errno(LOG_WARNING, "Unable to open %s for reading", 123 _PATH_DEVNULL); 124 } else if (fd != STDIN_FILENO) { 125 if (dup2(fd, STDIN_FILENO) < 0) { 126 pjdlog_errno(LOG_WARNING, 127 "Unable to duplicate descriptor for stdin"); 128 } 129 close(fd); 130 } 131 fd = open(_PATH_DEVNULL, O_WRONLY); 132 if (fd < 0) { 133 pjdlog_errno(LOG_WARNING, "Unable to open %s for writing", 134 _PATH_DEVNULL); 135 } else { 136 if (fd != STDOUT_FILENO && dup2(fd, STDOUT_FILENO) < 0) { 137 pjdlog_errno(LOG_WARNING, 138 "Unable to duplicate descriptor for stdout"); 139 } 140 if (fd != STDERR_FILENO && dup2(fd, STDERR_FILENO) < 0) { 141 pjdlog_errno(LOG_WARNING, 142 "Unable to duplicate descriptor for stderr"); 143 } 144 if (fd != STDOUT_FILENO && fd != STDERR_FILENO) 145 close(fd); 146 } 147 } 148 149 void 150 hook_init(void) 151 { 152 153 assert(!hooks_initialized); 154 155 mtx_init(&hookprocs_lock); 156 TAILQ_INIT(&hookprocs); 157 hooks_initialized = true; 158 } 159 160 void 161 hook_fini(void) 162 { 163 struct hookproc *hp; 164 165 assert(hooks_initialized); 166 167 mtx_lock(&hookprocs_lock); 168 while ((hp = TAILQ_FIRST(&hookprocs)) != NULL) { 169 assert(hp->hp_magic == HOOKPROC_MAGIC_ONLIST); 170 assert(hp->hp_pid > 0); 171 172 hook_remove(hp); 173 hook_free(hp); 174 } 175 mtx_unlock(&hookprocs_lock); 176 177 mtx_destroy(&hookprocs_lock); 178 TAILQ_INIT(&hookprocs); 179 hooks_initialized = false; 180 } 181 182 static struct hookproc * 183 hook_alloc(const char *path, char **args) 184 { 185 struct hookproc *hp; 186 unsigned int ii; 187 188 hp = malloc(sizeof(*hp)); 189 if (hp == NULL) { 190 pjdlog_error("Unable to allocate %zu bytes of memory for a hook.", 191 sizeof(*hp)); 192 return (NULL); 193 } 194 195 hp->hp_pid = 0; 196 hp->hp_birthtime = hp->hp_lastreport = time(NULL); 197 (void)strlcpy(hp->hp_comm, path, sizeof(hp->hp_comm)); 198 /* We start at 2nd argument as we don't want to have exec name twice. */ 199 for (ii = 1; args[ii] != NULL; ii++) { 200 (void)strlcat(hp->hp_comm, " ", sizeof(hp->hp_comm)); 201 (void)strlcat(hp->hp_comm, args[ii], sizeof(hp->hp_comm)); 202 } 203 if (strlen(hp->hp_comm) >= sizeof(hp->hp_comm) - 1) { 204 pjdlog_error("Exec path too long, correct configuration file."); 205 free(hp); 206 return (NULL); 207 } 208 hp->hp_magic = HOOKPROC_MAGIC_ALLOCATED; 209 return (hp); 210 } 211 212 static void 213 hook_add(struct hookproc *hp, pid_t pid) 214 { 215 216 assert(hp->hp_magic == HOOKPROC_MAGIC_ALLOCATED); 217 assert(hp->hp_pid == 0); 218 219 hp->hp_pid = pid; 220 mtx_lock(&hookprocs_lock); 221 hp->hp_magic = HOOKPROC_MAGIC_ONLIST; 222 TAILQ_INSERT_TAIL(&hookprocs, hp, hp_next); 223 mtx_unlock(&hookprocs_lock); 224 } 225 226 static void 227 hook_remove(struct hookproc *hp) 228 { 229 230 assert(hp->hp_magic == HOOKPROC_MAGIC_ONLIST); 231 assert(hp->hp_pid > 0); 232 assert(mtx_owned(&hookprocs_lock)); 233 234 TAILQ_REMOVE(&hookprocs, hp, hp_next); 235 hp->hp_magic = HOOKPROC_MAGIC_ALLOCATED; 236 } 237 238 static void 239 hook_free(struct hookproc *hp) 240 { 241 242 assert(hp->hp_magic == HOOKPROC_MAGIC_ALLOCATED); 243 assert(hp->hp_pid > 0); 244 245 hp->hp_magic = 0; 246 free(hp); 247 } 248 249 static struct hookproc * 250 hook_find(pid_t pid) 251 { 252 struct hookproc *hp; 253 254 assert(mtx_owned(&hookprocs_lock)); 255 256 TAILQ_FOREACH(hp, &hookprocs, hp_next) { 257 assert(hp->hp_magic == HOOKPROC_MAGIC_ONLIST); 258 assert(hp->hp_pid > 0); 259 260 if (hp->hp_pid == pid) 261 break; 262 } 263 264 return (hp); 265 } 266 267 void 268 hook_check_one(pid_t pid, int status) 269 { 270 struct hookproc *hp; 271 272 mtx_lock(&hookprocs_lock); 273 hp = hook_find(pid); 274 if (hp == NULL) { 275 mtx_unlock(&hookprocs_lock); 276 pjdlog_debug(1, "Unknown process pid=%u", pid); 277 return; 278 } 279 hook_remove(hp); 280 mtx_unlock(&hookprocs_lock); 281 if (WIFEXITED(status) && WEXITSTATUS(status) == 0) { 282 pjdlog_debug(1, "Hook exited gracefully (pid=%u, cmd=[%s]).", 283 pid, hp->hp_comm); 284 } else if (WIFSIGNALED(status)) { 285 pjdlog_error("Hook was killed (pid=%u, signal=%d, cmd=[%s]).", 286 pid, WTERMSIG(status), hp->hp_comm); 287 } else { 288 pjdlog_error("Hook exited ungracefully (pid=%u, exitcode=%d, cmd=[%s]).", 289 pid, WIFEXITED(status) ? WEXITSTATUS(status) : -1, 290 hp->hp_comm); 291 } 292 hook_free(hp); 293 } 294 295 void 296 hook_check(bool sigchld) 297 { 298 struct hookproc *hp, *hp2; 299 int status; 300 time_t now; 301 pid_t pid; 302 303 assert(hooks_initialized); 304 305 /* 306 * If SIGCHLD was received, garbage collect finished processes. 307 */ 308 if (sigchld) { 309 while ((pid = wait3(&status, WNOHANG, NULL)) > 0) 310 hook_check_one(pid, status); 311 } 312 313 /* 314 * Report about processes that are running for a long time. 315 */ 316 now = time(NULL); 317 mtx_lock(&hookprocs_lock); 318 TAILQ_FOREACH_SAFE(hp, &hookprocs, hp_next, hp2) { 319 assert(hp->hp_magic == HOOKPROC_MAGIC_ONLIST); 320 assert(hp->hp_pid > 0); 321 322 /* 323 * If process doesn't exists we somehow missed it. 324 * Not much can be done expect for logging this situation. 325 */ 326 if (kill(hp->hp_pid, 0) == -1 && errno == ESRCH) { 327 pjdlog_warning("Hook disappeared (pid=%u, cmd=[%s]).", 328 hp->hp_pid, hp->hp_comm); 329 hook_remove(hp); 330 hook_free(hp); 331 continue; 332 } 333 334 /* 335 * Skip proccesses younger than 1 minute. 336 */ 337 if (now - hp->hp_lastreport < REPORT_INTERVAL) 338 continue; 339 340 /* 341 * Hook is running for too long, report it. 342 */ 343 pjdlog_warning("Hook is running for %ju seconds (pid=%u, cmd=[%s]).", 344 (uintmax_t)(now - hp->hp_birthtime), hp->hp_pid, 345 hp->hp_comm); 346 hp->hp_lastreport = now; 347 } 348 mtx_unlock(&hookprocs_lock); 349 } 350 351 void 352 hook_exec(const char *path, ...) 353 { 354 va_list ap; 355 356 va_start(ap, path); 357 hook_execv(path, ap); 358 va_end(ap); 359 } 360 361 void 362 hook_execv(const char *path, va_list ap) 363 { 364 struct hookproc *hp; 365 char *args[64]; 366 unsigned int ii; 367 pid_t pid; 368 369 assert(hooks_initialized); 370 371 if (path == NULL || path[0] == '\0') 372 return; 373 374 memset(args, 0, sizeof(args)); 375 args[0] = basename(path); 376 for (ii = 1; ii < sizeof(args) / sizeof(args[0]); ii++) { 377 args[ii] = va_arg(ap, char *); 378 if (args[ii] == NULL) 379 break; 380 } 381 assert(ii < sizeof(args) / sizeof(args[0])); 382 383 hp = hook_alloc(path, args); 384 if (hp == NULL) 385 return; 386 387 pid = fork(); 388 switch (pid) { 389 case -1: /* Error. */ 390 pjdlog_errno(LOG_ERR, "Unable to fork to execute %s", path); 391 return; 392 case 0: /* Child. */ 393 descriptors(); 394 execv(path, args); 395 pjdlog_errno(LOG_ERR, "Unable to execute %s", path); 396 exit(EX_SOFTWARE); 397 default: /* Parent. */ 398 hook_add(hp, pid); 399 break; 400 } 401 } 402