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 set_booth_env(struct ticket_config *tk)
39   	{
40   		int rv;
41   		char expires[16];
42   	
43   		sprintf(expires, "%" PRId64, (int64_t)wall_ts(&tk->term_expires));
44   		rv = setenv("BOOTH_TICKET", tk->name, 1) ||
45   			setenv("BOOTH_LOCAL", local->addr_string, 1) ||
46   			setenv("BOOTH_CONF_NAME", booth_conf->name, 1) ||
47   			setenv("BOOTH_CONF_PATH", cl.configfile, 1) ||
48   			setenv("BOOTH_TICKET_EXPIRES", expires, 1);
49   	
50   		if (rv) {
51   			log_error("Cannot set environment: %s", strerror(errno));
52   		}
53   		return rv;
54   	}
55   	
56   	static void
57   	closefiles(void)
58   	{
59   		int fd;
60   	
61   		/* close all descriptors except stdin/out/err */
62   		for (fd = getdtablesize() - 1; fd > STDERR_FILENO; fd--) {
63   			close(fd);
64   		}
65   	}
66   	
67   	static void
68   	run_ext_prog(struct ticket_config *tk, char *prog)
69   	{
70   		if (set_booth_env(tk)) {
71   			_exit(1);
72   		}
73   		closefiles(); /* don't leak open files */
74   		tk_log_debug("running handler %s", prog);
75   		execv(prog, tk_test.argv);
76   		tk_log_error("%s: execv failed (%s)", prog, strerror(errno));
77   		_exit(1);
78   	}
79   	
80   	static int
81   	prog_filter(const struct dirent *dp)
82   	{
83   		return (*dp->d_name != '.');
84   	}
85   	
86   	static pid_t curr_pid;
87   	static int ignore_status;
88   	
89   	static int
90   	test_exit_status(struct ticket_config *tk, char *prog, int status, int log_msg)
91   	{
92   		int rv = -1;
93   	
94   		if (WIFEXITED(status)) {
95   			rv = WEXITSTATUS(status);
96   		} else if (WIFSIGNALED(status)) {
97   			rv = 128 + WTERMSIG(status);
98   		}
99   		if (rv) {
100  			if (log_msg) {
101  				tk_log_warn("handler \"%s\" failed: %s",
102  					prog, interpret_rv(status));
103  				tk_log_warn("we are not allowed to acquire ticket");
104  			}
105  		} else {
106  			tk_log_debug("handler \"%s\" exited with success",
107  				prog);
108  		}
109  		return rv;
110  	}
111  	
112  	static void
113  	reset_test_state(struct ticket_config *tk)
114  	{
115  		tk_test.pid = 0;
116  		set_progstate(tk, EXTPROG_IDLE);
117  	}
118  	
119  	int tk_test_exit_status(struct ticket_config *tk)
120  	{
121  		int rv;
122  	
123  		rv = test_exit_status(tk, tk_test.path, tk_test.status, !tk_test.is_dir);
124  		reset_test_state(tk);
125  		return rv;
126  	}
127  	
128  	void wait_child(int sig)
129  	{
130  		int i, status;
131  		struct ticket_config *tk;
132  	
133  		/* use waitpid(2) and not wait(2) in order not to interfere
134  		 * with popen(2)/pclose(2) and system(2) used in pacemaker.c
135  		 */
136  		_FOREACH_TICKET(i, tk) {
137  			if (tk_test.path && tk_test.pid > 0 &&
138  					(tk_test.progstate == EXTPROG_RUNNING ||
139  					tk_test.progstate == EXTPROG_IGNORE) &&
140  					waitpid(tk_test.pid, &status, WNOHANG) == tk_test.pid) {
141  				if (tk_test.progstate == EXTPROG_IGNORE) {
142  					/* not interested in the outcome */
143  					reset_test_state(tk);
144  				} else {
145  					tk_test.status = status;
146  					set_progstate(tk, EXTPROG_EXITED);
147  				}
148  			}
149  		}
150  	}
151  	
152  	/* the parent may want to have us stop processing scripts, say
153  	 * when the ticket gets revoked
154  	 */
155  	static void ignore_rest(int sig)
156  	{
157  		signal(SIGTERM, SIG_IGN);
158  		ignore_status = 1;
159  		if (curr_pid > 0) {
160  			(void)kill(curr_pid, SIGTERM);
161  		}
162  	}
163  	
164  	void ext_prog_timeout(struct ticket_config *tk)
165  	{
166  		tk_log_warn("handler timed out");
167  	}
168  	
169  	int is_ext_prog_running(struct ticket_config *tk)
170  	{
171  		if (!tk_test.path)
172  			return 0;
173  		return (tk_test.pid > 0 && tk_test.progstate == EXTPROG_RUNNING);
174  	}
175  	
176  	void ignore_ext_test(struct ticket_config *tk)
177  	{
178  		if (is_ext_prog_running(tk)) {
179  			(void)kill(tk_test.pid, SIGTERM);
180  			set_progstate(tk, EXTPROG_IGNORE);
181  		} else if (tk_test.progstate == EXTPROG_EXITED) {
182  			/* external prog exited, but the status not yet examined;
183  			 * we're not interested in checking the status anymore */
184  			reset_test_state(tk);
185  		}
186  	}
187  	
188  	static void
189  	process_ext_dir(struct ticket_config *tk)
190  	{
191  		char prog[FILENAME_MAX+1];
192  		int rv, n_progs, i, status;
(1) Event var_decl: Declaring variable "proglist" without initializer.
Also see events: [uninit_use_in_call]
193  		struct dirent **proglist, *dp;
194  	
195  		signal(SIGTERM, (__sighandler_t)ignore_rest);
196  		signal(SIGCHLD, SIG_DFL);
197  		signal(SIGUSR1, SIG_DFL);
198  		signal(SIGINT, SIG_DFL);
(2) Event cond_true: Condition "debug_level", taking true branch.
199  		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]
200  		n_progs = scandir(tk_test.path, &proglist, prog_filter, alphasort);
201  		if (n_progs == -1) {
202  			tk_log_error("%s: scandir failed (%s)", tk_test.path, strerror(errno));
203  			_exit(1);
204  		}
205  		for (i = 0; i < n_progs; i++) {
206  			if (ignore_status)
207  				break;
208  			dp = proglist[i];
209  			if (strlen(dp->d_name) + strlen(tk_test.path) + 1 > FILENAME_MAX) {
210  				tk_log_error("%s: name exceeds max length (%s)",
211  					tk_test.path, dp->d_name);
212  				_exit(1);
213  			}
214  			strcpy(prog, tk_test.path);
215  			strcat(prog, "/");
216  			strcat(prog, dp->d_name);
217  			switch(curr_pid=fork()) {
218  			case -1:
219  				log_error("fork: %s", strerror(errno));
220  				_exit(1);
221  			case 0: /* child */
222  				run_ext_prog(tk, prog);
223  				break;  /* run_ext_prog effectively noreturn */
224  			default: /* parent */
225  				while (waitpid(curr_pid, &status, 0) != curr_pid)
226  					;
227  				curr_pid = 0;
228  				if (!ignore_status) {
229  					rv = test_exit_status(tk, prog, status, 1);
230  					if (rv)
231  						_exit(rv);
232  				} else {
233  					/*
234  					 * To make ignore_rest function signal safe log_info
235  					 * must be removed from signal function. Information
236  					 * about signal delivery is important so put it here.
237  					 */
238  					log_info("external programs handler caught TERM, ignoring "
239  					    "status of external test programs");
240  				}
241  			}
242  		}
243  		_exit(0);
244  	}
245  	
246  	/* run some external program
247  	 * return codes:
248  	 * RUNCMD_ERR: executing program failed (or some other failure)
249  	 * RUNCMD_MORE: program forked, results later
250  	 */
251  	int run_handler(struct ticket_config *tk)
252  	{
253  		int rv = 0;
254  		pid_t pid;
255  		struct stat stbuf;
256  	
257  		if (!tk_test.path)
258  			return 0;
259  	
260  		if (stat(tk_test.path, &stbuf)) {
261  			tk_log_error("%s: stat failed (%s)", tk_test.path, strerror(errno));
262  			return RUNCMD_ERR;
263  		}
264  		tk_test.is_dir = (stbuf.st_mode & S_IFDIR);
265  	
266  		switch(pid=fork()) {
267  		case -1:
268  			log_error("fork: %s", strerror(errno));
269  			return RUNCMD_ERR;
270  		case 0: /* child */
271  			if (tk_test.is_dir) {
272  				process_ext_dir(tk);
273  			} else {
274  				run_ext_prog(tk, tk_test.path);
275  			}
276  		default: /* parent */
277  			tk_test.pid = pid;
278  			set_progstate(tk, EXTPROG_RUNNING);
279  			rv = RUNCMD_MORE; /* program runs */
280  		}
281  	
282  		return rv;
283  	}
284