Branch data Line data Source code
1 : : /*
2 : : *****************************************************************************
3 : : *
4 : : * File: extcmd.c
5 : : *
6 : : * Purpose: Routines for executing and processing external commands.
7 : : *
8 : : * Fwknop is developed primarily by the people listed in the file 'AUTHORS'.
9 : : * Copyright (C) 2009-2014 fwknop developers and contributors. For a full
10 : : * list of contributors, see the file 'CREDITS'.
11 : : *
12 : : * License (GNU General Public License):
13 : : *
14 : : * This program is free software; you can redistribute it and/or
15 : : * modify it under the terms of the GNU General Public License
16 : : * as published by the Free Software Foundation; either version 2
17 : : * of the License, or (at your option) any later version.
18 : : *
19 : : * This program is distributed in the hope that it will be useful,
20 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 : : * GNU General Public License for more details.
23 : : *
24 : : * You should have received a copy of the GNU General Public License
25 : : * along with this program; if not, write to the Free Software
26 : : * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
27 : : * USA
28 : : *
29 : : *****************************************************************************
30 : : */
31 : : #include "fwknopd_common.h"
32 : : #include "extcmd.h"
33 : : #include "log_msg.h"
34 : : #include "utils.h"
35 : :
36 : : /*
37 : : #include <stdio.h>
38 : : #include <stdlib.h>
39 : : #include <unistd.h>
40 : : #include <string.h>
41 : : #include <fcntl.h>
42 : : #include <sys/types.h>
43 : : #include <sys/select.h>
44 : : */
45 : : #include <errno.h>
46 : : #include <signal.h>
47 : :
48 : : #if HAVE_SYS_WAIT_H
49 : : #include <sys/wait.h>
50 : : #endif
51 : :
52 : : /*
53 : : static sig_atomic_t got_sigalrm;
54 : : */
55 : :
56 : : /* Takes a file descriptor and makes it non-blocking.
57 : : static int
58 : : set_nonblock(int fd)
59 : : {
60 : : int val;
61 : :
62 : : if((val = fcntl(fd, F_GETFL, 0)) < 0)
63 : : {
64 : : perror("fcntl F_GETFL error:");
65 : : return(-1);
66 : : }
67 : :
68 : : val |= O_NONBLOCK;
69 : :
70 : : if(fcntl(fd, F_SETFL, val) < 0)
71 : : {
72 : : perror("fcntl F_SETFL error setting O_NONBLOCK");
73 : : return(-1);
74 : : }
75 : :
76 : : return(0);
77 : : }
78 : :
79 : : static void
80 : : alarm_handler(int sig)
81 : : {
82 : : got_sigalrm = 1;
83 : : }
84 : : */
85 : :
86 : : /* Run en external command returning exit status, and optionally filling
87 : : * provided buffer with STDOUT output up to the size provided.
88 : : *
89 : : * Note: XXX: We are not using the timeout parameter at present. We still need
90 : : * to implement a reliable timeout mechanism.
91 : : */
92 : : static int
93 : 8314 : _run_extcmd(uid_t user_uid, const char *cmd, char *so_buf, const size_t so_buf_sz, const int timeout)
94 : : {
95 : : FILE *ipt;
96 : 8314 : int retval = 0;
97 : 8314 : char so_read_buf[IO_READ_BUF_LEN] = {0};
98 : : pid_t pid;
99 : : int res;
100 : :
101 [ + + ]: 8314 : if(so_buf == NULL)
102 : : {
103 : :
104 : : /* Since we do not have to capture output, we will fork here (which we
105 : : * would have to do anyway if we are running as another user as well).
106 : : */
107 : 2 : pid = fork();
108 [ - + ]: 4 : if(pid == -1)
109 : : {
110 : 0 : log_msg(LOG_ERR, "run_extcmd: fork failed: %s", strerror(errno));
111 : : return(EXTCMD_FORK_ERROR);
112 : : }
113 [ + + ]: 4 : else if (pid == 0)
114 : : {
115 : : /* We are the child */
116 : : /* If user is not null, then we setuid to that user before running the
117 : : * command.
118 : : */
119 [ - + ]: 2 : if(user_uid > 0)
120 : : {
121 [ # # ]: 0 : if(setuid(user_uid) < 0)
122 : : {
123 : 0 : exit(EXTCMD_SETUID_ERROR);
124 : : }
125 : : }
126 : 2 : res = system(cmd);
127 : 2 : exit(WEXITSTATUS(res));
128 : : }
129 : :
130 : : /* Retval is forced to 0 as we don't care about the exit status of
131 : : * the child (for now)>
132 : : */
133 : : retval = 0;
134 : : }
135 : : else
136 : : {
137 : : /* Looking for output use popen and fill the buffer to its limit.
138 : : */
139 : 8312 : ipt = popen(cmd, "r");
140 : :
141 [ - + ]: 8312 : if(ipt == NULL)
142 : : {
143 : 0 : log_msg(LOG_ERR, "Got popen error %i: %s", errno, strerror(errno));
144 : 0 : retval = -1;
145 : : }
146 : : else
147 : : {
148 : : memset(so_buf, 0x0, so_buf_sz);
149 : :
150 [ + + ]: 15080 : while((fgets(so_read_buf, IO_READ_BUF_LEN, ipt)) != NULL)
151 : : {
152 : 6770 : strlcat(so_buf, so_read_buf, so_buf_sz);
153 : :
154 [ + + ]: 6770 : if(strlen(so_buf) >= so_buf_sz-1)
155 : : break;
156 : : }
157 : :
158 : 8314 : pclose(ipt);
159 : : }
160 : : }
161 : :
162 : : return(retval);
163 : : }
164 : :
165 : :
166 : : #if 0 /* --DSS the original method that did not work on some systems */
167 : :
168 : : /* Create the pipes we will use for getting stdout and stderr
169 : : * from the child process.
170 : : */
171 : : if(pipe(so) != 0)
172 : : return(EXTCMD_PIPE_ERROR);
173 : :
174 : : if(pipe(se) != 0)
175 : : return(EXTCMD_PIPE_ERROR);
176 : :
177 : : /* Fork off a child process to run the command and provide its outputs.
178 : : */
179 : : pid = fork();
180 : : if(pid == -1)
181 : : {
182 : : return(EXTCMD_FORK_ERROR);
183 : : }
184 : : else if (pid == 0)
185 : : {
186 : : /* We are the child, so we dup stdout and stderr to our respective
187 : : * write-end of the pipes, close stdin and the read-end of the pipes
188 : : * (since we don't need them here). Then use system() to run the
189 : : * command and exit with the exit status of that command so we can
190 : : * grab it from the waitpid call in the parent.
191 : : */
192 : : close(fileno(stdin));
193 : : dup2(so[1], fileno(stdout));
194 : : dup2(se[1], fileno(stderr));
195 : : close(so[0]);
196 : : close(se[0]);
197 : :
198 : : /* If user is not null, then we setuid to that user before running the
199 : : * command.
200 : : */
201 : : if(user_uid > 0)
202 : : {
203 : : if(setuid(user_uid) < 0)
204 : : {
205 : : exit(EXTCMD_SETUID_ERROR);
206 : : }
207 : : }
208 : :
209 : : /* --DSS XXX: Would it be more efficient to use one of the exec()
210 : : * calls (i.e. 'return(execvp(ext_cmd, &argv[1]));')?
211 : : * For now, we use system() and exit with the external
212 : : * command exit status.
213 : : */
214 : : exit(WEXITSTATUS(system(cmd)));
215 : : }
216 : :
217 : : /* Parent from here */
218 : :
219 : : /* Give the exit status an initial value of -1.
220 : : */
221 : : *status = -1;
222 : :
223 : : /* Close the write-end of the pipes (we are only reading).
224 : : */
225 : : close(so[1]);
226 : : close(se[1]);
227 : :
228 : : /* Set our pipes to non-blocking
229 : : */
230 : : set_nonblock(so[0]);
231 : : set_nonblock(se[0]);
232 : :
233 : : tv.tv_sec = EXTCMD_DEF_TIMEOUT;
234 : : tv.tv_usec = 0;
235 : :
236 : : /* Initialize and setup our file descriptor sets for select.
237 : : */
238 : : FD_ZERO(&rfds);
239 : : FD_ZERO(&efds);
240 : : FD_SET(so[0], &rfds);
241 : : FD_SET(se[0], &rfds);
242 : : FD_SET(so[0], &efds);
243 : : FD_SET(se[0], &efds);
244 : :
245 : : /* Start with fully clear buffers.
246 : : */
247 : : memset(so_buf, 0x0, so_buf_sz);
248 : : memset(se_buf, 0x0, se_buf_sz);
249 : :
250 : : /* Read both stdout and stderr piped from the child until we get eof,
251 : : * fill the buffers, or error out.
252 : : */
253 : : while(so_buf_remaining > 0 || se_buf_remaining > 0)
254 : : {
255 : : selval = select(8, &rfds, NULL, &efds, &tv);
256 : :
257 : : if(selval == -1)
258 : : {
259 : : /* Select error - so kill the child and bail.
260 : : */
261 : : kill(pid, SIGTERM);
262 : : retval |= EXTCMD_SELECT_ERROR;
263 : : break;
264 : : }
265 : :
266 : : if(selval == 0)
267 : : {
268 : : /* Timeout - so kill the child and bail
269 : : */
270 : : kill(pid, SIGTERM);
271 : : retval |= EXTCMD_EXECUTION_TIMEOUT;
272 : : break;
273 : : }
274 : :
275 : : /* The stdout pipe...
276 : : */
277 : : bytes_read = read(so[0], so_read_buf, IO_READ_BUF_LEN);
278 : : if(so_buf_remaining > 0)
279 : : {
280 : : if(bytes_read > 0)
281 : : {
282 : : /* We have data, so process it...
283 : : */
284 : : if(bytes_read > so_buf_remaining)
285 : : {
286 : : bytes_read = so_buf_remaining;
287 : : retval |= EXTCMD_SUCCESS_PARTIAL_STDOUT;
288 : : }
289 : :
290 : : memcpy(so_buf, so_read_buf, bytes_read);
291 : : so_buf += bytes_read;
292 : : so_buf_remaining -= bytes_read;
293 : : }
294 : : else if(bytes_read < 0)
295 : : {
296 : : /* Anything other than EAGAIN or EWOULDBLOCK is conisdered
297 : : * error enough to bail. We are done here so we force the
298 : : * buf_remaining value to 0.
299 : : */
300 : : if(errno != EAGAIN && errno != EWOULDBLOCK)
301 : : {
302 : : retval |= EXTCMD_STDOUT_READ_ERROR;
303 : : so_buf_remaining = 0;
304 : : }
305 : : }
306 : : else
307 : : {
308 : : /* Bytes read was 0 which indicate end of file. So we are
309 : : * done.
310 : : */
311 : : so_buf_remaining = 0;
312 : : }
313 : : }
314 : : else
315 : : break;
316 : :
317 : : /* The stderr pipe...
318 : : */
319 : : bytes_read = read(se[0], se_read_buf, IO_READ_BUF_LEN);
320 : : if(se_buf_remaining > 0)
321 : : {
322 : : if(bytes_read > 0)
323 : : {
324 : : /* We have data, so process it...
325 : : */
326 : : if(bytes_read > se_buf_remaining)
327 : : {
328 : : bytes_read = se_buf_remaining;
329 : : retval |= EXTCMD_SUCCESS_PARTIAL_STDERR;
330 : : }
331 : :
332 : : memcpy(se_buf, se_read_buf, bytes_read);
333 : : se_buf += bytes_read;
334 : : se_buf_remaining -= bytes_read;
335 : : }
336 : : else if(bytes_read < 0)
337 : : {
338 : : /* Anything other than EAGAIN or EWOULDBLOCK is conisdered
339 : : * error enough to bail. We are done here so we force the
340 : : * buf_remaining value to 0.
341 : : */
342 : : if(errno != EAGAIN && errno != EWOULDBLOCK)
343 : : {
344 : : retval |= EXTCMD_STDERR_READ_ERROR;
345 : : se_buf_remaining = 0;
346 : : }
347 : : }
348 : : else
349 : : {
350 : : /* Bytes read was 0 which indicate end of file. So we are
351 : : * done.
352 : : */
353 : : se_buf_remaining = 0;
354 : : }
355 : : }
356 : : else
357 : : break;
358 : : }
359 : :
360 : : close(so[0]);
361 : : close(se[0]);
362 : :
363 : : /* Wait for the external command to finish and capture its exit status.
364 : : */
365 : : waitpid(pid, status, 0);
366 : :
367 : : if(*status != 0)
368 : : retval != EXTCMD_EXECUTION_ERROR;
369 : :
370 : : /* Return the our status of this operation command.
371 : : */
372 : : return(retval);
373 : : }
374 : : #endif
375 : :
376 : : /* Run an external command. This is wrapper around _run_extcmd()
377 : : */
378 : : int
379 : 8314 : run_extcmd(const char *cmd, char *so_buf, const size_t so_buf_sz, const int timeout)
380 : : {
381 : 8314 : return _run_extcmd(0, cmd, so_buf, so_buf_sz, timeout);
382 : : }
383 : :
384 : : /* Run an external command as the specified user. This is wrapper around _run_extcmd()
385 : : */
386 : : int
387 : 0 : run_extcmd_as(uid_t user_uid, const char *cmd, char *so_buf, const size_t so_buf_sz, const int timeout)
388 : : {
389 : 0 : return _run_extcmd(user_uid, cmd, so_buf, so_buf_sz, timeout);
390 : : }
391 : :
|