hooks.c revision 214119
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 214119 2010-10-20 21:10:01Z 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"
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);
108204076Spjd	if (fd < 0) {
109204076Spjd		pjdlog_errno(LOG_WARNING, "Unable to open %s for reading",
110204076Spjd		    _PATH_DEVNULL);
111204076Spjd	} else if (fd != STDIN_FILENO) {
112204076Spjd		if (dup2(fd, STDIN_FILENO) < 0) {
113204076Spjd			pjdlog_errno(LOG_WARNING,
114204076Spjd			    "Unable to duplicate descriptor for stdin");
115204076Spjd		}
116204076Spjd		close(fd);
117204076Spjd	}
118204076Spjd	fd = open(_PATH_DEVNULL, O_WRONLY);
119204076Spjd	if (fd < 0) {
120204076Spjd		pjdlog_errno(LOG_WARNING, "Unable to open %s for writing",
121204076Spjd		    _PATH_DEVNULL);
122204076Spjd	} else {
123204076Spjd		if (fd != STDOUT_FILENO && dup2(fd, STDOUT_FILENO) < 0) {
124204076Spjd			pjdlog_errno(LOG_WARNING,
125204076Spjd			    "Unable to duplicate descriptor for stdout");
126204076Spjd		}
127204076Spjd		if (fd != STDERR_FILENO && dup2(fd, STDERR_FILENO) < 0) {
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
140211976Spjd	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
152211976Spjd	assert(hooks_initialized);
153211976Spjd
154211976Spjd	mtx_lock(&hookprocs_lock);
155211976Spjd	while ((hp = TAILQ_FIRST(&hookprocs)) != NULL) {
156211976Spjd		assert(hp->hp_magic == HOOKPROC_MAGIC_ONLIST);
157211976Spjd		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++) {
187211885Spjd		(void)strlcat(hp->hp_comm, " ", sizeof(hp->hp_comm));
188211885Spjd		(void)strlcat(hp->hp_comm, args[ii], sizeof(hp->hp_comm));
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
203211885Spjd	assert(hp->hp_magic == HOOKPROC_MAGIC_ALLOCATED);
204211885Spjd	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
217211885Spjd	assert(hp->hp_magic == HOOKPROC_MAGIC_ONLIST);
218211885Spjd	assert(hp->hp_pid > 0);
219211885Spjd	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
229211885Spjd	assert(hp->hp_magic == HOOKPROC_MAGIC_ALLOCATED);
230211885Spjd	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
241211885Spjd	assert(mtx_owned(&hookprocs_lock));
242211885Spjd
243211885Spjd	TAILQ_FOREACH(hp, &hookprocs, hp_next) {
244211885Spjd		assert(hp->hp_magic == HOOKPROC_MAGIC_ONLIST);
245211885Spjd		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
288211885Spjd	assert(hooks_initialized);
289211885Spjd
290211885Spjd	/*
291211885Spjd	 * Report about processes that are running for a long time.
292211885Spjd	 */
293211885Spjd	now = time(NULL);
294211885Spjd	mtx_lock(&hookprocs_lock);
295211885Spjd	TAILQ_FOREACH_SAFE(hp, &hookprocs, hp_next, hp2) {
296211885Spjd		assert(hp->hp_magic == HOOKPROC_MAGIC_ONLIST);
297211885Spjd		assert(hp->hp_pid > 0);
298211885Spjd
299211885Spjd		/*
300211885Spjd		 * If process doesn't exists we somehow missed it.
301211885Spjd		 * Not much can be done expect for logging this situation.
302211885Spjd		 */
303211885Spjd		if (kill(hp->hp_pid, 0) == -1 && errno == ESRCH) {
304211885Spjd			pjdlog_warning("Hook disappeared (pid=%u, cmd=[%s]).",
305211885Spjd			    hp->hp_pid, hp->hp_comm);
306211885Spjd			hook_remove(hp);
307211885Spjd			hook_free(hp);
308211885Spjd			continue;
309211885Spjd		}
310211885Spjd
311211885Spjd		/*
312211885Spjd		 * Skip proccesses younger than 1 minute.
313211885Spjd		 */
314211885Spjd		if (now - hp->hp_lastreport < REPORT_INTERVAL)
315211885Spjd			continue;
316211885Spjd
317211885Spjd		/*
318211885Spjd		 * Hook is running for too long, report it.
319211885Spjd		 */
320211885Spjd		pjdlog_warning("Hook is running for %ju seconds (pid=%u, cmd=[%s]).",
321211885Spjd		    (uintmax_t)(now - hp->hp_birthtime), hp->hp_pid,
322211885Spjd		    hp->hp_comm);
323211885Spjd		hp->hp_lastreport = now;
324211885Spjd	}
325211885Spjd	mtx_unlock(&hookprocs_lock);
326211885Spjd}
327211885Spjd
328211885Spjdvoid
329204076Spjdhook_exec(const char *path, ...)
330204076Spjd{
331204076Spjd	va_list ap;
332204076Spjd
333204076Spjd	va_start(ap, path);
334211885Spjd	hook_execv(path, ap);
335204076Spjd	va_end(ap);
336204076Spjd}
337204076Spjd
338211885Spjdvoid
339204076Spjdhook_execv(const char *path, va_list ap)
340204076Spjd{
341211885Spjd	struct hookproc *hp;
342204076Spjd	char *args[64];
343204076Spjd	unsigned int ii;
344213938Spjd	sigset_t mask;
345211885Spjd	pid_t pid;
346204076Spjd
347211885Spjd	assert(hooks_initialized);
348211885Spjd
349204076Spjd	if (path == NULL || path[0] == '\0')
350211885Spjd		return;
351204076Spjd
352204076Spjd	memset(args, 0, sizeof(args));
353204076Spjd	args[0] = basename(path);
354204076Spjd	for (ii = 1; ii < sizeof(args) / sizeof(args[0]); ii++) {
355204076Spjd		args[ii] = va_arg(ap, char *);
356204076Spjd		if (args[ii] == NULL)
357204076Spjd			break;
358204076Spjd	}
359204076Spjd	assert(ii < sizeof(args) / sizeof(args[0]));
360204076Spjd
361211885Spjd	hp = hook_alloc(path, args);
362211885Spjd	if (hp == NULL)
363211885Spjd		return;
364211885Spjd
365204076Spjd	pid = fork();
366204076Spjd	switch (pid) {
367204076Spjd	case -1:	/* Error. */
368211885Spjd		pjdlog_errno(LOG_ERR, "Unable to fork to execute %s", path);
369213183Spjd		hook_free(hp);
370211885Spjd		return;
371204076Spjd	case 0:		/* Child. */
372204076Spjd		descriptors();
373213938Spjd		PJDLOG_VERIFY(sigemptyset(&mask) == 0);
374213938Spjd		PJDLOG_VERIFY(sigprocmask(SIG_SETMASK, &mask, NULL) == 0);
375204076Spjd		execv(path, args);
376204076Spjd		pjdlog_errno(LOG_ERR, "Unable to execute %s", path);
377204076Spjd		exit(EX_SOFTWARE);
378204076Spjd	default:	/* Parent. */
379211885Spjd		hook_add(hp, pid);
380204076Spjd		break;
381204076Spjd	}
382204076Spjd}
383