1316958Sdchagin/*-
259243Sobrien * Copyright (c) 2010 The FreeBSD Foundation
359243Sobrien * Copyright (c) 2010 Pawel Jakub Dawidek <pjd@FreeBSD.org>
459243Sobrien * All rights reserved.
559243Sobrien *
659243Sobrien * This software was developed by Pawel Jakub Dawidek under sponsorship from
759243Sobrien * the FreeBSD Foundation.
859243Sobrien *
959243Sobrien * Redistribution and use in source and binary forms, with or without
1059243Sobrien * modification, are permitted provided that the following conditions
1159243Sobrien * are met:
1259243Sobrien * 1. Redistributions of source code must retain the above copyright
1359243Sobrien *    notice, this list of conditions and the following disclaimer.
1459243Sobrien * 2. Redistributions in binary form must reproduce the above copyright
1559243Sobrien *    notice, this list of conditions and the following disclaimer in the
1659243Sobrien *    documentation and/or other materials provided with the distribution.
17100616Smp *
1859243Sobrien * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
1959243Sobrien * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2059243Sobrien * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2159243Sobrien * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
2259243Sobrien * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2359243Sobrien * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2459243Sobrien * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2559243Sobrien * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2659243Sobrien * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2759243Sobrien * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2859243Sobrien * SUCH DAMAGE.
2959243Sobrien */
3059243Sobrien
3159243Sobrien#include <sys/cdefs.h>
3259243Sobrien__FBSDID("$FreeBSD$");
3359243Sobrien
3459243Sobrien#include <sys/types.h>
35316958Sdchagin#include <sys/sysctl.h>
3659243Sobrien#include <sys/wait.h>
3759243Sobrien
3859243Sobrien#include <errno.h>
3969408Sache#include <fcntl.h>
4059243Sobrien#include <libgen.h>
4169408Sache#include <paths.h>
4259243Sobrien#include <signal.h>
4359243Sobrien#include <stdbool.h>
4459243Sobrien#include <stdint.h>
4559243Sobrien#include <stdio.h>
4659243Sobrien#include <stdlib.h>
4759243Sobrien#include <string.h>
4859243Sobrien#include <syslog.h>
4959243Sobrien#include <unistd.h>
5059243Sobrien
5159243Sobrien#include <pjdlog.h>
5259243Sobrien
5359243Sobrien#include "hooks.h"
5459243Sobrien#include "subr.h"
5559243Sobrien#include "synch.h"
5659243Sobrien
5759243Sobrien/* Report processes that are running for too long not often than this value. */
5859243Sobrien#define	REPORT_INTERVAL	60
5959243Sobrien
6059243Sobrien/* Are we initialized? */
6159243Sobrienstatic bool hooks_initialized = false;
6259243Sobrien
6359243Sobrien/*
6459243Sobrien * Keep all processes we forked on a global queue, so we can report nicely
6559243Sobrien * when they finish or report that they are running for a long time.
6659243Sobrien */
6759243Sobrien#define	HOOKPROC_MAGIC_ALLOCATED	0x80090ca
6859243Sobrien#define	HOOKPROC_MAGIC_ONLIST		0x80090c0
6959243Sobrienstruct hookproc {
7059243Sobrien	/* Magic. */
7159243Sobrien	int	hp_magic;
7259243Sobrien	/* PID of a forked child. */
7359243Sobrien	pid_t	hp_pid;
7459243Sobrien	/* When process were forked? */
7559243Sobrien	time_t	hp_birthtime;
7659243Sobrien	/* When we logged previous reported? */
7759243Sobrien	time_t	hp_lastreport;
7859243Sobrien	/* Path to executable and all the arguments we passed. */
7959243Sobrien	char	hp_comm[PATH_MAX];
80231990Smp	TAILQ_ENTRY(hookproc) hp_next;
8159243Sobrien};
8259243Sobrienstatic TAILQ_HEAD(, hookproc) hookprocs;
8359243Sobrienstatic pthread_mutex_t hookprocs_lock;
8459243Sobrien
8559243Sobrienstatic void hook_remove(struct hookproc *hp);
8659243Sobrienstatic void hook_free(struct hookproc *hp);
8759243Sobrien
8859243Sobrienstatic void
8959243Sobriendescriptors(void)
9059243Sobrien{
9159243Sobrien	int fd;
9259243Sobrien
9359243Sobrien	/*
9459243Sobrien	 * Close all (or almost all) descriptors.
9559243Sobrien	 */
96145479Smp	if (pjdlog_mode_get() == PJDLOG_MODE_STD) {
9759243Sobrien		closefrom(MAX(MAX(STDIN_FILENO, STDOUT_FILENO),
98145479Smp		    STDERR_FILENO) + 1);
9959243Sobrien		return;
10059243Sobrien	}
10159243Sobrien
10259243Sobrien	closefrom(0);
10359243Sobrien
10459243Sobrien	/*
10559243Sobrien	 * Redirect stdin, stdout and stderr to /dev/null.
10659243Sobrien	 */
10759243Sobrien	fd = open(_PATH_DEVNULL, O_RDONLY);
10859243Sobrien	if (fd == -1) {
10959243Sobrien		pjdlog_errno(LOG_WARNING, "Unable to open %s for reading",
11059243Sobrien		    _PATH_DEVNULL);
11159243Sobrien	} else if (fd != STDIN_FILENO) {
11259243Sobrien		if (dup2(fd, STDIN_FILENO) == -1) {
11359243Sobrien			pjdlog_errno(LOG_WARNING,
11459243Sobrien			    "Unable to duplicate descriptor for stdin");
11559243Sobrien		}
11659243Sobrien		close(fd);
11759243Sobrien	}
11859243Sobrien	fd = open(_PATH_DEVNULL, O_WRONLY);
11959243Sobrien	if (fd == -1) {
12059243Sobrien		pjdlog_errno(LOG_WARNING, "Unable to open %s for writing",
12159243Sobrien		    _PATH_DEVNULL);
12259243Sobrien	} else {
12359243Sobrien		if (fd != STDOUT_FILENO && dup2(fd, STDOUT_FILENO) == -1) {
12459243Sobrien			pjdlog_errno(LOG_WARNING,
12559243Sobrien			    "Unable to duplicate descriptor for stdout");
12659243Sobrien		}
12759243Sobrien		if (fd != STDERR_FILENO && dup2(fd, STDERR_FILENO) == -1) {
12859243Sobrien			pjdlog_errno(LOG_WARNING,
12959243Sobrien			    "Unable to duplicate descriptor for stderr");
13059243Sobrien		}
13159243Sobrien		if (fd != STDOUT_FILENO && fd != STDERR_FILENO)
13259243Sobrien			close(fd);
13359243Sobrien	}
13459243Sobrien}
13559243Sobrien
13659243Sobrienvoid
13759243Sobrienhook_init(void)
13859243Sobrien{
13959243Sobrien
14059243Sobrien	PJDLOG_ASSERT(!hooks_initialized);
14159243Sobrien
14259243Sobrien	mtx_init(&hookprocs_lock);
14359243Sobrien	TAILQ_INIT(&hookprocs);
144231990Smp	hooks_initialized = true;
145167465Smp}
146167465Smp
147167465Smpvoid
14859243Sobrienhook_fini(void)
14959243Sobrien{
150167465Smp	struct hookproc *hp;
15159243Sobrien
152231990Smp	PJDLOG_ASSERT(hooks_initialized);
153100616Smp
154231990Smp	mtx_lock(&hookprocs_lock);
155100616Smp	while ((hp = TAILQ_FIRST(&hookprocs)) != NULL) {
15659243Sobrien		PJDLOG_ASSERT(hp->hp_magic == HOOKPROC_MAGIC_ONLIST);
15759243Sobrien		PJDLOG_ASSERT(hp->hp_pid > 0);
15859243Sobrien
15959243Sobrien		hook_remove(hp);
16059243Sobrien		hook_free(hp);
16159243Sobrien	}
16259243Sobrien	mtx_unlock(&hookprocs_lock);
16359243Sobrien
16459243Sobrien	mtx_destroy(&hookprocs_lock);
165100616Smp	TAILQ_INIT(&hookprocs);
166100616Smp	hooks_initialized = false;
167167465Smp}
16859243Sobrien
169167465Smpstatic struct hookproc *
17059243Sobrienhook_alloc(const char *path, char **args)
17159243Sobrien{
17259243Sobrien	struct hookproc *hp;
17359243Sobrien	unsigned int ii;
17459243Sobrien
17559243Sobrien	hp = malloc(sizeof(*hp));
17659243Sobrien	if (hp == NULL) {
177167465Smp		pjdlog_error("Unable to allocate %zu bytes of memory for a hook.",
17859243Sobrien		    sizeof(*hp));
17959243Sobrien		return (NULL);
18059243Sobrien	}
18159243Sobrien
18259243Sobrien	hp->hp_pid = 0;
18359243Sobrien	hp->hp_birthtime = hp->hp_lastreport = time(NULL);
18459243Sobrien	(void)strlcpy(hp->hp_comm, path, sizeof(hp->hp_comm));
18559243Sobrien	/* We start at 2nd argument as we don't want to have exec name twice. */
18659243Sobrien	for (ii = 1; args[ii] != NULL; ii++) {
18759243Sobrien		(void)snprlcat(hp->hp_comm, sizeof(hp->hp_comm), " %s",
188167465Smp		    args[ii]);
18959243Sobrien	}
19059243Sobrien	if (strlen(hp->hp_comm) >= sizeof(hp->hp_comm) - 1) {
19159243Sobrien		pjdlog_error("Exec path too long, correct configuration file.");
19259243Sobrien		free(hp);
19359243Sobrien		return (NULL);
19459243Sobrien	}
19559243Sobrien	hp->hp_magic = HOOKPROC_MAGIC_ALLOCATED;
19659243Sobrien	return (hp);
197100616Smp}
198167465Smp
19959243Sobrienstatic void
200167465Smphook_add(struct hookproc *hp, pid_t pid)
20159243Sobrien{
20259243Sobrien
20359243Sobrien	PJDLOG_ASSERT(hp->hp_magic == HOOKPROC_MAGIC_ALLOCATED);
20459243Sobrien	PJDLOG_ASSERT(hp->hp_pid == 0);
20559243Sobrien
20659243Sobrien	hp->hp_pid = pid;
20759243Sobrien	mtx_lock(&hookprocs_lock);
20859243Sobrien	hp->hp_magic = HOOKPROC_MAGIC_ONLIST;
20959243Sobrien	TAILQ_INSERT_TAIL(&hookprocs, hp, hp_next);
210167465Smp	mtx_unlock(&hookprocs_lock);
211167465Smp}
21259243Sobrien
213167465Smpstatic void
214167465Smphook_remove(struct hookproc *hp)
21559243Sobrien{
21659243Sobrien
21759243Sobrien	PJDLOG_ASSERT(hp->hp_magic == HOOKPROC_MAGIC_ONLIST);
21859243Sobrien	PJDLOG_ASSERT(hp->hp_pid > 0);
21959243Sobrien	PJDLOG_ASSERT(mtx_owned(&hookprocs_lock));
22059243Sobrien
22159243Sobrien	TAILQ_REMOVE(&hookprocs, hp, hp_next);
22259243Sobrien	hp->hp_magic = HOOKPROC_MAGIC_ALLOCATED;
22359243Sobrien}
22459243Sobrien
22559243Sobrienstatic void
22659243Sobrienhook_free(struct hookproc *hp)
22759243Sobrien{
22859243Sobrien
22959243Sobrien	PJDLOG_ASSERT(hp->hp_magic == HOOKPROC_MAGIC_ALLOCATED);
23059243Sobrien	PJDLOG_ASSERT(hp->hp_pid > 0);
23159243Sobrien
23259243Sobrien	hp->hp_magic = 0;
23359243Sobrien	free(hp);
23459243Sobrien}
23559243Sobrien
23659243Sobrienstatic struct hookproc *
23759243Sobrienhook_find(pid_t pid)
23859243Sobrien{
23959243Sobrien	struct hookproc *hp;
24059243Sobrien
241167465Smp	PJDLOG_ASSERT(mtx_owned(&hookprocs_lock));
242167465Smp
243167465Smp	TAILQ_FOREACH(hp, &hookprocs, hp_next) {
244167465Smp		PJDLOG_ASSERT(hp->hp_magic == HOOKPROC_MAGIC_ONLIST);
245167465Smp		PJDLOG_ASSERT(hp->hp_pid > 0);
246167465Smp
247167465Smp		if (hp->hp_pid == pid)
248167465Smp			break;
249167465Smp	}
25059243Sobrien
25159243Sobrien	return (hp);
25259243Sobrien}
25359243Sobrien
25459243Sobrienvoid
255100616Smphook_check_one(pid_t pid, int status)
256231990Smp{
25759243Sobrien	struct hookproc *hp;
258231990Smp
25959243Sobrien	mtx_lock(&hookprocs_lock);
260167465Smp	hp = hook_find(pid);
261167465Smp	if (hp == NULL) {
262167465Smp		mtx_unlock(&hookprocs_lock);
26359243Sobrien		pjdlog_debug(1, "Unknown process pid=%u", pid);
26459243Sobrien		return;
26559243Sobrien	}
26659243Sobrien	hook_remove(hp);
267231990Smp	mtx_unlock(&hookprocs_lock);
268231990Smp	if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
269231990Smp		pjdlog_debug(1, "Hook exited gracefully (pid=%u, cmd=[%s]).",
27059243Sobrien		    pid, hp->hp_comm);
27159243Sobrien	} else if (WIFSIGNALED(status)) {
27259243Sobrien		pjdlog_error("Hook was killed (pid=%u, signal=%d, cmd=[%s]).",
27359243Sobrien		    pid, WTERMSIG(status), hp->hp_comm);
27459243Sobrien	} else {
27559243Sobrien		pjdlog_error("Hook exited ungracefully (pid=%u, exitcode=%d, cmd=[%s]).",
27659243Sobrien		    pid, WIFEXITED(status) ? WEXITSTATUS(status) : -1,
27759243Sobrien		    hp->hp_comm);
27859243Sobrien	}
27959243Sobrien	hook_free(hp);
28059243Sobrien}
28159243Sobrien
28259243Sobrienvoid
28359243Sobrienhook_check(void)
28459243Sobrien{
28559243Sobrien	struct hookproc *hp, *hp2;
28659243Sobrien	time_t now;
28759243Sobrien
28859243Sobrien	PJDLOG_ASSERT(hooks_initialized);
28959243Sobrien
29059243Sobrien	pjdlog_debug(2, "Checking hooks.");
29159243Sobrien
29259243Sobrien	/*
29359243Sobrien	 * Report about processes that are running for a long time.
29459243Sobrien	 */
295167465Smp	now = time(NULL);
296167465Smp	mtx_lock(&hookprocs_lock);
297167465Smp	TAILQ_FOREACH_SAFE(hp, &hookprocs, hp_next, hp2) {
29859243Sobrien		PJDLOG_ASSERT(hp->hp_magic == HOOKPROC_MAGIC_ONLIST);
29959243Sobrien		PJDLOG_ASSERT(hp->hp_pid > 0);
30059243Sobrien
30159243Sobrien		/*
302167465Smp		 * If process doesn't exists we somehow missed it.
303167465Smp		 * Not much can be done expect for logging this situation.
304167465Smp		 */
30559243Sobrien		if (kill(hp->hp_pid, 0) == -1 && errno == ESRCH) {
306167465Smp			pjdlog_warning("Hook disappeared (pid=%u, cmd=[%s]).",
30759243Sobrien			    hp->hp_pid, hp->hp_comm);
30859243Sobrien			hook_remove(hp);
30959243Sobrien			hook_free(hp);
31059243Sobrien			continue;
31159243Sobrien		}
31259243Sobrien
31359243Sobrien		/*
31459243Sobrien		 * Skip proccesses younger than 1 minute.
31559243Sobrien		 */
31659243Sobrien		if (now - hp->hp_lastreport < REPORT_INTERVAL)
31759243Sobrien			continue;
318167465Smp
319231990Smp		/*
320231990Smp		 * Hook is running for too long, report it.
321231990Smp		 */
322231990Smp		pjdlog_warning("Hook is running for %ju seconds (pid=%u, cmd=[%s]).",
323231990Smp		    (uintmax_t)(now - hp->hp_birthtime), hp->hp_pid,
324167465Smp		    hp->hp_comm);
325167465Smp		hp->hp_lastreport = now;
326167465Smp	}
32759243Sobrien	mtx_unlock(&hookprocs_lock);
328167465Smp}
32959243Sobrien
33059243Sobrienvoid
33159243Sobrienhook_exec(const char *path, ...)
33259243Sobrien{
33359243Sobrien	va_list ap;
334167465Smp
33559243Sobrien	va_start(ap, path);
33659243Sobrien	hook_execv(path, ap);
33759243Sobrien	va_end(ap);
33859243Sobrien}
33959243Sobrien
34059243Sobrienvoid
34159243Sobrienhook_execv(const char *path, va_list ap)
342167465Smp{
34359243Sobrien	struct hookproc *hp;
34459243Sobrien	char *args[64];
34559243Sobrien	unsigned int ii;
34659243Sobrien	sigset_t mask;
34759243Sobrien	pid_t pid;
34859243Sobrien
34959243Sobrien	PJDLOG_ASSERT(hooks_initialized);
35059243Sobrien
35159243Sobrien	if (path == NULL || path[0] == '\0')
35259243Sobrien		return;
35359243Sobrien
35459243Sobrien	memset(args, 0, sizeof(args));
35559243Sobrien	args[0] = basename(path);
35659243Sobrien	for (ii = 1; ii < sizeof(args) / sizeof(args[0]); ii++) {
35759243Sobrien		args[ii] = va_arg(ap, char *);
358167465Smp		if (args[ii] == NULL)
35959243Sobrien			break;
360145479Smp	}
361145479Smp	PJDLOG_ASSERT(ii < sizeof(args) / sizeof(args[0]));
362145479Smp
36359243Sobrien	hp = hook_alloc(path, args);
36459243Sobrien	if (hp == NULL)
36559243Sobrien		return;
36659243Sobrien
36759243Sobrien	pjdlog_debug(1, "Executing hook: %s", hp->hp_comm);
36859243Sobrien
36959243Sobrien	pid = fork();
37059243Sobrien	switch (pid) {
37159243Sobrien	case -1:	/* Error. */
37259243Sobrien		pjdlog_errno(LOG_ERR, "Unable to fork to execute %s", path);
37359243Sobrien		hook_free(hp);
37459243Sobrien		return;
37559243Sobrien	case 0:		/* Child. */
37659243Sobrien		descriptors();
37759243Sobrien		PJDLOG_VERIFY(sigemptyset(&mask) == 0);
37859243Sobrien		PJDLOG_VERIFY(sigprocmask(SIG_SETMASK, &mask, NULL) == 0);
37959243Sobrien		/*
38059243Sobrien		 * Dummy handler set for SIGCHLD in the parent will be restored
38159243Sobrien		 * to SIG_IGN on execv(3) below, so there is no need to do
38259243Sobrien		 * anything with it.
38359243Sobrien		 */
38459243Sobrien		execv(path, args);
38559243Sobrien		pjdlog_errno(LOG_ERR, "Unable to execute %s", path);
38659243Sobrien		exit(EX_SOFTWARE);
38759243Sobrien	default:	/* Parent. */
38859243Sobrien		hook_add(hp, pid);
38959243Sobrien		break;
39059243Sobrien	}
39159243Sobrien}
39259243Sobrien