1    	/* 
2    	 * Copyright (C) 2014 Philipp Marek <philipp.marek@linbit.com>
3    	 * 
4    	 * This program is free software; you can redistribute it and/or
5    	 * modify it under the terms of the GNU General Public
6    	 * License as published by the Free Software Foundation; either
7    	 * version 2 of the License, or (at your option) any later version.
8    	 * 
9    	 * This software is distributed in the hope that it will be useful,
10   	 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11   	 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12   	 * General Public License for more details.
13   	 * 
14   	 * You should have received a copy of the GNU General Public License along
15   	 * with this program; if not, write to the Free Software Foundation, Inc.,
16   	 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17   	 */
18   	
19   	#include <stdlib.h>
20   	#include <string.h>
21   	#include <errno.h>
22   	#include <arpa/inet.h>
23   	#include <signal.h>
24   	#include <sys/wait.h>
25   	#include <inttypes.h>
26   	#include <dirent.h>
27   	#include <stdio.h>
28   	#include <assert.h>
29   	#include <time.h>
30   	#include "ticket.h"
31   	#include "config.h"
32   	#include "inline-fn.h"
33   	#include "log.h"
34   	#include "pacemaker.h"
35   	#include "booth.h"
36   	#include "handler.h"
37   	
38   	static int
39   	set_booth_env(const struct booth_config *conf, struct ticket_config *tk)
40   	{
41   		int rv;
42   		char expires[16];
43   	
44   		sprintf(expires, "%" PRId64, (int64_t)wall_ts(&tk->term_expires));
45   		rv = setenv("BOOTH_TICKET", tk->name, 1) ||
46   			setenv("BOOTH_LOCAL", local->addr_string, 1) ||
47   			setenv("BOOTH_CONF_NAME", conf->name, 1) ||
48   			setenv("BOOTH_CONF_PATH", cl.configfile, 1) ||
49   			setenv("BOOTH_TICKET_EXPIRES", expires, 1);
50   	
51   		if (rv) {
52   			log_error("Cannot set environment: %s", strerror(errno));
53   		}
54   		return rv;
55   	}
56   	
57   	static void
58   	closefiles(void)
59   	{
60   		int fd;
61   	
62   		/* close all descriptors except stdin/out/err */
63   		for (fd = getdtablesize() - 1; fd > STDERR_FILENO; fd--) {
64   			close(fd);
65   		}
66   	}
67   	
68   	static void
69   	run_ext_prog(const struct booth_config *conf, struct ticket_config *tk,
70   	             char *prog)
71   	{
72   		if (set_booth_env(conf, tk)) {
73   			_exit(1);
74   		}
75   		closefiles(); /* don't leak open files */
76   		tk_log_debug("running handler %s", prog);
77   		execv(prog, tk_test.argv);
78   		tk_log_error("%s: execv failed (%s)", prog, strerror(errno));
79   		_exit(1);
80   	}
81   	
82   	static int
83   	prog_filter(const struct dirent *dp)
84   	{
85   		return (*dp->d_name != '.');
86   	}
87   	
88   	static pid_t curr_pid;
89   	static int ignore_status;
90   	
91   	static int
92   	test_exit_status(struct ticket_config *tk, char *prog, int status, int log_msg)
93   	{
94   		int rv = -1;
95   	
96   		if (WIFEXITED(status)) {
97   			rv = WEXITSTATUS(status);
98   		} else if (WIFSIGNALED(status)) {
99   			rv = 128 + WTERMSIG(status);
100  		}
101  		if (rv) {
102  			if (log_msg) {
103  				tk_log_warn("handler \"%s\" failed: %s",
104  					prog, interpret_rv(status));
105  				tk_log_warn("we are not allowed to acquire ticket");
106  			}
107  		} else {
108  			tk_log_debug("handler \"%s\" exited with success",
109  				prog);
110  		}
111  		return rv;
112  	}
113  	
114  	static void
115  	reset_test_state(struct ticket_config *tk)
116  	{
117  		tk_test.pid = 0;
118  		set_progstate(tk, EXTPROG_IDLE);
119  	}
120  	
121  	int
122  	tk_test_exit_status(struct ticket_config *tk)
123  	{
124  		int rv;
125  	
126  		rv = test_exit_status(tk, tk_test.path, tk_test.status, !tk_test.is_dir);
127  		reset_test_state(tk);
128  		return rv;
129  	}
130  	
131  	void
132  	wait_child(struct booth_config *conf, int sig)
133  	{
134  		int i, status;
135  		struct ticket_config *tk;
136  	
137  		/* use waitpid(2) and not wait(2) in order not to interfere
138  		 * with popen(2)/pclose(2) and system(2) used in pacemaker.c
139  		 */
140  		FOREACH_TICKET(conf, i, tk) {
141  			if (tk_test.path && tk_test.pid > 0 &&
142  					(tk_test.progstate == EXTPROG_RUNNING ||
143  					tk_test.progstate == EXTPROG_IGNORE) &&
144  					waitpid(tk_test.pid, &status, WNOHANG) == tk_test.pid) {
145  				if (tk_test.progstate == EXTPROG_IGNORE) {
146  					/* not interested in the outcome */
147  					reset_test_state(tk);
148  				} else {
149  					tk_test.status = status;
150  					set_progstate(tk, EXTPROG_EXITED);
151  				}
152  			}
153  		}
154  	}
155  	
156  	/* the parent may want to have us stop processing scripts, say
157  	 * when the ticket gets revoked
158  	 */
159  	static void
160  	ignore_rest(int sig)
161  	{
162  		signal(SIGTERM, SIG_IGN);
163  		ignore_status = 1;
164  		if (curr_pid > 0) {
165  			(void)kill(curr_pid, SIGTERM);
166  		}
167  	}
168  	
169  	void
170  	ext_prog_timeout(struct ticket_config *tk)
171  	{
172  		tk_log_warn("handler timed out");
173  	}
174  	
175  	int
176  	is_ext_prog_running(struct ticket_config *tk)
177  	{
178  		if (!tk_test.path)
179  			return 0;
180  		return (tk_test.pid > 0 && tk_test.progstate == EXTPROG_RUNNING);
181  	}
182  	
183  	void
184  	ignore_ext_test(struct ticket_config *tk)
185  	{
186  		if (is_ext_prog_running(tk)) {
187  			(void)kill(tk_test.pid, SIGTERM);
188  			set_progstate(tk, EXTPROG_IGNORE);
189  		} else if (tk_test.progstate == EXTPROG_EXITED) {
190  			/* external prog exited, but the status not yet examined;
191  			 * we're not interested in checking the status anymore */
192  			reset_test_state(tk);
193  		}
194  	}
195  	
196  	static void
197  	process_ext_dir(const struct booth_config *conf, struct ticket_config *tk)
198  	{
199  		char prog[FILENAME_MAX+1];
200  		int rv, n_progs, i, status;
(1) Event var_decl: Declaring variable "proglist" without initializer.
Also see events: [uninit_use_in_call]
201  		struct dirent **proglist, *dp;
202  	
203  		signal(SIGTERM, (__sighandler_t)ignore_rest);
204  		signal(SIGCHLD, SIG_DFL);
205  		signal(SIGUSR1, SIG_DFL);
206  		signal(SIGINT, SIG_DFL);
(2) Event cond_true: Condition "debug_level", taking true branch.
207  		tk_log_debug("running programs in directory %s", tk_test.path);
(3) Event uninit_use_in_call: Using uninitialized value "proglist" when calling "scandir".
Also see events: [var_decl]
208  		n_progs = scandir(tk_test.path, &proglist, prog_filter, alphasort);
209  		if (n_progs == -1) {
210  			tk_log_error("%s: scandir failed (%s)", tk_test.path, strerror(errno));
211  			_exit(1);
212  		}
213  		for (i = 0; i < n_progs; i++) {
214  			if (ignore_status)
215  				break;
216  			dp = proglist[i];
217  			if (strlen(dp->d_name) + strlen(tk_test.path) + 1 > FILENAME_MAX) {
218  				tk_log_error("%s: name exceeds max length (%s)",
219  					tk_test.path, dp->d_name);
220  				_exit(1);
221  			}
222  			strcpy(prog, tk_test.path);
223  			strcat(prog, "/");
224  			strcat(prog, dp->d_name);
225  			switch(curr_pid=fork()) {
226  			case -1:
227  				log_error("fork: %s", strerror(errno));
228  				_exit(1);
229  			case 0: /* child */
230  				run_ext_prog(conf, tk, prog);
231  				break;  /* run_ext_prog effectively noreturn */
232  			default: /* parent */
233  				while (waitpid(curr_pid, &status, 0) != curr_pid)
234  					;
235  				curr_pid = 0;
236  				if (!ignore_status) {
237  					rv = test_exit_status(tk, prog, status, 1);
238  					if (rv)
239  						_exit(rv);
240  				} else {
241  					/*
242  					 * To make ignore_rest function signal safe log_info
243  					 * must be removed from signal function. Information
244  					 * about signal delivery is important so put it here.
245  					 */
246  					log_info("external programs handler caught TERM, ignoring "
247  					    "status of external test programs");
248  				}
249  			}
250  		}
251  		_exit(0);
252  	}
253  	
254  	/* run some external program
255  	 * return codes:
256  	 * RUNCMD_ERR: executing program failed (or some other failure)
257  	 * RUNCMD_MORE: program forked, results later
258  	 */
259  	int
260  	run_handler(const struct booth_config *conf, struct ticket_config *tk)
261  	{
262  		int rv = 0;
263  		pid_t pid;
264  		struct stat stbuf;
265  	
266  		if (!tk_test.path)
267  			return 0;
268  	
269  		if (stat(tk_test.path, &stbuf)) {
270  			tk_log_error("%s: stat failed (%s)", tk_test.path, strerror(errno));
271  			return RUNCMD_ERR;
272  		}
273  		tk_test.is_dir = (stbuf.st_mode & S_IFDIR);
274  	
275  		switch(pid=fork()) {
276  		case -1:
277  			log_error("fork: %s", strerror(errno));
278  			return RUNCMD_ERR;
279  		case 0: /* child */
280  			if (tk_test.is_dir) {
281  				process_ext_dir(conf, tk);
282  			} else {
283  				run_ext_prog(conf, tk, tk_test.path);
284  			}
285  		default: /* parent */
286  			tk_test.pid = pid;
287  			set_progstate(tk, EXTPROG_RUNNING);
288  			rv = RUNCMD_MORE; /* program runs */
289  		}
290  	
291  		return rv;
292  	}
293