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$");
33204076Spjd
34204076Spjd#include <sys/types.h>
35211885Spjd#include <sys/sysctl.h>
36204076Spjd#include <sys/wait.h>
37204076Spjd
38211885Spjd#include <errno.h>
39204076Spjd#include <fcntl.h>
40211885Spjd#include <libgen.h>
41211885Spjd#include <paths.h>
42211885Spjd#include <signal.h>
43211885Spjd#include <stdbool.h>
44211885Spjd#include <stdint.h>
45204076Spjd#include <stdio.h>
46204076Spjd#include <stdlib.h>
47204076Spjd#include <string.h>
48204076Spjd#include <syslog.h>
49211885Spjd#include <unistd.h>
50204076Spjd
51204076Spjd#include <pjdlog.h>
52204076Spjd
53204076Spjd#include "hooks.h"
54219816Spjd#include "subr.h"
55211885Spjd#include "synch.h"
56204076Spjd
57211885Spjd/* Report processes that are running for too long not often than this value. */
58211885Spjd#define	REPORT_INTERVAL	60
59211885Spjd
60211885Spjd/* Are we initialized? */
61211885Spjdstatic bool hooks_initialized = false;
62211885Spjd
63211885Spjd/*
64211885Spjd * Keep all processes we forked on a global queue, so we can report nicely
65211885Spjd * when they finish or report that they are running for a long time.
66211885Spjd */
67211885Spjd#define	HOOKPROC_MAGIC_ALLOCATED	0x80090ca
68211885Spjd#define	HOOKPROC_MAGIC_ONLIST		0x80090c0
69211885Spjdstruct hookproc {
70211885Spjd	/* Magic. */
71211885Spjd	int	hp_magic;
72211885Spjd	/* PID of a forked child. */
73211885Spjd	pid_t	hp_pid;
74211885Spjd	/* When process were forked? */
75211885Spjd	time_t	hp_birthtime;
76211885Spjd	/* When we logged previous reported? */
77211885Spjd	time_t	hp_lastreport;
78211885Spjd	/* Path to executable and all the arguments we passed. */
79211885Spjd	char	hp_comm[PATH_MAX];
80211885Spjd	TAILQ_ENTRY(hookproc) hp_next;
81211885Spjd};
82211885Spjdstatic TAILQ_HEAD(, hookproc) hookprocs;
83211885Spjdstatic pthread_mutex_t hookprocs_lock;
84211885Spjd
85211976Spjdstatic void hook_remove(struct hookproc *hp);
86211976Spjdstatic void hook_free(struct hookproc *hp);
87211976Spjd
88204076Spjdstatic void
89204076Spjddescriptors(void)
90204076Spjd{
91204076Spjd	int fd;
92204076Spjd
93204076Spjd	/*
94214119Spjd	 * Close all (or almost all) descriptors.
95204076Spjd	 */
96214119Spjd	if (pjdlog_mode_get() == PJDLOG_MODE_STD) {
97214119Spjd		closefrom(MAX(MAX(STDIN_FILENO, STDOUT_FILENO),
98214119Spjd		    STDERR_FILENO) + 1);
99214119Spjd		return;
100204076Spjd	}
101214119Spjd
102214119Spjd	closefrom(0);
103214119Spjd
104204076Spjd	/*
105204076Spjd	 * Redirect stdin, stdout and stderr to /dev/null.
106204076Spjd	 */
107204076Spjd	fd = open(_PATH_DEVNULL, O_RDONLY);
108231017Strociny	if (fd == -1) {
109204076Spjd		pjdlog_errno(LOG_WARNING, "Unable to open %s for reading",
110204076Spjd		    _PATH_DEVNULL);
111204076Spjd	} else if (fd != STDIN_FILENO) {
112231017Strociny		if (dup2(fd, STDIN_FILENO) == -1) {
113204076Spjd			pjdlog_errno(LOG_WARNING,
114204076Spjd			    "Unable to duplicate descriptor for stdin");
115204076Spjd		}
116204076Spjd		close(fd);
117204076Spjd	}
118204076Spjd	fd = open(_PATH_DEVNULL, O_WRONLY);
119231017Strociny	if (fd == -1) {
120204076Spjd		pjdlog_errno(LOG_WARNING, "Unable to open %s for writing",
121204076Spjd		    _PATH_DEVNULL);
122204076Spjd	} else {
123231017Strociny		if (fd != STDOUT_FILENO && dup2(fd, STDOUT_FILENO) == -1) {
124204076Spjd			pjdlog_errno(LOG_WARNING,
125204076Spjd			    "Unable to duplicate descriptor for stdout");
126204076Spjd		}
127231017Strociny		if (fd != STDERR_FILENO && dup2(fd, STDERR_FILENO) == -1) {
128204076Spjd			pjdlog_errno(LOG_WARNING,
129204076Spjd			    "Unable to duplicate descriptor for stderr");
130204076Spjd		}
131204076Spjd		if (fd != STDOUT_FILENO && fd != STDERR_FILENO)
132204076Spjd			close(fd);
133204076Spjd	}
134204076Spjd}
135204076Spjd
136211885Spjdvoid
137211885Spjdhook_init(void)
138211885Spjd{
139211885Spjd
140229509Strociny	PJDLOG_ASSERT(!hooks_initialized);
141211976Spjd
142211885Spjd	mtx_init(&hookprocs_lock);
143211885Spjd	TAILQ_INIT(&hookprocs);
144211885Spjd	hooks_initialized = true;
145211885Spjd}
146211885Spjd
147211976Spjdvoid
148211976Spjdhook_fini(void)
149211976Spjd{
150211976Spjd	struct hookproc *hp;
151211976Spjd
152229509Strociny	PJDLOG_ASSERT(hooks_initialized);
153211976Spjd
154211976Spjd	mtx_lock(&hookprocs_lock);
155211976Spjd	while ((hp = TAILQ_FIRST(&hookprocs)) != NULL) {
156229509Strociny		PJDLOG_ASSERT(hp->hp_magic == HOOKPROC_MAGIC_ONLIST);
157229509Strociny		PJDLOG_ASSERT(hp->hp_pid > 0);
158211976Spjd
159211976Spjd		hook_remove(hp);
160211976Spjd		hook_free(hp);
161211976Spjd	}
162211976Spjd	mtx_unlock(&hookprocs_lock);
163211976Spjd
164211976Spjd	mtx_destroy(&hookprocs_lock);
165211976Spjd	TAILQ_INIT(&hookprocs);
166211976Spjd	hooks_initialized = false;
167211976Spjd}
168211976Spjd
169211885Spjdstatic struct hookproc *
170211885Spjdhook_alloc(const char *path, char **args)
171211885Spjd{
172211885Spjd	struct hookproc *hp;
173211885Spjd	unsigned int ii;
174211885Spjd
175211885Spjd	hp = malloc(sizeof(*hp));
176211885Spjd	if (hp == NULL) {
177211885Spjd		pjdlog_error("Unable to allocate %zu bytes of memory for a hook.",
178211885Spjd		    sizeof(*hp));
179211885Spjd		return (NULL);
180211885Spjd	}
181211885Spjd
182211885Spjd	hp->hp_pid = 0;
183211885Spjd	hp->hp_birthtime = hp->hp_lastreport = time(NULL);
184211885Spjd	(void)strlcpy(hp->hp_comm, path, sizeof(hp->hp_comm));
185211885Spjd	/* We start at 2nd argument as we don't want to have exec name twice. */
186211885Spjd	for (ii = 1; args[ii] != NULL; ii++) {
187219816Spjd		(void)snprlcat(hp->hp_comm, sizeof(hp->hp_comm), " %s",
188219816Spjd		    args[ii]);
189211885Spjd	}
190211885Spjd	if (strlen(hp->hp_comm) >= sizeof(hp->hp_comm) - 1) {
191211885Spjd		pjdlog_error("Exec path too long, correct configuration file.");
192211885Spjd		free(hp);
193211885Spjd		return (NULL);
194211885Spjd	}
195211885Spjd	hp->hp_magic = HOOKPROC_MAGIC_ALLOCATED;
196211885Spjd	return (hp);
197211885Spjd}
198211885Spjd
199211885Spjdstatic void
200211885Spjdhook_add(struct hookproc *hp, pid_t pid)
201211885Spjd{
202211885Spjd
203229509Strociny	PJDLOG_ASSERT(hp->hp_magic == HOOKPROC_MAGIC_ALLOCATED);
204229509Strociny	PJDLOG_ASSERT(hp->hp_pid == 0);
205211885Spjd
206211885Spjd	hp->hp_pid = pid;
207211885Spjd	mtx_lock(&hookprocs_lock);
208211885Spjd	hp->hp_magic = HOOKPROC_MAGIC_ONLIST;
209211885Spjd	TAILQ_INSERT_TAIL(&hookprocs, hp, hp_next);
210211885Spjd	mtx_unlock(&hookprocs_lock);
211211885Spjd}
212211885Spjd
213211885Spjdstatic void
214211885Spjdhook_remove(struct hookproc *hp)
215211885Spjd{
216211885Spjd
217229509Strociny	PJDLOG_ASSERT(hp->hp_magic == HOOKPROC_MAGIC_ONLIST);
218229509Strociny	PJDLOG_ASSERT(hp->hp_pid > 0);
219229509Strociny	PJDLOG_ASSERT(mtx_owned(&hookprocs_lock));
220211885Spjd
221211885Spjd	TAILQ_REMOVE(&hookprocs, hp, hp_next);
222211885Spjd	hp->hp_magic = HOOKPROC_MAGIC_ALLOCATED;
223211885Spjd}
224211885Spjd
225211885Spjdstatic void
226211885Spjdhook_free(struct hookproc *hp)
227211885Spjd{
228211885Spjd
229229509Strociny	PJDLOG_ASSERT(hp->hp_magic == HOOKPROC_MAGIC_ALLOCATED);
230229509Strociny	PJDLOG_ASSERT(hp->hp_pid > 0);
231211885Spjd
232211885Spjd	hp->hp_magic = 0;
233211885Spjd	free(hp);
234211885Spjd}
235211885Spjd
236211885Spjdstatic struct hookproc *
237211885Spjdhook_find(pid_t pid)
238211885Spjd{
239211885Spjd	struct hookproc *hp;
240211885Spjd
241229509Strociny	PJDLOG_ASSERT(mtx_owned(&hookprocs_lock));
242211885Spjd
243211885Spjd	TAILQ_FOREACH(hp, &hookprocs, hp_next) {
244229509Strociny		PJDLOG_ASSERT(hp->hp_magic == HOOKPROC_MAGIC_ONLIST);
245229509Strociny		PJDLOG_ASSERT(hp->hp_pid > 0);
246211885Spjd
247211885Spjd		if (hp->hp_pid == pid)
248211885Spjd			break;
249211885Spjd	}
250211885Spjd
251211885Spjd	return (hp);
252211885Spjd}
253211885Spjd
254211885Spjdvoid
255211976Spjdhook_check_one(pid_t pid, int status)
256211976Spjd{
257211976Spjd	struct hookproc *hp;
258211976Spjd
259211976Spjd	mtx_lock(&hookprocs_lock);
260211976Spjd	hp = hook_find(pid);
261211976Spjd	if (hp == NULL) {
262211976Spjd		mtx_unlock(&hookprocs_lock);
263211976Spjd		pjdlog_debug(1, "Unknown process pid=%u", pid);
264211976Spjd		return;
265211976Spjd	}
266211976Spjd	hook_remove(hp);
267211976Spjd	mtx_unlock(&hookprocs_lock);
268211976Spjd	if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
269211976Spjd		pjdlog_debug(1, "Hook exited gracefully (pid=%u, cmd=[%s]).",
270211976Spjd		    pid, hp->hp_comm);
271211976Spjd	} else if (WIFSIGNALED(status)) {
272211976Spjd		pjdlog_error("Hook was killed (pid=%u, signal=%d, cmd=[%s]).",
273211976Spjd		    pid, WTERMSIG(status), hp->hp_comm);
274211976Spjd	} else {
275211976Spjd		pjdlog_error("Hook exited ungracefully (pid=%u, exitcode=%d, cmd=[%s]).",
276211976Spjd		    pid, WIFEXITED(status) ? WEXITSTATUS(status) : -1,
277211976Spjd		    hp->hp_comm);
278211976Spjd	}
279211976Spjd	hook_free(hp);
280211976Spjd}
281211976Spjd
282211976Spjdvoid
283213429Spjdhook_check(void)
284211885Spjd{
285211885Spjd	struct hookproc *hp, *hp2;
286211885Spjd	time_t now;
287211885Spjd
288229509Strociny	PJDLOG_ASSERT(hooks_initialized);
289211885Spjd
290219832Spjd	pjdlog_debug(2, "Checking hooks.");
291219817Spjd
292211885Spjd	/*
293211885Spjd	 * Report about processes that are running for a long time.
294211885Spjd	 */
295211885Spjd	now = time(NULL);
296211885Spjd	mtx_lock(&hookprocs_lock);
297211885Spjd	TAILQ_FOREACH_SAFE(hp, &hookprocs, hp_next, hp2) {
298229509Strociny		PJDLOG_ASSERT(hp->hp_magic == HOOKPROC_MAGIC_ONLIST);
299229509Strociny		PJDLOG_ASSERT(hp->hp_pid > 0);
300211885Spjd
301211885Spjd		/*
302211885Spjd		 * If process doesn't exists we somehow missed it.
303211885Spjd		 * Not much can be done expect for logging this situation.
304211885Spjd		 */
305211885Spjd		if (kill(hp->hp_pid, 0) == -1 && errno == ESRCH) {
306211885Spjd			pjdlog_warning("Hook disappeared (pid=%u, cmd=[%s]).",
307211885Spjd			    hp->hp_pid, hp->hp_comm);
308211885Spjd			hook_remove(hp);
309211885Spjd			hook_free(hp);
310211885Spjd			continue;
311211885Spjd		}
312211885Spjd
313211885Spjd		/*
314211885Spjd		 * Skip proccesses younger than 1 minute.
315211885Spjd		 */
316211885Spjd		if (now - hp->hp_lastreport < REPORT_INTERVAL)
317211885Spjd			continue;
318211885Spjd
319211885Spjd		/*
320211885Spjd		 * Hook is running for too long, report it.
321211885Spjd		 */
322211885Spjd		pjdlog_warning("Hook is running for %ju seconds (pid=%u, cmd=[%s]).",
323211885Spjd		    (uintmax_t)(now - hp->hp_birthtime), hp->hp_pid,
324211885Spjd		    hp->hp_comm);
325211885Spjd		hp->hp_lastreport = now;
326211885Spjd	}
327211885Spjd	mtx_unlock(&hookprocs_lock);
328211885Spjd}
329211885Spjd
330211885Spjdvoid
331204076Spjdhook_exec(const char *path, ...)
332204076Spjd{
333204076Spjd	va_list ap;
334204076Spjd
335204076Spjd	va_start(ap, path);
336211885Spjd	hook_execv(path, ap);
337204076Spjd	va_end(ap);
338204076Spjd}
339204076Spjd
340211885Spjdvoid
341204076Spjdhook_execv(const char *path, va_list ap)
342204076Spjd{
343211885Spjd	struct hookproc *hp;
344204076Spjd	char *args[64];
345204076Spjd	unsigned int ii;
346213938Spjd	sigset_t mask;
347211885Spjd	pid_t pid;
348204076Spjd
349229509Strociny	PJDLOG_ASSERT(hooks_initialized);
350211885Spjd
351204076Spjd	if (path == NULL || path[0] == '\0')
352211885Spjd		return;
353204076Spjd
354204076Spjd	memset(args, 0, sizeof(args));
355204076Spjd	args[0] = basename(path);
356204076Spjd	for (ii = 1; ii < sizeof(args) / sizeof(args[0]); ii++) {
357204076Spjd		args[ii] = va_arg(ap, char *);
358204076Spjd		if (args[ii] == NULL)
359204076Spjd			break;
360204076Spjd	}
361229509Strociny	PJDLOG_ASSERT(ii < sizeof(args) / sizeof(args[0]));
362204076Spjd
363211885Spjd	hp = hook_alloc(path, args);
364211885Spjd	if (hp == NULL)
365211885Spjd		return;
366211885Spjd
367219817Spjd	pjdlog_debug(1, "Executing hook: %s", hp->hp_comm);
368219817Spjd
369204076Spjd	pid = fork();
370204076Spjd	switch (pid) {
371204076Spjd	case -1:	/* Error. */
372211885Spjd		pjdlog_errno(LOG_ERR, "Unable to fork to execute %s", path);
373213183Spjd		hook_free(hp);
374211885Spjd		return;
375204076Spjd	case 0:		/* Child. */
376204076Spjd		descriptors();
377213938Spjd		PJDLOG_VERIFY(sigemptyset(&mask) == 0);
378213938Spjd		PJDLOG_VERIFY(sigprocmask(SIG_SETMASK, &mask, NULL) == 0);
379217308Spjd		/*
380217312Spjd		 * Dummy handler set for SIGCHLD in the parent will be restored
381217312Spjd		 * to SIG_IGN on execv(3) below, so there is no need to do
382217312Spjd		 * anything with it.
383217308Spjd		 */
384204076Spjd		execv(path, args);
385204076Spjd		pjdlog_errno(LOG_ERR, "Unable to execute %s", path);
386204076Spjd		exit(EX_SOFTWARE);
387204076Spjd	default:	/* Parent. */
388211885Spjd		hook_add(hp, pid);
389204076Spjd		break;
390204076Spjd	}
391204076Spjd}
392