hooks.c revision 229509
1231200Smm/*-
2231200Smm * Copyright (c) 2010 The FreeBSD Foundation
3231200Smm * Copyright (c) 2010 Pawel Jakub Dawidek <pjd@FreeBSD.org>
4231200Smm * All rights reserved.
5231200Smm *
6231200Smm * This software was developed by Pawel Jakub Dawidek under sponsorship from
7231200Smm * the FreeBSD Foundation.
8231200Smm *
9231200Smm * Redistribution and use in source and binary forms, with or without
10231200Smm * modification, are permitted provided that the following conditions
11231200Smm * are met:
12231200Smm * 1. Redistributions of source code must retain the above copyright
13231200Smm *    notice, this list of conditions and the following disclaimer.
14231200Smm * 2. Redistributions in binary form must reproduce the above copyright
15231200Smm *    notice, this list of conditions and the following disclaimer in the
16231200Smm *    documentation and/or other materials provided with the distribution.
17231200Smm *
18231200Smm * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
19231200Smm * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20231200Smm * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21231200Smm * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
22231200Smm * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23231200Smm * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24231200Smm * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25231200Smm * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26231200Smm * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27231200Smm * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28231200Smm * SUCH DAMAGE.
29231200Smm */
30231200Smm
31231200Smm#include <sys/cdefs.h>
32231200Smm__FBSDID("$FreeBSD: stable/9/sbin/hastd/hooks.c 229509 2012-01-04 17:22:10Z trociny $");
33231200Smm
34231200Smm#include <sys/types.h>
35231200Smm#include <sys/sysctl.h>
36231200Smm#include <sys/wait.h>
37231200Smm
38231200Smm#include <errno.h>
39231200Smm#include <fcntl.h>
40231200Smm#include <libgen.h>
41231200Smm#include <paths.h>
42231200Smm#include <signal.h>
43231200Smm#include <stdbool.h>
44231200Smm#include <stdint.h>
45231200Smm#include <stdio.h>
46231200Smm#include <stdlib.h>
47231200Smm#include <string.h>
48231200Smm#include <syslog.h>
49231200Smm#include <unistd.h>
50231200Smm
51231200Smm#include <pjdlog.h>
52231200Smm
53231200Smm#include "hooks.h"
54231200Smm#include "subr.h"
55231200Smm#include "synch.h"
56231200Smm
57231200Smm/* Report processes that are running for too long not often than this value. */
58231200Smm#define	REPORT_INTERVAL	60
59231200Smm
60231200Smm/* Are we initialized? */
61231200Smmstatic bool hooks_initialized = false;
62231200Smm
63231200Smm/*
64231200Smm * Keep all processes we forked on a global queue, so we can report nicely
65231200Smm * when they finish or report that they are running for a long time.
66231200Smm */
67231200Smm#define	HOOKPROC_MAGIC_ALLOCATED	0x80090ca
68231200Smm#define	HOOKPROC_MAGIC_ONLIST		0x80090c0
69231200Smmstruct hookproc {
70231200Smm	/* Magic. */
71231200Smm	int	hp_magic;
72231200Smm	/* PID of a forked child. */
73231200Smm	pid_t	hp_pid;
74231200Smm	/* When process were forked? */
75231200Smm	time_t	hp_birthtime;
76231200Smm	/* When we logged previous reported? */
77231200Smm	time_t	hp_lastreport;
78231200Smm	/* Path to executable and all the arguments we passed. */
79231200Smm	char	hp_comm[PATH_MAX];
80231200Smm	TAILQ_ENTRY(hookproc) hp_next;
81231200Smm};
82231200Smmstatic TAILQ_HEAD(, hookproc) hookprocs;
83231200Smmstatic pthread_mutex_t hookprocs_lock;
84231200Smm
85231200Smmstatic void hook_remove(struct hookproc *hp);
86231200Smmstatic void hook_free(struct hookproc *hp);
87231200Smm
88231200Smmstatic void
89231200Smmdescriptors(void)
90231200Smm{
91231200Smm	int fd;
92231200Smm
93231200Smm	/*
94231200Smm	 * Close all (or almost all) descriptors.
95231200Smm	 */
96231200Smm	if (pjdlog_mode_get() == PJDLOG_MODE_STD) {
97231200Smm		closefrom(MAX(MAX(STDIN_FILENO, STDOUT_FILENO),
98231200Smm		    STDERR_FILENO) + 1);
99231200Smm		return;
100231200Smm	}
101231200Smm
102231200Smm	closefrom(0);
103231200Smm
104231200Smm	/*
105231200Smm	 * Redirect stdin, stdout and stderr to /dev/null.
106231200Smm	 */
107231200Smm	fd = open(_PATH_DEVNULL, O_RDONLY);
108231200Smm	if (fd < 0) {
109231200Smm		pjdlog_errno(LOG_WARNING, "Unable to open %s for reading",
110231200Smm		    _PATH_DEVNULL);
111231200Smm	} else if (fd != STDIN_FILENO) {
112231200Smm		if (dup2(fd, STDIN_FILENO) < 0) {
113231200Smm			pjdlog_errno(LOG_WARNING,
114231200Smm			    "Unable to duplicate descriptor for stdin");
115231200Smm		}
116231200Smm		close(fd);
117231200Smm	}
118231200Smm	fd = open(_PATH_DEVNULL, O_WRONLY);
119231200Smm	if (fd < 0) {
120231200Smm		pjdlog_errno(LOG_WARNING, "Unable to open %s for writing",
121231200Smm		    _PATH_DEVNULL);
122231200Smm	} else {
123231200Smm		if (fd != STDOUT_FILENO && dup2(fd, STDOUT_FILENO) < 0) {
124231200Smm			pjdlog_errno(LOG_WARNING,
125231200Smm			    "Unable to duplicate descriptor for stdout");
126231200Smm		}
127231200Smm		if (fd != STDERR_FILENO && dup2(fd, STDERR_FILENO) < 0) {
128231200Smm			pjdlog_errno(LOG_WARNING,
129231200Smm			    "Unable to duplicate descriptor for stderr");
130231200Smm		}
131231200Smm		if (fd != STDOUT_FILENO && fd != STDERR_FILENO)
132231200Smm			close(fd);
133231200Smm	}
134231200Smm}
135231200Smm
136231200Smmvoid
137231200Smmhook_init(void)
138231200Smm{
139231200Smm
140231200Smm	PJDLOG_ASSERT(!hooks_initialized);
141231200Smm
142231200Smm	mtx_init(&hookprocs_lock);
143231200Smm	TAILQ_INIT(&hookprocs);
144231200Smm	hooks_initialized = true;
145231200Smm}
146231200Smm
147231200Smmvoid
148231200Smmhook_fini(void)
149231200Smm{
150231200Smm	struct hookproc *hp;
151231200Smm
152231200Smm	PJDLOG_ASSERT(hooks_initialized);
153231200Smm
154231200Smm	mtx_lock(&hookprocs_lock);
155231200Smm	while ((hp = TAILQ_FIRST(&hookprocs)) != NULL) {
156231200Smm		PJDLOG_ASSERT(hp->hp_magic == HOOKPROC_MAGIC_ONLIST);
157231200Smm		PJDLOG_ASSERT(hp->hp_pid > 0);
158231200Smm
159231200Smm		hook_remove(hp);
160231200Smm		hook_free(hp);
161231200Smm	}
162231200Smm	mtx_unlock(&hookprocs_lock);
163231200Smm
164231200Smm	mtx_destroy(&hookprocs_lock);
165231200Smm	TAILQ_INIT(&hookprocs);
166231200Smm	hooks_initialized = false;
167231200Smm}
168231200Smm
169231200Smmstatic struct hookproc *
170231200Smmhook_alloc(const char *path, char **args)
171231200Smm{
172231200Smm	struct hookproc *hp;
173231200Smm	unsigned int ii;
174231200Smm
175231200Smm	hp = malloc(sizeof(*hp));
176231200Smm	if (hp == NULL) {
177231200Smm		pjdlog_error("Unable to allocate %zu bytes of memory for a hook.",
178231200Smm		    sizeof(*hp));
179231200Smm		return (NULL);
180231200Smm	}
181231200Smm
182231200Smm	hp->hp_pid = 0;
183231200Smm	hp->hp_birthtime = hp->hp_lastreport = time(NULL);
184231200Smm	(void)strlcpy(hp->hp_comm, path, sizeof(hp->hp_comm));
185231200Smm	/* We start at 2nd argument as we don't want to have exec name twice. */
186231200Smm	for (ii = 1; args[ii] != NULL; ii++) {
187231200Smm		(void)snprlcat(hp->hp_comm, sizeof(hp->hp_comm), " %s",
188231200Smm		    args[ii]);
189299529Smm	}
190231200Smm	if (strlen(hp->hp_comm) >= sizeof(hp->hp_comm) - 1) {
191231200Smm		pjdlog_error("Exec path too long, correct configuration file.");
192231200Smm		free(hp);
193231200Smm		return (NULL);
194231200Smm	}
195231200Smm	hp->hp_magic = HOOKPROC_MAGIC_ALLOCATED;
196231200Smm	return (hp);
197231200Smm}
198231200Smm
199231200Smmstatic void
200231200Smmhook_add(struct hookproc *hp, pid_t pid)
201231200Smm{
202231200Smm
203248616Smm	PJDLOG_ASSERT(hp->hp_magic == HOOKPROC_MAGIC_ALLOCATED);
204248616Smm	PJDLOG_ASSERT(hp->hp_pid == 0);
205248616Smm
206248616Smm	hp->hp_pid = pid;
207248616Smm	mtx_lock(&hookprocs_lock);
208248616Smm	hp->hp_magic = HOOKPROC_MAGIC_ONLIST;
209248616Smm	TAILQ_INSERT_TAIL(&hookprocs, hp, hp_next);
210231200Smm	mtx_unlock(&hookprocs_lock);
211231200Smm}
212231200Smm
213231200Smmstatic void
214231200Smmhook_remove(struct hookproc *hp)
215231200Smm{
216231200Smm
217231200Smm	PJDLOG_ASSERT(hp->hp_magic == HOOKPROC_MAGIC_ONLIST);
218231200Smm	PJDLOG_ASSERT(hp->hp_pid > 0);
219231200Smm	PJDLOG_ASSERT(mtx_owned(&hookprocs_lock));
220231200Smm
221231200Smm	TAILQ_REMOVE(&hookprocs, hp, hp_next);
222231200Smm	hp->hp_magic = HOOKPROC_MAGIC_ALLOCATED;
223231200Smm}
224231200Smm
225231200Smmstatic void
226231200Smmhook_free(struct hookproc *hp)
227231200Smm{
228248616Smm
229299529Smm	PJDLOG_ASSERT(hp->hp_magic == HOOKPROC_MAGIC_ALLOCATED);
230231200Smm	PJDLOG_ASSERT(hp->hp_pid > 0);
231231200Smm
232231200Smm	hp->hp_magic = 0;
233231200Smm	free(hp);
234231200Smm}
235231200Smm
236231200Smmstatic struct hookproc *
237231200Smmhook_find(pid_t pid)
238231200Smm{
239231200Smm	struct hookproc *hp;
240231200Smm
241231200Smm	PJDLOG_ASSERT(mtx_owned(&hookprocs_lock));
242231200Smm
243231200Smm	TAILQ_FOREACH(hp, &hookprocs, hp_next) {
244231200Smm		PJDLOG_ASSERT(hp->hp_magic == HOOKPROC_MAGIC_ONLIST);
245231200Smm		PJDLOG_ASSERT(hp->hp_pid > 0);
246231200Smm
247248616Smm		if (hp->hp_pid == pid)
248231200Smm			break;
249231200Smm	}
250231200Smm
251231200Smm	return (hp);
252231200Smm}
253231200Smm
254231200Smmvoid
255231200Smmhook_check_one(pid_t pid, int status)
256231200Smm{
257248616Smm	struct hookproc *hp;
258248616Smm
259248616Smm	mtx_lock(&hookprocs_lock);
260248616Smm	hp = hook_find(pid);
261231200Smm	if (hp == NULL) {
262231200Smm		mtx_unlock(&hookprocs_lock);
263231200Smm		pjdlog_debug(1, "Unknown process pid=%u", pid);
264231200Smm		return;
265231200Smm	}
266231200Smm	hook_remove(hp);
267231200Smm	mtx_unlock(&hookprocs_lock);
268231200Smm	if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
269231200Smm		pjdlog_debug(1, "Hook exited gracefully (pid=%u, cmd=[%s]).",
270231200Smm		    pid, hp->hp_comm);
271231200Smm	} else if (WIFSIGNALED(status)) {
272231200Smm		pjdlog_error("Hook was killed (pid=%u, signal=%d, cmd=[%s]).",
273231200Smm		    pid, WTERMSIG(status), hp->hp_comm);
274231200Smm	} else {
275231200Smm		pjdlog_error("Hook exited ungracefully (pid=%u, exitcode=%d, cmd=[%s]).",
276231200Smm		    pid, WIFEXITED(status) ? WEXITSTATUS(status) : -1,
277231200Smm		    hp->hp_comm);
278231200Smm	}
279231200Smm	hook_free(hp);
280231200Smm}
281231200Smm
282231200Smmvoid
283231200Smmhook_check(void)
284231200Smm{
285231200Smm	struct hookproc *hp, *hp2;
286231200Smm	time_t now;
287231200Smm
288231200Smm	PJDLOG_ASSERT(hooks_initialized);
289231200Smm
290231200Smm	pjdlog_debug(2, "Checking hooks.");
291231200Smm
292231200Smm	/*
293231200Smm	 * Report about processes that are running for a long time.
294231200Smm	 */
295231200Smm	now = time(NULL);
296231200Smm	mtx_lock(&hookprocs_lock);
297231200Smm	TAILQ_FOREACH_SAFE(hp, &hookprocs, hp_next, hp2) {
298231200Smm		PJDLOG_ASSERT(hp->hp_magic == HOOKPROC_MAGIC_ONLIST);
299231200Smm		PJDLOG_ASSERT(hp->hp_pid > 0);
300231200Smm
301231200Smm		/*
302231200Smm		 * If process doesn't exists we somehow missed it.
303231200Smm		 * Not much can be done expect for logging this situation.
304231200Smm		 */
305231200Smm		if (kill(hp->hp_pid, 0) == -1 && errno == ESRCH) {
306231200Smm			pjdlog_warning("Hook disappeared (pid=%u, cmd=[%s]).",
307231200Smm			    hp->hp_pid, hp->hp_comm);
308231200Smm			hook_remove(hp);
309299529Smm			hook_free(hp);
310299529Smm			continue;
311299529Smm		}
312299529Smm
313299529Smm		/*
314231200Smm		 * Skip proccesses younger than 1 minute.
315231200Smm		 */
316299529Smm		if (now - hp->hp_lastreport < REPORT_INTERVAL)
317299529Smm			continue;
318231200Smm
319231200Smm		/*
320231200Smm		 * Hook is running for too long, report it.
321231200Smm		 */
322231200Smm		pjdlog_warning("Hook is running for %ju seconds (pid=%u, cmd=[%s]).",
323231200Smm		    (uintmax_t)(now - hp->hp_birthtime), hp->hp_pid,
324231200Smm		    hp->hp_comm);
325231200Smm		hp->hp_lastreport = now;
326248616Smm	}
327248616Smm	mtx_unlock(&hookprocs_lock);
328231200Smm}
329231200Smm
330231200Smmvoid
331231200Smmhook_exec(const char *path, ...)
332232153Smm{
333231200Smm	va_list ap;
334231200Smm
335231200Smm	va_start(ap, path);
336231200Smm	hook_execv(path, ap);
337231200Smm	va_end(ap);
338231200Smm}
339231200Smm
340231200Smmvoid
341231200Smmhook_execv(const char *path, va_list ap)
342231200Smm{
343231200Smm	struct hookproc *hp;
344231200Smm	char *args[64];
345231200Smm	unsigned int ii;
346231200Smm	sigset_t mask;
347231200Smm	pid_t pid;
348231200Smm
349231200Smm	PJDLOG_ASSERT(hooks_initialized);
350231200Smm
351231200Smm	if (path == NULL || path[0] == '\0')
352231200Smm		return;
353231200Smm
354231200Smm	memset(args, 0, sizeof(args));
355248616Smm	args[0] = basename(path);
356231200Smm	for (ii = 1; ii < sizeof(args) / sizeof(args[0]); ii++) {
357231200Smm		args[ii] = va_arg(ap, char *);
358231200Smm		if (args[ii] == NULL)
359231200Smm			break;
360231200Smm	}
361231200Smm	PJDLOG_ASSERT(ii < sizeof(args) / sizeof(args[0]));
362231200Smm
363231200Smm	hp = hook_alloc(path, args);
364231200Smm	if (hp == NULL)
365231200Smm		return;
366231200Smm
367231200Smm	pjdlog_debug(1, "Executing hook: %s", hp->hp_comm);
368231200Smm
369231200Smm	pid = fork();
370231200Smm	switch (pid) {
371231200Smm	case -1:	/* Error. */
372231200Smm		pjdlog_errno(LOG_ERR, "Unable to fork to execute %s", path);
373231200Smm		hook_free(hp);
374231200Smm		return;
375231200Smm	case 0:		/* Child. */
376231200Smm		descriptors();
377231200Smm		PJDLOG_VERIFY(sigemptyset(&mask) == 0);
378231200Smm		PJDLOG_VERIFY(sigprocmask(SIG_SETMASK, &mask, NULL) == 0);
379231200Smm		/*
380231200Smm		 * Dummy handler set for SIGCHLD in the parent will be restored
381231200Smm		 * to SIG_IGN on execv(3) below, so there is no need to do
382231200Smm		 * anything with it.
383231200Smm		 */
384231200Smm		execv(path, args);
385231200Smm		pjdlog_errno(LOG_ERR, "Unable to execute %s", path);
386231200Smm		exit(EX_SOFTWARE);
387231200Smm	default:	/* Parent. */
388231200Smm		hook_add(hp, pid);
389231200Smm		break;
390231200Smm	}
391231200Smm}
392231200Smm