/src/freeradius-server/src/lib/server/exec_legacy.c
Line | Count | Source |
1 | | /* |
2 | | * This program is free software; you can redistribute it and/or modify |
3 | | * it under the terms of the GNU General Public License as published by |
4 | | * the Free Software Foundation; either version 2 of the License, or |
5 | | * (at your option) any later version. |
6 | | * |
7 | | * This program is distributed in the hope that it will be useful, |
8 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
9 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
10 | | * GNU General Public License for more details. |
11 | | * |
12 | | * You should have received a copy of the GNU General Public License |
13 | | * along with this program; if not, write to the Free Software |
14 | | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA |
15 | | */ |
16 | | |
17 | | /** |
18 | | * $Id: cea33ab19b240b4d79c45870302e6127a4247732 $ |
19 | | * |
20 | | * @file src/lib/server/exec_legacy.c |
21 | | * @brief Execute external programs. |
22 | | * |
23 | | * @copyright 2000-2004,2006 The FreeRADIUS server project |
24 | | */ |
25 | | RCSID("$Id: cea33ab19b240b4d79c45870302e6127a4247732 $") |
26 | | |
27 | | #include <freeradius-devel/util/debug.h> |
28 | | #include <freeradius-devel/server/request.h> |
29 | | #include <freeradius-devel/server/util.h> |
30 | | #include <freeradius-devel/server/exec_legacy.h> |
31 | | #include <freeradius-devel/server/exec_priv.h> |
32 | | |
33 | 0 | #define MAX_ARGV (256) |
34 | | |
35 | | static void exec_pair_to_env_legacy(request_t *request, fr_pair_list_t *input_pairs, char **envp, |
36 | | size_t envlen, bool shell_escape) |
37 | 0 | { |
38 | 0 | char *p; |
39 | 0 | size_t i; |
40 | 0 | fr_dcursor_t cursor; |
41 | 0 | fr_dict_attr_t const *da; |
42 | 0 | fr_pair_t *vp; |
43 | 0 | char buffer[1024]; |
44 | | |
45 | | /* |
46 | | * Set up the environment variables in the |
47 | | * parent, so we don't call libc functions that |
48 | | * hold mutexes. They might be locked when we fork, |
49 | | * and will remain locked in the child. |
50 | | */ |
51 | 0 | for (vp = fr_pair_list_head(input_pairs), i = 0; |
52 | 0 | vp && (i < envlen - 1); |
53 | 0 | vp = fr_pair_list_next(input_pairs, vp)) { |
54 | 0 | size_t n; |
55 | | |
56 | | /* |
57 | | * Hmm... maybe we shouldn't pass the |
58 | | * user's password in an environment |
59 | | * variable... |
60 | | */ |
61 | 0 | snprintf(buffer, sizeof(buffer), "%s=", vp->da->name); |
62 | 0 | if (shell_escape) { |
63 | 0 | for (p = buffer; *p != '='; p++) { |
64 | 0 | if (*p == '-') { |
65 | 0 | *p = '_'; |
66 | 0 | } else if (isalpha((uint8_t) *p)) { |
67 | 0 | *p = toupper((uint8_t) *p); |
68 | 0 | } |
69 | 0 | } |
70 | 0 | } |
71 | |
|
72 | 0 | n = strlen(buffer); |
73 | 0 | fr_pair_print_value_quoted(&FR_SBUFF_OUT(buffer + n, sizeof(buffer) - n), vp, |
74 | 0 | shell_escape ? T_DOUBLE_QUOTED_STRING : T_BARE_WORD); |
75 | |
|
76 | 0 | DEBUG3("export %s", buffer); |
77 | 0 | envp[i++] = talloc_strdup(envp, buffer); |
78 | 0 | } |
79 | |
|
80 | 0 | if (request) { |
81 | 0 | da = fr_dict_attr_child_by_num(fr_dict_root(fr_dict_internal()), FR_EXEC_EXPORT); |
82 | 0 | if (da) { |
83 | 0 | for (vp = fr_pair_dcursor_by_da_init(&cursor, &request->control_pairs, da); |
84 | 0 | vp && (i < (envlen - 1)); |
85 | 0 | vp = fr_dcursor_next(&cursor)) { |
86 | 0 | DEBUG3("export %pV", &vp->data); |
87 | 0 | memcpy(&envp[i++], &vp->vp_strvalue, sizeof(*envp)); |
88 | 0 | } |
89 | | |
90 | | /* |
91 | | * NULL terminate for execve |
92 | | */ |
93 | 0 | envp[i] = NULL; |
94 | 0 | } |
95 | 0 | } |
96 | 0 | } |
97 | | |
98 | | |
99 | | /* |
100 | | * Child process. |
101 | | * |
102 | | * We try to be fail-safe here. So if ANYTHING |
103 | | * goes wrong, we exit with status 1. |
104 | | */ |
105 | | static NEVER_RETURNS void exec_child_legacy(request_t *request, char **argv, char **envp, |
106 | | bool exec_wait, |
107 | | int stdin_pipe[static 2], int stdout_pipe[static 2], int stderr_pipe[static 2]) |
108 | 0 | { |
109 | 0 | int devnull; |
110 | | |
111 | | /* |
112 | | * Open STDIN to /dev/null |
113 | | */ |
114 | 0 | devnull = open("/dev/null", O_RDWR); |
115 | 0 | if (devnull < 0) { |
116 | 0 | fprintf(stderr, "Failed opening /dev/null: %s\n", fr_syserror(errno)); |
117 | | |
118 | | /* |
119 | | * Where the status code is interpreted as a module rcode |
120 | | * one is subtracted from it, to allow 0 to equal success |
121 | | * |
122 | | * 2 is RLM_MODULE_FAIL + 1 |
123 | | */ |
124 | 0 | exit(2); |
125 | 0 | } |
126 | | |
127 | | /* |
128 | | * Only massage the pipe handles if the parent |
129 | | * has created them. |
130 | | */ |
131 | 0 | if (exec_wait) { |
132 | 0 | if (stdin_pipe[1] >= 0) { |
133 | 0 | close(stdin_pipe[1]); |
134 | 0 | dup2(stdin_pipe[0], STDIN_FILENO); |
135 | 0 | } else { |
136 | 0 | dup2(devnull, STDIN_FILENO); |
137 | 0 | } |
138 | |
|
139 | 0 | if (stdout_pipe[1] >= 0) { |
140 | 0 | close(stdout_pipe[0]); |
141 | 0 | dup2(stdout_pipe[1], STDOUT_FILENO); |
142 | 0 | } else { |
143 | 0 | dup2(devnull, STDOUT_FILENO); |
144 | 0 | } |
145 | |
|
146 | 0 | if (stderr_pipe[1] >= 0) { |
147 | 0 | close(stderr_pipe[0]); |
148 | 0 | dup2(stderr_pipe[1], STDERR_FILENO); |
149 | 0 | } else { |
150 | 0 | dup2(devnull, STDERR_FILENO); |
151 | 0 | } |
152 | 0 | } else { /* no pipe, STDOUT should be /dev/null */ |
153 | 0 | dup2(devnull, STDIN_FILENO); |
154 | 0 | dup2(devnull, STDOUT_FILENO); |
155 | | |
156 | | /* |
157 | | * If we're not debugging, then we can't do |
158 | | * anything with the error messages, so we throw |
159 | | * them away. |
160 | | * |
161 | | * If we are debugging, then we want the error |
162 | | * messages to go to the STDERR of the server. |
163 | | */ |
164 | 0 | if (!request || !RDEBUG_ENABLED) dup2(devnull, STDERR_FILENO); |
165 | 0 | } |
166 | |
|
167 | 0 | close(devnull); |
168 | | |
169 | | /* |
170 | | * The server may have MANY FD's open. We don't |
171 | | * want to leave dangling FD's for the child process |
172 | | * to play funky games with, so we close them. |
173 | | */ |
174 | 0 | fr_closefrom(STDERR_FILENO + 1); |
175 | | |
176 | | /* |
177 | | * Disarm the thread local destructors |
178 | | * |
179 | | * It's not safe to free memory between fork and exec. |
180 | | */ |
181 | 0 | fr_atexit_thread_local_disarm_all(); |
182 | | |
183 | | /* |
184 | | * I swear the signature for execve is wrong and should |
185 | | * take 'char const * const argv[]'. |
186 | | * |
187 | | * Note: execve(), unlike system(), treats all the space |
188 | | * delimited arguments as literals, so there's no need |
189 | | * to perform additional escaping. |
190 | | */ |
191 | 0 | execve(argv[0], argv, envp); |
192 | 0 | printf("Failed to execute \"%s\": %s", argv[0], fr_syserror(errno)); /* fork output will be captured */ |
193 | | |
194 | | /* |
195 | | * Where the status code is interpreted as a module rcode |
196 | | * one is subtracted from it, to allow 0 to equal success |
197 | | * |
198 | | * 2 is RLM_MODULE_FAIL + 1 |
199 | | */ |
200 | 0 | exit(2); |
201 | 0 | } |
202 | | |
203 | | |
204 | | /** Start a process |
205 | | * |
206 | | * @param[out] stdin_fd pointer to int, receives the stdin file |
207 | | * descriptor. Set to NULL and the child |
208 | | * will have /dev/null on stdin. |
209 | | * @param[out] stdout_fd pointer to int, receives the stdout file |
210 | | * descriptor. Set to NULL and the child |
211 | | * will have /dev/null on stdout. |
212 | | * @param[out] stderr_fd pointer to int, receives the stderr file |
213 | | * descriptor. Set to NULL and the child |
214 | | * will have /dev/null on stderr. |
215 | | * @param[in] cmd Command to execute. This is parsed into argv[] |
216 | | * parts, then each individual argv part is |
217 | | * xlat'ed. |
218 | | * @param[in] request Current request |
219 | | * @param[in] exec_wait set to true to read from or write to child. |
220 | | * @param[in] input_pairs list of value pairs - these will be put into |
221 | | * the environment variables of the child. |
222 | | * @param[in] shell_escape values before passing them as arguments. |
223 | | * @return |
224 | | * - PID of the child process. |
225 | | * - -1 on failure. |
226 | | */ |
227 | | pid_t radius_start_program_legacy(int *stdin_fd, int *stdout_fd, int *stderr_fd, |
228 | | char const *cmd, request_t *request, bool exec_wait, |
229 | | fr_pair_list_t *input_pairs, bool shell_escape) |
230 | 0 | { |
231 | 0 | int stdin_pipe[2] = {-1, -1}; |
232 | 0 | int stdout_pipe[2] = {-1, -1}; |
233 | 0 | int stderr_pipe[2] = {-1, -1}; |
234 | 0 | pid_t pid; |
235 | 0 | int argc; |
236 | 0 | int i; |
237 | 0 | char const **argv_p; |
238 | 0 | char *argv[MAX_ARGV], **argv_start = argv; |
239 | 0 | char argv_buf[4096]; |
240 | 0 | #define MAX_ENVP 1024 |
241 | 0 | char **envp; |
242 | | |
243 | | /* |
244 | | * Stupid array decomposition... |
245 | | * |
246 | | * If we do memcpy(&argv_p, &argv, sizeof(argv_p)) src ends up being a char ** |
247 | | * pointing to the value of the first element. |
248 | | */ |
249 | 0 | memcpy(&argv_p, &argv_start, sizeof(argv_p)); |
250 | 0 | argc = rad_expand_xlat(request, cmd, MAX_ARGV, argv_p, true, sizeof(argv_buf), argv_buf); |
251 | 0 | if (argc <= 0) { |
252 | 0 | ROPTIONAL(RPEDEBUG, PERROR, "Invalid command '%s'", cmd); |
253 | 0 | return -1; |
254 | 0 | } |
255 | | |
256 | 0 | if (DEBUG_ENABLED3) { |
257 | 0 | for (i = 0; i < argc; i++) DEBUG3("arg[%d] %s", i, argv[i]); |
258 | 0 | } |
259 | | |
260 | | /* |
261 | | * Open a pipe for child/parent communication, if necessary. |
262 | | */ |
263 | 0 | if (exec_wait) { |
264 | 0 | if (stdin_fd) { |
265 | 0 | if (pipe(stdin_pipe) != 0) { |
266 | 0 | ERROR("Couldn't open pipe to child: %s", fr_syserror(errno)); |
267 | 0 | return -1; |
268 | 0 | } |
269 | 0 | } |
270 | 0 | if (stdout_fd) { |
271 | 0 | if (pipe(stdout_pipe) != 0) { |
272 | 0 | ERROR("Couldn't open pipe from child: %s", fr_syserror(errno)); |
273 | | /* safe because these either need closing or are == -1 */ |
274 | 0 | error: |
275 | 0 | close(stdin_pipe[0]); |
276 | 0 | close(stdin_pipe[1]); |
277 | 0 | close(stdout_pipe[0]); |
278 | 0 | close(stdout_pipe[1]); |
279 | 0 | close(stderr_pipe[0]); |
280 | 0 | close(stderr_pipe[1]); |
281 | 0 | return -1; |
282 | 0 | } |
283 | 0 | } |
284 | 0 | if (stderr_fd) { |
285 | 0 | if (pipe(stderr_pipe) != 0) { |
286 | 0 | ERROR("Couldn't open pipe from child: %s", fr_syserror(errno)); |
287 | |
|
288 | 0 | goto error; |
289 | 0 | } |
290 | 0 | } |
291 | 0 | } |
292 | | |
293 | 0 | MEM(envp = talloc_zero_array(request, char *, MAX_ENVP)); |
294 | 0 | envp[0] = NULL; |
295 | 0 | if (input_pairs) exec_pair_to_env_legacy(request, input_pairs, envp, MAX_ENVP, shell_escape); |
296 | |
|
297 | 0 | pid = fork(); |
298 | 0 | if (pid == 0) { |
299 | 0 | exec_child_legacy(request, argv, envp, exec_wait, stdin_pipe, stdout_pipe, stderr_pipe); |
300 | 0 | } |
301 | | |
302 | | /* |
303 | | * Free child environment variables |
304 | | */ |
305 | 0 | talloc_free(envp); |
306 | | |
307 | | /* |
308 | | * Parent process. |
309 | | */ |
310 | 0 | if (pid < 0) { |
311 | 0 | ERROR("Couldn't fork %s: %s", argv[0], fr_syserror(errno)); |
312 | 0 | if (exec_wait) goto error; |
313 | 0 | } |
314 | | |
315 | | /* |
316 | | * We're done. Do any necessary cleanups. |
317 | | */ |
318 | 0 | if (exec_wait) { |
319 | | /* |
320 | | * Close the ends of the pipe(s) the child is using |
321 | | * return the ends of the pipe(s) our caller wants |
322 | | * |
323 | | */ |
324 | 0 | if (stdin_fd) { |
325 | 0 | *stdin_fd = stdin_pipe[1]; |
326 | 0 | close(stdin_pipe[0]); |
327 | 0 | } |
328 | 0 | if (stdout_fd) { |
329 | 0 | *stdout_fd = stdout_pipe[0]; |
330 | 0 | close(stdout_pipe[1]); |
331 | 0 | } |
332 | 0 | if (stderr_fd) { |
333 | 0 | *stderr_fd = stderr_pipe[0]; |
334 | 0 | close(stderr_pipe[1]); |
335 | 0 | } |
336 | 0 | } else { |
337 | 0 | fr_event_list_t *el = unlang_interpret_event_list(request); |
338 | |
|
339 | 0 | (void) fr_event_pid_wait(el, el, NULL, pid, NULL, NULL); |
340 | 0 | } |
341 | |
|
342 | 0 | return pid; |
343 | 0 | } |
344 | | |
345 | | |
346 | | /** Read from the child process. |
347 | | * |
348 | | * @param fd file descriptor to read from. |
349 | | * @param pid pid of child, will be reaped if it dies. |
350 | | * @param timeout amount of time to wait, in seconds. |
351 | | * @param answer buffer to write into. |
352 | | * @param left length of buffer. |
353 | | * @return |
354 | | * - -1 on failure. |
355 | | * - Length of output. |
356 | | */ |
357 | | int radius_readfrom_program_legacy(int fd, pid_t pid, fr_time_delta_t timeout, char *answer, int left) |
358 | 0 | { |
359 | 0 | int done = 0; |
360 | 0 | int status; |
361 | 0 | fr_time_t start; |
362 | |
|
363 | 0 | (void) fr_nonblock(fd); |
364 | | |
365 | | /* |
366 | | * Minimum timeout period is one section |
367 | | */ |
368 | 0 | if (fr_time_delta_unwrap(timeout) < NSEC) timeout = fr_time_delta_from_sec(1); |
369 | | |
370 | | /* |
371 | | * Read from the pipe until we doesn't get any more or |
372 | | * until the message is full. |
373 | | */ |
374 | 0 | start = fr_time(); |
375 | 0 | while (1) { |
376 | 0 | int rcode; |
377 | 0 | fd_set fds; |
378 | 0 | fr_time_delta_t elapsed; |
379 | |
|
380 | 0 | FD_ZERO(&fds); |
381 | 0 | FD_SET(fd, &fds); |
382 | |
|
383 | 0 | elapsed = fr_time_sub(fr_time(), start); |
384 | 0 | if (fr_time_delta_gteq(elapsed, timeout)) goto too_long; |
385 | | |
386 | 0 | rcode = select(fd + 1, &fds, NULL, NULL, &fr_time_delta_to_timeval(fr_time_delta_sub(timeout, elapsed))); |
387 | 0 | if (rcode == 0) { |
388 | 0 | too_long: |
389 | 0 | DEBUG("Child PID %u is taking too much time: forcing failure and killing child.", pid); |
390 | 0 | kill(pid, SIGTERM); |
391 | 0 | close(fd); /* should give SIGPIPE to child, too */ |
392 | | |
393 | | /* |
394 | | * Clean up the child entry. |
395 | | */ |
396 | 0 | waitpid(pid, &status, 0); |
397 | 0 | return -1; |
398 | 0 | } |
399 | 0 | if (rcode < 0) { |
400 | 0 | if (errno == EINTR) continue; |
401 | 0 | break; |
402 | 0 | } |
403 | | |
404 | 0 | #ifdef O_NONBLOCK |
405 | | /* |
406 | | * Read as many bytes as possible. The kernel |
407 | | * will return the number of bytes available. |
408 | | */ |
409 | 0 | status = read(fd, answer + done, left); |
410 | | #else |
411 | | /* |
412 | | * There's at least 1 byte ready: read it. |
413 | | * This is a terrible hack for non-blocking IO. |
414 | | */ |
415 | | status = read(fd, answer + done, 1); |
416 | | #endif |
417 | | |
418 | | /* |
419 | | * Nothing more to read: stop. |
420 | | */ |
421 | 0 | if (status == 0) { |
422 | 0 | break; |
423 | 0 | } |
424 | | |
425 | | /* |
426 | | * Error: See if we have to continue. |
427 | | */ |
428 | 0 | if (status < 0) { |
429 | | /* |
430 | | * We were interrupted: continue reading. |
431 | | */ |
432 | 0 | if (errno == EINTR) { |
433 | 0 | continue; |
434 | 0 | } |
435 | | |
436 | | /* |
437 | | * There was another error. Most likely |
438 | | * The child process has finished, and |
439 | | * exited. |
440 | | */ |
441 | 0 | break; |
442 | 0 | } |
443 | | |
444 | 0 | done += status; |
445 | 0 | left -= status; |
446 | 0 | if (left <= 0) break; |
447 | 0 | } |
448 | | |
449 | | /* Strip trailing new lines */ |
450 | 0 | while ((done > 0) && (answer[done - 1] == '\n')) { |
451 | 0 | answer[--done] = '\0'; |
452 | 0 | } |
453 | |
|
454 | 0 | return done; |
455 | 0 | } |
456 | | |
457 | | /** Execute a program. |
458 | | * |
459 | | * @param[out] out buffer to append plaintext (non valuepair) output. |
460 | | * @param[in] outlen length of out buffer. |
461 | | * @param[in] request Current request (may be NULL). |
462 | | * @param[in] cmd Command to execute. This is parsed into argv[] parts, then each individual argv |
463 | | * part is xlat'ed. |
464 | | * @param[in] input_pairs list of value pairs - these will be available in the environment of the |
465 | | * child. |
466 | | * @param[in] exec_wait set to 1 if you want to read from or write to child. |
467 | | * @param[in] shell_escape values before passing them as arguments. |
468 | | * @param[in] timeout amount of time to wait, in seconds. |
469 | | * @return |
470 | | * - 0 if exec_wait==0. |
471 | | * - exit code if exec_wait!=0. |
472 | | * - -1 on failure. |
473 | | */ |
474 | | int radius_exec_program_legacy(char *out, size_t outlen, |
475 | | request_t *request, char const *cmd, fr_pair_list_t *input_pairs, |
476 | | bool exec_wait, bool shell_escape, fr_time_delta_t timeout) |
477 | 0 | { |
478 | 0 | pid_t pid; |
479 | 0 | int stdout_pipe; |
480 | 0 | pid_t child_pid; |
481 | 0 | int status; |
482 | 0 | ssize_t len; |
483 | 0 | char answer[4096]; |
484 | |
|
485 | 0 | RDEBUG2("Executing: %s", cmd); |
486 | |
|
487 | 0 | if (out) *out = '\0'; |
488 | |
|
489 | 0 | pid = radius_start_program_legacy(NULL, &stdout_pipe, NULL, cmd, request, exec_wait, input_pairs, shell_escape); |
490 | 0 | if (pid < 0) { |
491 | 0 | return -1; |
492 | 0 | } |
493 | | |
494 | 0 | if (!exec_wait) { |
495 | 0 | return 0; |
496 | 0 | } |
497 | | |
498 | 0 | len = radius_readfrom_program_legacy(stdout_pipe, pid, timeout, answer, sizeof(answer)); |
499 | 0 | if (len < 0) { |
500 | | /* |
501 | | * Failure - radius_readfrom_program_legacy will |
502 | | * have called close(stdout_pipe) for us |
503 | | */ |
504 | 0 | RERROR("Failed to read from child output"); |
505 | 0 | return -1; |
506 | |
|
507 | 0 | } |
508 | 0 | answer[len] = '\0'; |
509 | | |
510 | | /* |
511 | | * Make sure that the writer can't block while writing to |
512 | | * a pipe that no one is reading from anymore. |
513 | | */ |
514 | 0 | close(stdout_pipe); |
515 | |
|
516 | 0 | if (len == 0) { |
517 | 0 | goto wait; |
518 | 0 | } |
519 | | |
520 | 0 | if (out) { |
521 | | /* |
522 | | * We've not been told to extract output pairs, |
523 | | * just copy the programs output to the out |
524 | | * buffer. |
525 | | */ |
526 | 0 | strlcpy(out, answer, outlen); |
527 | 0 | } |
528 | |
|
529 | 0 | wait: |
530 | 0 | child_pid = waitpid(pid, &status, 0); |
531 | 0 | if (child_pid == 0) { |
532 | 0 | RERROR("Timeout waiting for child"); |
533 | |
|
534 | 0 | return -2; |
535 | 0 | } |
536 | | |
537 | 0 | if (child_pid == pid) { |
538 | 0 | if (WIFEXITED(status)) { |
539 | 0 | status = WEXITSTATUS(status); |
540 | 0 | if (status != 0) { |
541 | 0 | RERROR("Program returned code (%d) and output %pV", status, |
542 | 0 | fr_box_strvalue_len(answer, len)); |
543 | 0 | } else { |
544 | 0 | RDEBUG2("Program returned code (%d) and output %pV", status, |
545 | 0 | fr_box_strvalue_len(answer, len)); |
546 | 0 | } |
547 | |
|
548 | 0 | return status; |
549 | 0 | } |
550 | 0 | } |
551 | | |
552 | 0 | RERROR("Abnormal child exit: %s", fr_syserror(errno)); |
553 | |
|
554 | 0 | return -1; |
555 | 0 | } |