hooks.c revision 211885
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 211885 2010-08-27 14:38:12Z 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
85204076Spjdstatic void
86204076Spjddescriptors(void)
87204076Spjd{
88204076Spjd	long maxfd;
89204076Spjd	int fd;
90204076Spjd
91204076Spjd	/*
92204076Spjd	 * Close all descriptors.
93204076Spjd	 */
94204076Spjd	maxfd = sysconf(_SC_OPEN_MAX);
95204076Spjd	if (maxfd < 0) {
96204076Spjd		pjdlog_errno(LOG_WARNING, "sysconf(_SC_OPEN_MAX) failed");
97204076Spjd		maxfd = 1024;
98204076Spjd	}
99211884Spjd	for (fd = 0; fd <= maxfd; fd++) {
100211884Spjd		switch (fd) {
101211884Spjd		case STDIN_FILENO:
102211884Spjd		case STDOUT_FILENO:
103211884Spjd		case STDERR_FILENO:
104211884Spjd			if (pjdlog_mode_get() == PJDLOG_MODE_STD)
105211884Spjd				break;
106211884Spjd			/* FALLTHROUGH */
107211884Spjd		default:
108211884Spjd			close(fd);
109211884Spjd			break;
110211884Spjd		}
111211884Spjd	}
112211884Spjd	if (pjdlog_mode_get() == PJDLOG_MODE_STD)
113211884Spjd		return;
114204076Spjd	/*
115204076Spjd	 * Redirect stdin, stdout and stderr to /dev/null.
116204076Spjd	 */
117204076Spjd	fd = open(_PATH_DEVNULL, O_RDONLY);
118204076Spjd	if (fd < 0) {
119204076Spjd		pjdlog_errno(LOG_WARNING, "Unable to open %s for reading",
120204076Spjd		    _PATH_DEVNULL);
121204076Spjd	} else if (fd != STDIN_FILENO) {
122204076Spjd		if (dup2(fd, STDIN_FILENO) < 0) {
123204076Spjd			pjdlog_errno(LOG_WARNING,
124204076Spjd			    "Unable to duplicate descriptor for stdin");
125204076Spjd		}
126204076Spjd		close(fd);
127204076Spjd	}
128204076Spjd	fd = open(_PATH_DEVNULL, O_WRONLY);
129204076Spjd	if (fd < 0) {
130204076Spjd		pjdlog_errno(LOG_WARNING, "Unable to open %s for writing",
131204076Spjd		    _PATH_DEVNULL);
132204076Spjd	} else {
133204076Spjd		if (fd != STDOUT_FILENO && dup2(fd, STDOUT_FILENO) < 0) {
134204076Spjd			pjdlog_errno(LOG_WARNING,
135204076Spjd			    "Unable to duplicate descriptor for stdout");
136204076Spjd		}
137204076Spjd		if (fd != STDERR_FILENO && dup2(fd, STDERR_FILENO) < 0) {
138204076Spjd			pjdlog_errno(LOG_WARNING,
139204076Spjd			    "Unable to duplicate descriptor for stderr");
140204076Spjd		}
141204076Spjd		if (fd != STDOUT_FILENO && fd != STDERR_FILENO)
142204076Spjd			close(fd);
143204076Spjd	}
144204076Spjd}
145204076Spjd
146211885Spjdvoid
147211885Spjdhook_init(void)
148211885Spjd{
149211885Spjd
150211885Spjd	mtx_init(&hookprocs_lock);
151211885Spjd	TAILQ_INIT(&hookprocs);
152211885Spjd	hooks_initialized = true;
153211885Spjd}
154211885Spjd
155211885Spjdstatic struct hookproc *
156211885Spjdhook_alloc(const char *path, char **args)
157211885Spjd{
158211885Spjd	struct hookproc *hp;
159211885Spjd	unsigned int ii;
160211885Spjd
161211885Spjd	hp = malloc(sizeof(*hp));
162211885Spjd	if (hp == NULL) {
163211885Spjd		pjdlog_error("Unable to allocate %zu bytes of memory for a hook.",
164211885Spjd		    sizeof(*hp));
165211885Spjd		return (NULL);
166211885Spjd	}
167211885Spjd
168211885Spjd	hp->hp_pid = 0;
169211885Spjd	hp->hp_birthtime = hp->hp_lastreport = time(NULL);
170211885Spjd	(void)strlcpy(hp->hp_comm, path, sizeof(hp->hp_comm));
171211885Spjd	/* We start at 2nd argument as we don't want to have exec name twice. */
172211885Spjd	for (ii = 1; args[ii] != NULL; ii++) {
173211885Spjd		(void)strlcat(hp->hp_comm, " ", sizeof(hp->hp_comm));
174211885Spjd		(void)strlcat(hp->hp_comm, args[ii], sizeof(hp->hp_comm));
175211885Spjd	}
176211885Spjd	if (strlen(hp->hp_comm) >= sizeof(hp->hp_comm) - 1) {
177211885Spjd		pjdlog_error("Exec path too long, correct configuration file.");
178211885Spjd		free(hp);
179211885Spjd		return (NULL);
180211885Spjd	}
181211885Spjd	hp->hp_magic = HOOKPROC_MAGIC_ALLOCATED;
182211885Spjd	return (hp);
183211885Spjd}
184211885Spjd
185211885Spjdstatic void
186211885Spjdhook_add(struct hookproc *hp, pid_t pid)
187211885Spjd{
188211885Spjd
189211885Spjd	assert(hp->hp_magic == HOOKPROC_MAGIC_ALLOCATED);
190211885Spjd	assert(hp->hp_pid == 0);
191211885Spjd
192211885Spjd	hp->hp_pid = pid;
193211885Spjd	mtx_lock(&hookprocs_lock);
194211885Spjd	hp->hp_magic = HOOKPROC_MAGIC_ONLIST;
195211885Spjd	TAILQ_INSERT_TAIL(&hookprocs, hp, hp_next);
196211885Spjd	mtx_unlock(&hookprocs_lock);
197211885Spjd}
198211885Spjd
199211885Spjdstatic void
200211885Spjdhook_remove(struct hookproc *hp)
201211885Spjd{
202211885Spjd
203211885Spjd	assert(hp->hp_magic == HOOKPROC_MAGIC_ONLIST);
204211885Spjd	assert(hp->hp_pid > 0);
205211885Spjd	assert(mtx_owned(&hookprocs_lock));
206211885Spjd
207211885Spjd	TAILQ_REMOVE(&hookprocs, hp, hp_next);
208211885Spjd	hp->hp_magic = HOOKPROC_MAGIC_ALLOCATED;
209211885Spjd}
210211885Spjd
211211885Spjdstatic void
212211885Spjdhook_free(struct hookproc *hp)
213211885Spjd{
214211885Spjd
215211885Spjd	assert(hp->hp_magic == HOOKPROC_MAGIC_ALLOCATED);
216211885Spjd	assert(hp->hp_pid > 0);
217211885Spjd
218211885Spjd	hp->hp_magic = 0;
219211885Spjd	free(hp);
220211885Spjd}
221211885Spjd
222211885Spjdstatic struct hookproc *
223211885Spjdhook_find(pid_t pid)
224211885Spjd{
225211885Spjd	struct hookproc *hp;
226211885Spjd
227211885Spjd	assert(mtx_owned(&hookprocs_lock));
228211885Spjd
229211885Spjd	TAILQ_FOREACH(hp, &hookprocs, hp_next) {
230211885Spjd		assert(hp->hp_magic == HOOKPROC_MAGIC_ONLIST);
231211885Spjd		assert(hp->hp_pid > 0);
232211885Spjd
233211885Spjd		if (hp->hp_pid == pid)
234211885Spjd			break;
235211885Spjd	}
236211885Spjd
237211885Spjd	return (hp);
238211885Spjd}
239211885Spjd
240211885Spjdvoid
241211885Spjdhook_check(bool sigchld)
242211885Spjd{
243211885Spjd	struct hookproc *hp, *hp2;
244211885Spjd	int status;
245211885Spjd	time_t now;
246211885Spjd	pid_t pid;
247211885Spjd
248211885Spjd	assert(hooks_initialized);
249211885Spjd
250211885Spjd	/*
251211885Spjd	 * If SIGCHLD was received, garbage collect finished processes.
252211885Spjd	 */
253211885Spjd	while (sigchld && (pid = wait3(&status, WNOHANG, NULL)) > 0) {
254211885Spjd		mtx_lock(&hookprocs_lock);
255211885Spjd		hp = hook_find(pid);
256211885Spjd		if (hp == NULL) {
257211885Spjd			mtx_unlock(&hookprocs_lock);
258211885Spjd			pjdlog_warning("Unknown process pid=%u", pid);
259211885Spjd			continue;
260211885Spjd		}
261211885Spjd		hook_remove(hp);
262211885Spjd		mtx_unlock(&hookprocs_lock);
263211885Spjd		if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
264211885Spjd			pjdlog_debug(1, "Hook exited gracefully (pid=%u, cmd=[%s]).",
265211885Spjd			    pid, hp->hp_comm);
266211885Spjd		} else if (WIFSIGNALED(status)) {
267211885Spjd			pjdlog_error("Hook was killed (pid=%u, signal=%d, cmd=[%s]).",
268211885Spjd			    pid, WTERMSIG(status), hp->hp_comm);
269211885Spjd		} else {
270211885Spjd			pjdlog_error("Hook exited ungracefully (pid=%u, exitcode=%d, cmd=[%s]).",
271211885Spjd			    pid, WIFEXITED(status) ? WEXITSTATUS(status) : -1,
272211885Spjd			    hp->hp_comm);
273211885Spjd		}
274211885Spjd		hook_free(hp);
275211885Spjd	}
276211885Spjd
277211885Spjd	/*
278211885Spjd	 * Report about processes that are running for a long time.
279211885Spjd	 */
280211885Spjd	now = time(NULL);
281211885Spjd	mtx_lock(&hookprocs_lock);
282211885Spjd	TAILQ_FOREACH_SAFE(hp, &hookprocs, hp_next, hp2) {
283211885Spjd		assert(hp->hp_magic == HOOKPROC_MAGIC_ONLIST);
284211885Spjd		assert(hp->hp_pid > 0);
285211885Spjd
286211885Spjd		/*
287211885Spjd		 * If process doesn't exists we somehow missed it.
288211885Spjd		 * Not much can be done expect for logging this situation.
289211885Spjd		 */
290211885Spjd		if (kill(hp->hp_pid, 0) == -1 && errno == ESRCH) {
291211885Spjd			pjdlog_warning("Hook disappeared (pid=%u, cmd=[%s]).",
292211885Spjd			    hp->hp_pid, hp->hp_comm);
293211885Spjd			hook_remove(hp);
294211885Spjd			hook_free(hp);
295211885Spjd			continue;
296211885Spjd		}
297211885Spjd
298211885Spjd		/*
299211885Spjd		 * Skip proccesses younger than 1 minute.
300211885Spjd		 */
301211885Spjd		if (now - hp->hp_lastreport < REPORT_INTERVAL)
302211885Spjd			continue;
303211885Spjd
304211885Spjd		/*
305211885Spjd		 * Hook is running for too long, report it.
306211885Spjd		 */
307211885Spjd		pjdlog_warning("Hook is running for %ju seconds (pid=%u, cmd=[%s]).",
308211885Spjd		    (uintmax_t)(now - hp->hp_birthtime), hp->hp_pid,
309211885Spjd		    hp->hp_comm);
310211885Spjd		hp->hp_lastreport = now;
311211885Spjd	}
312211885Spjd	mtx_unlock(&hookprocs_lock);
313211885Spjd}
314211885Spjd
315211885Spjdvoid
316204076Spjdhook_exec(const char *path, ...)
317204076Spjd{
318204076Spjd	va_list ap;
319204076Spjd
320204076Spjd	va_start(ap, path);
321211885Spjd	hook_execv(path, ap);
322204076Spjd	va_end(ap);
323204076Spjd}
324204076Spjd
325211885Spjdvoid
326204076Spjdhook_execv(const char *path, va_list ap)
327204076Spjd{
328211885Spjd	struct hookproc *hp;
329204076Spjd	char *args[64];
330204076Spjd	unsigned int ii;
331211885Spjd	pid_t pid;
332204076Spjd
333211885Spjd	assert(hooks_initialized);
334211885Spjd
335204076Spjd	if (path == NULL || path[0] == '\0')
336211885Spjd		return;
337204076Spjd
338204076Spjd	memset(args, 0, sizeof(args));
339204076Spjd	args[0] = basename(path);
340204076Spjd	for (ii = 1; ii < sizeof(args) / sizeof(args[0]); ii++) {
341204076Spjd		args[ii] = va_arg(ap, char *);
342204076Spjd		if (args[ii] == NULL)
343204076Spjd			break;
344204076Spjd	}
345204076Spjd	assert(ii < sizeof(args) / sizeof(args[0]));
346204076Spjd
347211885Spjd	hp = hook_alloc(path, args);
348211885Spjd	if (hp == NULL)
349211885Spjd		return;
350211885Spjd
351204076Spjd	pid = fork();
352204076Spjd	switch (pid) {
353204076Spjd	case -1:	/* Error. */
354211885Spjd		pjdlog_errno(LOG_ERR, "Unable to fork to execute %s", path);
355211885Spjd		return;
356204076Spjd	case 0:		/* Child. */
357204076Spjd		descriptors();
358204076Spjd		execv(path, args);
359204076Spjd		pjdlog_errno(LOG_ERR, "Unable to execute %s", path);
360204076Spjd		exit(EX_SOFTWARE);
361204076Spjd	default:	/* Parent. */
362211885Spjd		hook_add(hp, pid);
363204076Spjd		break;
364204076Spjd	}
365204076Spjd}
366