/src/freeradius-server/src/lib/server/exec.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: 72785d1af35bada4ba37209e16a75f50ba3a4b31 $ |
19 | | * |
20 | | * @file src/lib/server/exec.c |
21 | | * @brief Execute external programs. |
22 | | * |
23 | | * @copyright 2022-2023 Arran Cudbard-Bell (a.cudbardb@freeradius.org) |
24 | | * @copyright 2000-2004,2006 The FreeRADIUS server project |
25 | | */ |
26 | | RCSID("$Id: 72785d1af35bada4ba37209e16a75f50ba3a4b31 $") |
27 | | |
28 | | #include <stdint.h> |
29 | | |
30 | | #include <freeradius-devel/server/log.h> |
31 | | #include <freeradius-devel/server/exec.h> |
32 | | #include <freeradius-devel/server/exec_priv.h> |
33 | | #include <freeradius-devel/server/main_config.h> |
34 | | #include <freeradius-devel/server/util.h> |
35 | | |
36 | | #define MAX_ENVP 1024 |
37 | | |
38 | | static _Thread_local char *env_exec_arr[MAX_ENVP]; /* Avoid allocing 8k on the stack */ |
39 | | |
40 | | /** Flatten a list into individual "char *" argv-style array |
41 | | * |
42 | | * @param[in] ctx to allocate boxes in. |
43 | | * @param[out] argv_p where output strings go |
44 | | * @param[in] in boxes to flatten |
45 | | * @return |
46 | | * - >= 0 number of array elements in argv |
47 | | * - <0 on error |
48 | | */ |
49 | | int fr_exec_value_box_list_to_argv(TALLOC_CTX *ctx, char ***argv_p, fr_value_box_list_t const *in) |
50 | 0 | { |
51 | 0 | char **argv; |
52 | 0 | unsigned int i = 0; |
53 | 0 | size_t argc = fr_value_box_list_num_elements(in); |
54 | 0 | fr_value_box_t const *first; |
55 | | |
56 | | /* |
57 | | * Check that we're not trying to run a program from |
58 | | * a tainted source. |
59 | | */ |
60 | 0 | first = fr_value_box_list_head(in); |
61 | 0 | if (!first) { |
62 | 0 | missing: |
63 | 0 | fr_strerror_const("No program to run"); |
64 | 0 | return -1; |
65 | 0 | } |
66 | 0 | if (first->type == FR_TYPE_GROUP) first = fr_value_box_list_head(&first->vb_group); |
67 | 0 | if (!first) goto missing; |
68 | 0 | if (first->tainted) { |
69 | 0 | fr_strerror_printf("Program to run comes from tainted source - %pV", first); |
70 | 0 | return -1; |
71 | 0 | } |
72 | | |
73 | 0 | argv = talloc_zero_array(ctx, char *, argc + 1); |
74 | 0 | if (!argv) return -1; |
75 | | |
76 | 0 | fr_value_box_list_foreach(in, vb) { |
77 | | /* |
78 | | * Print the children of each group into the argv array. |
79 | | */ |
80 | 0 | argv[i] = fr_value_box_list_aprint(argv, &vb->vb_group, NULL, NULL); |
81 | 0 | if (!argv[i]) { |
82 | 0 | talloc_free(argv); |
83 | 0 | return -1; |
84 | 0 | } |
85 | 0 | i++; |
86 | 0 | } |
87 | | |
88 | 0 | *argv_p = argv; |
89 | |
|
90 | 0 | return argc; |
91 | 0 | } |
92 | | |
93 | | /** Print debug information showing the arguments and environment for a process |
94 | | * |
95 | | * @param[in] request The current request, may be NULL. |
96 | | * @param[in] argv_in arguments to pass to process. |
97 | | * @param[in] env_in environment to pass to process. |
98 | | * @param[in] env_inherit print debug for the environment from the environment. |
99 | | */ |
100 | | static inline CC_HINT(always_inline) void exec_debug(request_t *request, char **argv_in, char **env_in, bool env_inherit) |
101 | 0 | { |
102 | 0 | char **p; |
103 | |
|
104 | 0 | if (argv_in) for (p = argv_in; *p; p++) ROPTIONAL(RDEBUG3, DEBUG3, "arg[%d] %s", (unsigned int)(p - argv_in), *p); |
105 | 0 | if (env_in) for (p = env_in; *p; p++) ROPTIONAL(RDEBUG3, DEBUG3, "export %s", *p); |
106 | 0 | if (env_inherit) for (p = environ; *p; p++) ROPTIONAL(RDEBUG3, DEBUG3, "export %s", *p); |
107 | 0 | } |
108 | | |
109 | | /** Convert pairs from a request and a list of pairs into environmental variables |
110 | | * |
111 | | * @param[out] env_p Where to write an array of \0 terminated strings. |
112 | | * @param[in] env_len Length of env_p. |
113 | | * @param[out] env_sbuff To write environmental variables too. Each variable |
114 | | * will be written to the buffer, and separated with |
115 | | * a '\0'. |
116 | | * @param[in] env_m an array of markers of the same length as env_len. |
117 | | * @param[in] request Will look for &control.Exec-Export items to convert to |
118 | | * env vars. |
119 | | * @param[in] env_pairs Other items to convert to environmental variables. |
120 | | * The dictionary attribute name will be converted to |
121 | | * uppercase, and all '-' converted to '_' and will form |
122 | | * the variable name. |
123 | | * @param[in] env_escape Wrap string values in double quotes, and apply doublequote |
124 | | * escaping to all environmental variable values. |
125 | | * @return |
126 | | * - The number of environmental variables created. |
127 | | * - -1 on failure. |
128 | | */ |
129 | | static inline CC_HINT(nonnull(1,3,4,5)) CC_HINT(always_inline) |
130 | | int exec_pair_to_env(char **env_p, size_t env_len, |
131 | | fr_sbuff_t *env_sbuff, fr_sbuff_marker_t env_m[], |
132 | | request_t *request, fr_pair_list_t *env_pairs, bool env_escape) |
133 | 0 | { |
134 | 0 | char *p; |
135 | 0 | size_t i, j; |
136 | 0 | fr_dcursor_t cursor; |
137 | 0 | fr_dict_attr_t const *da; |
138 | 0 | fr_sbuff_t sbuff = FR_SBUFF_BIND_CURRENT(env_sbuff); |
139 | |
|
140 | 0 | if (!env_pairs) { |
141 | 0 | env_p[0] = NULL; |
142 | 0 | return 0; |
143 | 0 | } |
144 | | |
145 | | /* |
146 | | * Set up the environment variables in the |
147 | | * parent, so we don't call libc functions that |
148 | | * hold mutexes. They might be locked when we fork, |
149 | | * and will remain locked in the child. |
150 | | */ |
151 | 0 | i = 0; |
152 | 0 | fr_pair_list_foreach_leaf(env_pairs, vp) { |
153 | 0 | fr_sbuff_marker(&env_m[i], &sbuff); |
154 | |
|
155 | 0 | if (fr_sbuff_in_strcpy(&sbuff, vp->da->name) <= 0) { |
156 | 0 | fr_strerror_printf("Out of buffer space adding attribute name"); |
157 | 0 | return -1; |
158 | 0 | } |
159 | | |
160 | | /* |
161 | | * POSIX only allows names to contain |
162 | | * uppercase chars, digits, and |
163 | | * underscores. Digits are not allowed |
164 | | * for the first char. |
165 | | */ |
166 | 0 | p = fr_sbuff_current(&env_m[i]); |
167 | 0 | if (isdigit((uint8_t)*p)) *p++ = '_'; |
168 | 0 | for (; p < fr_sbuff_current(&sbuff); p++) { |
169 | 0 | if (isalpha((uint8_t)*p)) *p = toupper((uint8_t) *p); |
170 | 0 | else if (*p == '-') *p = '_'; |
171 | 0 | else if (isdigit((uint8_t)*p)) goto next; |
172 | 0 | else *p = '_'; |
173 | 0 | } |
174 | | |
175 | 0 | if (fr_sbuff_in_char(&sbuff, '=') <= 0) { |
176 | 0 | fr_strerror_printf("Out of buffer space"); |
177 | 0 | return -1; |
178 | 0 | } |
179 | | |
180 | 0 | if (env_escape) { |
181 | 0 | if (fr_value_box_print_quoted(&sbuff, &vp->data, T_DOUBLE_QUOTED_STRING) < 0) { |
182 | 0 | fr_strerror_printf("Out of buffer space adding attribute value for %pV", &vp->data); |
183 | 0 | return -1; |
184 | 0 | } |
185 | 0 | } else { |
186 | | /* |
187 | | * This can be zero length for empty strings |
188 | | * |
189 | | * Note we don't do double quote escaping here, |
190 | | * we just escape unprintable chars. |
191 | | * |
192 | | * Environmental variable values are not |
193 | | * restricted we likely wouldn't need to do |
194 | | * any escaping if we weren't dealing with C |
195 | | * strings. |
196 | | * |
197 | | * If we end up passing binary data through |
198 | | * then the user can unescape the octal |
199 | | * sequences on the other side. |
200 | | * |
201 | | * We unfortunately still need to escape '\' |
202 | | * because of this. |
203 | | */ |
204 | 0 | if (fr_value_box_print(&sbuff, &vp->data, &fr_value_escape_unprintables) < 0) { |
205 | 0 | fr_strerror_printf("Out of buffer space adding attribute value for %pV", &vp->data); |
206 | 0 | return -1; |
207 | 0 | } |
208 | 0 | } |
209 | 0 | if (fr_sbuff_in_char(&sbuff, '\0') <= 0) { |
210 | 0 | fr_strerror_printf("Out of buffer space"); |
211 | 0 | return -1; |
212 | 0 | } |
213 | | |
214 | 0 | next: |
215 | 0 | i++; |
216 | 0 | if (i == (env_len - 1)) break; |
217 | 0 | } |
218 | | |
219 | | /* |
220 | | * Do this as a separate step so that if env_sbuff |
221 | | * is extended at any point during the conversion |
222 | | * the sbuff we use is the final one. |
223 | | */ |
224 | 0 | for (j = 0; j < i; j++) { |
225 | 0 | env_p[j] = fr_sbuff_current(&env_m[j]); |
226 | 0 | } |
227 | |
|
228 | 0 | da = fr_dict_attr_child_by_num(fr_dict_root(fr_dict_internal()), FR_EXEC_EXPORT); |
229 | 0 | if (da) { |
230 | 0 | fr_pair_t *vp; |
231 | |
|
232 | 0 | for (vp = fr_pair_dcursor_by_da_init(&cursor, &request->control_pairs, da); |
233 | 0 | vp; |
234 | 0 | vp = fr_dcursor_next(&cursor)) { |
235 | 0 | env_p[i++] = UNCONST(char *, vp->vp_strvalue); |
236 | 0 | } |
237 | 0 | } |
238 | |
|
239 | 0 | if (unlikely(i == (env_len - 1))) { |
240 | 0 | fr_strerror_printf("Out of space for environmental variables"); |
241 | 0 | return -1; |
242 | 0 | } |
243 | | |
244 | | /* |
245 | | * NULL terminate for execve |
246 | | */ |
247 | 0 | env_p[i] = NULL; |
248 | |
|
249 | 0 | return i; |
250 | 0 | } |
251 | | |
252 | | /** Convert env_pairs into an array of environmental variables using thread local buffers |
253 | | * |
254 | | * @param[in] request Will be searched for control.Exec-Export pairs. |
255 | | * @param[in] env_pairs env_pairs to put into into the environment. May be NULL. |
256 | | * @param[in] env_escape Wrap string values in double quotes, and apply doublequote |
257 | | * escaping to all environmental variable values. |
258 | | * @return |
259 | | * - An array of environmental variable definitions, valid until the next call |
260 | | * to fr_exec_pair_to_env within the same thread. |
261 | | * - NULL on error. Error retrievable fr_strerror(). |
262 | | */ |
263 | | char **fr_exec_pair_to_env(request_t *request, fr_pair_list_t *env_pairs, bool env_escape) |
264 | 0 | { |
265 | 0 | static _Thread_local char *env_arr[MAX_ENVP]; /* Avoid allocing 8k on the stack */ |
266 | 0 | static _Thread_local char env_buff[NUM_ELEMENTS(env_arr) * 128]; /* Avoid allocing 128k on the stack */ |
267 | 0 | static _Thread_local fr_sbuff_marker_t env_m[NUM_ELEMENTS(env_arr)]; |
268 | |
|
269 | 0 | if (exec_pair_to_env(env_arr, NUM_ELEMENTS(env_arr), |
270 | 0 | &FR_SBUFF_OUT(env_buff, sizeof(env_buff)), env_m, |
271 | 0 | request, env_pairs, env_escape) < 0) return NULL; |
272 | | |
273 | 0 | return env_arr; |
274 | 0 | } |
275 | | |
276 | | /** Start a child process |
277 | | * |
278 | | * We try to be fail-safe here. So if ANYTHING goes wrong, we exit with status 1. |
279 | | * |
280 | | * @param[in] argv array of arguments to pass to child. |
281 | | * @param[in] envp array of environment variables in form `<attr>=<val>` |
282 | | * @param[in] exec_wait if true, redirect child process' stdin, stdout, stderr |
283 | | * to the pipes provided, redirecting any to /dev/null |
284 | | * where no pipe was provided. If false redirect |
285 | | * stdin, and stdout to /dev/null. |
286 | | * @param[in] debug If true, and !exec_wait, don't molest stderr. |
287 | | * @param[in] stdin_pipe the pipe used to write data to the process. STDIN will |
288 | | * be set to stdin_pipe[0], stdin_pipe[1] will be closed. |
289 | | * @param[in] stdout_pipe the pipe used to read data from the process. |
290 | | * STDOUT will be set to stdout_pipe[1], stdout_pipe[0] |
291 | | * will be closed. |
292 | | * @param[in] stderr_pipe the pipe used to read error text from the process. |
293 | | * STDERR will be set to stderr_pipe[1], stderr_pipe[0] |
294 | | * will be closed. |
295 | | */ |
296 | | static NEVER_RETURNS void exec_child(char **argv, char **envp, |
297 | | bool exec_wait, bool debug, |
298 | | int stdin_pipe[static 2], int stdout_pipe[static 2], int stderr_pipe[static 2]) |
299 | 0 | { |
300 | 0 | int devnull; |
301 | | |
302 | | /* |
303 | | * Open STDIN to /dev/null |
304 | | */ |
305 | 0 | devnull = open("/dev/null", O_RDWR); |
306 | 0 | if (devnull < 0) { |
307 | 0 | fprintf(stderr, "Failed opening /dev/null: %s\n", fr_syserror(errno)); |
308 | | |
309 | | /* |
310 | | * Where the status code is interpreted as a module rcode |
311 | | * one is subtracted from it, to allow 0 to equal success |
312 | | * |
313 | | * 2 is RLM_MODULE_FAIL + 1 |
314 | | */ |
315 | 0 | exit(2); |
316 | 0 | } |
317 | | |
318 | | /* |
319 | | * Only massage the pipe handles if the parent |
320 | | * has created them. |
321 | | */ |
322 | 0 | if (exec_wait) { |
323 | 0 | if (stdin_pipe[1] >= 0) { |
324 | 0 | close(stdin_pipe[1]); |
325 | 0 | dup2(stdin_pipe[0], STDIN_FILENO); |
326 | 0 | } else { |
327 | 0 | dup2(devnull, STDIN_FILENO); |
328 | 0 | } |
329 | |
|
330 | 0 | if (stdout_pipe[1] >= 0) { |
331 | 0 | close(stdout_pipe[0]); |
332 | 0 | dup2(stdout_pipe[1], STDOUT_FILENO); |
333 | 0 | } else { |
334 | 0 | dup2(devnull, STDOUT_FILENO); |
335 | 0 | } |
336 | |
|
337 | 0 | if (stderr_pipe[1] >= 0) { |
338 | 0 | close(stderr_pipe[0]); |
339 | 0 | dup2(stderr_pipe[1], STDERR_FILENO); |
340 | 0 | } else { |
341 | 0 | dup2(devnull, STDERR_FILENO); |
342 | 0 | } |
343 | 0 | } else { /* no pipe, STDOUT should be /dev/null */ |
344 | 0 | dup2(devnull, STDIN_FILENO); |
345 | 0 | dup2(devnull, STDOUT_FILENO); |
346 | | |
347 | | /* |
348 | | * If we're not debugging, then we can't do |
349 | | * anything with the error messages, so we throw |
350 | | * them away. |
351 | | * |
352 | | * If we are debugging, then we want the error |
353 | | * messages to go to the STDERR of the server. |
354 | | */ |
355 | 0 | if (!debug) dup2(devnull, STDERR_FILENO); |
356 | 0 | } |
357 | |
|
358 | 0 | close(devnull); |
359 | | |
360 | | /* |
361 | | * The server may have MANY FD's open. We don't |
362 | | * want to leave dangling FD's for the child process |
363 | | * to play funky games with, so we close them. |
364 | | */ |
365 | 0 | fr_closefrom(STDERR_FILENO + 1); |
366 | | |
367 | | /* |
368 | | * Disarm the thread local destructors |
369 | | * |
370 | | * It's not safe to free memory between fork and exec. |
371 | | */ |
372 | 0 | fr_atexit_thread_local_disarm_all(); |
373 | | |
374 | | /* |
375 | | * Disarm the global destructors for the same reason |
376 | | */ |
377 | 0 | fr_atexit_global_disarm_all(); |
378 | | |
379 | | /* |
380 | | * I swear the signature for execve is wrong and should |
381 | | * take 'char const * const argv[]'. |
382 | | * |
383 | | * Note: execve(), unlike system(), treats all the space |
384 | | * delimited arguments as literals, so there's no need |
385 | | * to perform additional escaping. |
386 | | */ |
387 | 0 | execve(argv[0], argv, envp); |
388 | 0 | printf("Failed to execute \"%s\": %s", argv[0], fr_syserror(errno)); /* fork output will be captured */ |
389 | | |
390 | | /* |
391 | | * Where the status code is interpreted as a module rcode |
392 | | * one is subtracted from it, to allow 0 to equal success |
393 | | * |
394 | | * 2 is RLM_MODULE_FAIL + 1 |
395 | | */ |
396 | 0 | exit(2); |
397 | 0 | } |
398 | | |
399 | | /** Merge extra environmental variables and potentially the inherited environment |
400 | | * |
401 | | * @param[in] env_in to merge. |
402 | | * @param[in] env_inherit inherite environment from radiusd. |
403 | | * @return merged environmental variables. |
404 | | */ |
405 | | static |
406 | | char **exec_build_env(char **env_in, bool env_inherit) |
407 | 0 | { |
408 | 0 | size_t num_in, num_environ; |
409 | | |
410 | | /* |
411 | | * Not inheriting the radiusd environment, just return whatever we were given. |
412 | | */ |
413 | 0 | if (!env_inherit) { |
414 | 0 | return env_in; |
415 | 0 | } |
416 | | |
417 | | /* |
418 | | * No additional environment variables, just return the ones from radiusd. |
419 | | */ |
420 | 0 | if (!env_in) return environ; |
421 | | |
422 | 0 | for (num_environ = 0; environ[num_environ] != NULL; num_environ++) { |
423 | | /* nothing */ |
424 | 0 | } |
425 | | |
426 | | /* |
427 | | * No room to copy anything after the environment variables. |
428 | | */ |
429 | 0 | if (((num_environ + 1) >= NUM_ELEMENTS(env_exec_arr))) { |
430 | 0 | return environ; |
431 | 0 | } |
432 | | |
433 | | /* |
434 | | * Copy the radiusd environment to the local array |
435 | | */ |
436 | 0 | memcpy(env_exec_arr, environ, (num_environ + 1) * sizeof(environ[0])); |
437 | |
|
438 | 0 | for (num_in = 0; env_in[num_in] != NULL; num_in++) { |
439 | 0 | if ((num_environ + num_in + 1) >= NUM_ELEMENTS(env_exec_arr)) break; |
440 | 0 | } |
441 | |
|
442 | 0 | memcpy(env_exec_arr + num_environ, env_in, num_in * sizeof(environ[0])); |
443 | 0 | env_exec_arr[num_environ + num_in] = NULL; |
444 | |
|
445 | 0 | return env_exec_arr; |
446 | 0 | } |
447 | | |
448 | | static bool fr_exec_allowed(char const *filename) |
449 | 0 | { |
450 | 0 | size_t i, num_files, len; |
451 | |
|
452 | 0 | if (!main_config->limit_exec) return true; |
453 | | |
454 | 0 | num_files = talloc_array_length(main_config->limit_exec); |
455 | 0 | if (!num_files) goto fail; |
456 | | |
457 | 0 | len = strlen(filename); |
458 | |
|
459 | 0 | for (i = 0; i < num_files; i++) { |
460 | 0 | size_t alen = talloc_array_length(main_config->limit_exec[i]); |
461 | | |
462 | | /* |
463 | | * The allowed directory is longer than the filename, it's not allowed. |
464 | | */ |
465 | 0 | if (alen > len) continue; |
466 | | |
467 | | /* |
468 | | * No leading match, it's not allowed. |
469 | | */ |
470 | 0 | if (memcmp(filename, main_config->limit_exec[i], alen) != 0) continue; |
471 | | |
472 | 0 | if (alen == len) return true; |
473 | | |
474 | | /* |
475 | | * Setting "allow = foo/bar" does NOT mean that |
476 | | * we allow "foo/bard". It MUST be "foo/bar/bad" |
477 | | */ |
478 | 0 | if (filename[alen] != '/') break; |
479 | | |
480 | 0 | return true; |
481 | 0 | } |
482 | | |
483 | 0 | fail: |
484 | 0 | EDEBUG("Failed running program %s - it is outside of 'limit exec { ... }'", filename); |
485 | 0 | return false; |
486 | 0 | } |
487 | | |
488 | | /** Execute a program without waiting for the program to finish. |
489 | | * |
490 | | * @param[in] el event list to insert reaper child into. |
491 | | * @param[in] argv_in arg[0] is the path to the program, arg[...] are arguments |
492 | | * to pass to the program. |
493 | | * @param[in] env_in any additional environmental variables to pass to the program. |
494 | | * @param[in] env_inherit Inherit the environment from the current process. |
495 | | * This will be merged with any variables from env_pairs. |
496 | | * @param[in] debug If true, STDERR will be left open and pointing to the stderr |
497 | | * descriptor of the parent. |
498 | | * @return |
499 | | * - <0 on error. Error retrievable fr_strerror(). |
500 | | * - 0 on success |
501 | | * |
502 | | * @todo - maybe take an fr_dcursor_t instead of env_pairs? That |
503 | | * would allow finer-grained control over the attributes to put into |
504 | | * the environment. |
505 | | */ |
506 | | int fr_exec_fork_nowait(fr_event_list_t *el, char **argv_in, char **env_in, bool env_inherit, bool debug) |
507 | 0 | { |
508 | 0 | char **env; |
509 | 0 | pid_t pid; |
510 | |
|
511 | 0 | if (!fr_exec_allowed(argv_in[0])) return -1; |
512 | | |
513 | 0 | env = exec_build_env(env_in, env_inherit); |
514 | 0 | pid = fork(); |
515 | | /* |
516 | | * The child never returns from calling exec_child(); |
517 | | */ |
518 | 0 | if (pid == 0) { |
519 | 0 | int unused[2] = { -1, -1 }; |
520 | |
|
521 | 0 | exec_child(argv_in, env, false, debug, unused, unused, unused); |
522 | 0 | } |
523 | |
|
524 | 0 | if (pid < 0) { |
525 | 0 | fr_strerror_printf("Couldn't fork %s", argv_in[0]); |
526 | 0 | error: |
527 | 0 | return -1; |
528 | 0 | } |
529 | | |
530 | | /* |
531 | | * Ensure that we can clean up any child processes. We |
532 | | * don't want them left over as zombies. |
533 | | */ |
534 | 0 | if (fr_event_pid_reap(el, pid, NULL, NULL) < 0) { |
535 | 0 | int status; |
536 | | |
537 | | /* |
538 | | * Try and cleanup... really we have |
539 | | * no idea what state things are in. |
540 | | */ |
541 | 0 | kill(pid, SIGKILL); |
542 | 0 | waitpid(pid, &status, WNOHANG); |
543 | 0 | goto error; |
544 | 0 | } |
545 | | |
546 | 0 | return 0; |
547 | 0 | } |
548 | | |
549 | | /** Execute a program assuming that the caller waits for it to finish. |
550 | | * |
551 | | * The caller takes responsibility for calling waitpid() on the returned PID. |
552 | | * |
553 | | * The caller takes responsibility for reading from the returned FD, |
554 | | * and closing it. |
555 | | * |
556 | | * @param[out] pid_p The PID of the child |
557 | | * @param[out] stdin_fd The stdin FD of the child. |
558 | | * @param[out] stdout_fd The stdout FD of the child. |
559 | | * @param[out] stderr_fd The stderr FD of the child. |
560 | | * @param[in] argv_in arg[0] is the path to the program, arg[...] are arguments |
561 | | * to pass to the program. |
562 | | * @param[in] env_in Environmental variables to pass to the program. |
563 | | * @param[in] env_inherit Inherit the environment from the current process. |
564 | | * This will be merged with any variables from env_pairs. |
565 | | * @param[in] debug If true, STDERR will be left open and pointing to the stderr |
566 | | * descriptor of the parent, if no stderr_fd pointer is provided. |
567 | | * @return |
568 | | * - <0 on error. Error retrievable fr_strerror(). |
569 | | * - 0 on success. |
570 | | * |
571 | | * @todo - maybe take an fr_dcursor_t instead of env_pairs? That |
572 | | * would allow finer-grained control over the attributes to put into |
573 | | * the environment. |
574 | | */ |
575 | | int fr_exec_fork_wait(pid_t *pid_p, |
576 | | int *stdin_fd, int *stdout_fd, int *stderr_fd, |
577 | | char **argv_in, char **env_in, bool env_inherit, bool debug) |
578 | 0 | { |
579 | 0 | char **env; |
580 | 0 | pid_t pid; |
581 | 0 | int stdin_pipe[2] = {-1, -1}; |
582 | 0 | int stderr_pipe[2] = {-1, -1}; |
583 | 0 | int stdout_pipe[2] = {-1, -1}; |
584 | |
|
585 | 0 | if (!fr_exec_allowed(argv_in[0])) return -1; |
586 | | |
587 | 0 | if (stdin_fd) { |
588 | 0 | if (pipe(stdin_pipe) < 0) { |
589 | 0 | fr_strerror_const("Failed opening pipe to write to child"); |
590 | |
|
591 | 0 | error1: |
592 | 0 | return -1; |
593 | 0 | } |
594 | 0 | if (fr_nonblock(stdin_pipe[1]) < 0) { |
595 | 0 | fr_strerror_const("Error setting stdin to nonblock"); |
596 | 0 | goto error2; |
597 | 0 | } |
598 | 0 | } |
599 | | |
600 | 0 | if (stdout_fd) { |
601 | 0 | if (pipe(stdout_pipe) < 0) { |
602 | 0 | fr_strerror_const("Failed opening pipe to read from child"); |
603 | |
|
604 | 0 | error2: |
605 | 0 | close(stdin_pipe[0]); |
606 | 0 | close(stdin_pipe[1]); |
607 | 0 | goto error1; |
608 | 0 | } |
609 | 0 | if (fr_nonblock(stdout_pipe[0]) < 0) { |
610 | 0 | fr_strerror_const("Error setting stdout to nonblock"); |
611 | 0 | goto error3; |
612 | 0 | } |
613 | 0 | } |
614 | | |
615 | 0 | if (stderr_fd) { |
616 | 0 | if (pipe(stderr_pipe) < 0) { |
617 | 0 | fr_strerror_const("Failed opening pipe to read from child"); |
618 | |
|
619 | 0 | error3: |
620 | 0 | close(stdout_pipe[0]); |
621 | 0 | close(stdout_pipe[1]); |
622 | 0 | goto error2; |
623 | 0 | } |
624 | 0 | if (fr_nonblock(stderr_pipe[0]) < 0) { |
625 | 0 | fr_strerror_const("Error setting stderr to nonblock"); |
626 | 0 | close(stderr_pipe[0]); |
627 | 0 | close(stderr_pipe[1]); |
628 | 0 | goto error3; |
629 | 0 | } |
630 | 0 | } |
631 | | |
632 | 0 | env = exec_build_env(env_in, env_inherit); |
633 | 0 | pid = fork(); |
634 | | |
635 | | /* |
636 | | * The child never returns from calling exec_child(); |
637 | | */ |
638 | 0 | if (pid == 0) exec_child(argv_in, env, true, debug, stdin_pipe, stdout_pipe, stderr_pipe); |
639 | 0 | if (pid < 0) { |
640 | 0 | fr_strerror_printf("Couldn't fork %s", argv_in[0]); |
641 | 0 | *pid_p = -1; /* Ensure the PID is set even if the caller didn't check the return code */ |
642 | 0 | if (stderr_fd) { |
643 | 0 | close(stderr_pipe[0]); |
644 | 0 | close(stderr_pipe[1]); |
645 | 0 | } |
646 | 0 | goto error3; |
647 | 0 | } |
648 | | |
649 | | /* |
650 | | * Tell the caller the childs PID, and the FD to read from. |
651 | | */ |
652 | 0 | *pid_p = pid; |
653 | |
|
654 | 0 | if (stdin_fd) { |
655 | 0 | *stdin_fd = stdin_pipe[1]; |
656 | 0 | close(stdin_pipe[0]); |
657 | 0 | } |
658 | |
|
659 | 0 | if (stdout_fd) { |
660 | 0 | *stdout_fd = stdout_pipe[0]; |
661 | 0 | close(stdout_pipe[1]); |
662 | 0 | } |
663 | |
|
664 | 0 | if (stderr_fd) { |
665 | 0 | *stderr_fd = stderr_pipe[0]; |
666 | 0 | close(stderr_pipe[1]); |
667 | 0 | } |
668 | |
|
669 | 0 | return 0; |
670 | 0 | } |
671 | | |
672 | | /** Similar to fr_exec_oneshot, but does not attempt to parse output |
673 | | * |
674 | | * @param[in] request currently being processed, may be NULL. |
675 | | * @param[in] args to call as a fr_value_box_list_t. Program will |
676 | | * be the first box and arguments in the subsequent boxes. |
677 | | * @param[in] env_pairs list of pairs to be presented as environment variables |
678 | | * to the child. |
679 | | * @param[in] env_escape Wrap string values in double quotes, and apply doublequote |
680 | | * escaping to all environmental variable values. |
681 | | * @param[in] env_inherit Inherit the environment from the current process. |
682 | | * This will be merged with any variables from env_pairs. |
683 | | * @return |
684 | | * - 0 on success. |
685 | | * - -1 on error. |
686 | | */ |
687 | | int fr_exec_oneshot_nowait(request_t *request, |
688 | | fr_value_box_list_t *args, fr_pair_list_t *env_pairs, |
689 | | bool env_escape, bool env_inherit) |
690 | 0 | { |
691 | 0 | char **argv = NULL; |
692 | 0 | char **env = NULL; |
693 | 0 | int ret; |
694 | |
|
695 | 0 | if (unlikely(fr_exec_value_box_list_to_argv(unlang_interpret_frame_talloc_ctx(request), &argv, args) < 0)) { |
696 | 0 | RPEDEBUG("Failed converting boxes to argument strings"); |
697 | 0 | return -1; |
698 | 0 | } |
699 | | |
700 | 0 | if (env_pairs) { |
701 | 0 | env = fr_exec_pair_to_env(request, env_pairs, env_escape); |
702 | 0 | if (unlikely(env == NULL)) { |
703 | 0 | RPEDEBUG("Failed creating environment pairs"); |
704 | 0 | return -1; |
705 | 0 | } |
706 | 0 | } |
707 | | |
708 | 0 | if (RDEBUG_ENABLED3) exec_debug(request, argv, env, env_inherit); |
709 | 0 | ret = fr_exec_fork_nowait(unlang_interpret_event_list(request), argv, env, |
710 | 0 | env_inherit, ROPTIONAL_ENABLED(RDEBUG_ENABLED2, DEBUG_ENABLED2)); |
711 | 0 | talloc_free(argv); |
712 | 0 | if (unlikely(ret < 0)) RPEDEBUG("Failed executing program"); |
713 | |
|
714 | 0 | return ret; |
715 | 0 | } |
716 | | |
717 | | /** Cleans up an exec'd process on error |
718 | | * |
719 | | * This function is intended to be called at any point after a successful |
720 | | * #fr_exec_oneshot call in order to release resources and cleanup |
721 | | * zombie processes. |
722 | | * |
723 | | * @param[in] exec state to cleanup. |
724 | | * @param[in] signal If non-zero, and we think the process is still |
725 | | * running, send it a signal to cause it to exit. |
726 | | * The PID reaper we insert here will cleanup its |
727 | | * state so it doesn't become a zombie. |
728 | | * |
729 | | */ |
730 | | void fr_exec_oneshot_cleanup(fr_exec_state_t *exec, int signal) |
731 | 0 | { |
732 | 0 | request_t *request = exec->request; |
733 | 0 | fr_event_list_t *el = unlang_interpret_event_list(request); |
734 | |
|
735 | 0 | if (exec->pid >= 0) { |
736 | 0 | RDEBUG3("Cleaning up exec state for PID %u", exec->pid); |
737 | |
|
738 | 0 | } else if (exec->failed != FR_EXEC_FAIL_NONE) { |
739 | 0 | RDEBUG3("Cleaning up failed exec"); |
740 | 0 | } |
741 | | |
742 | | /* |
743 | | * There's still an EV_PROC event installed |
744 | | * for the PID remove it (there's a destructor). |
745 | | */ |
746 | 0 | if (exec->ev_pid) { |
747 | 0 | talloc_const_free(exec->ev_pid); |
748 | 0 | fr_assert(!exec->ev_pid); /* Should be NULLified by destructor */ |
749 | 0 | } |
750 | |
|
751 | 0 | if (exec->stdout_fd >= 0) { |
752 | 0 | if (fr_event_fd_delete(el, exec->stdout_fd, FR_EVENT_FILTER_IO) < 0){ |
753 | 0 | RPERROR("Failed removing stdout handler"); |
754 | 0 | } |
755 | 0 | close(exec->stdout_fd); |
756 | 0 | exec->stdout_fd = -1; |
757 | 0 | } |
758 | |
|
759 | 0 | if (exec->stderr_fd >= 0) { |
760 | 0 | if (fr_event_fd_delete(el, exec->stderr_fd, FR_EVENT_FILTER_IO) < 0) { |
761 | 0 | RPERROR("Failed removing stderr handler"); |
762 | 0 | } |
763 | 0 | close(exec->stderr_fd); |
764 | 0 | exec->stderr_fd = -1; |
765 | 0 | } |
766 | |
|
767 | 0 | if (exec->pid >= 0) { |
768 | 0 | if (signal > 0) kill(exec->pid, signal); |
769 | |
|
770 | 0 | if (unlikely(fr_event_pid_reap(el, exec->pid, NULL, NULL) < 0)) { |
771 | 0 | int status; |
772 | |
|
773 | 0 | RPERROR("Failed setting up async PID reaper, PID %u may now be a zombie", exec->pid); |
774 | | |
775 | | /* |
776 | | * Try and cleanup... really we have |
777 | | * no idea what state things are in. |
778 | | */ |
779 | 0 | kill(exec->pid, SIGKILL); |
780 | 0 | waitpid(exec->pid, &status, WNOHANG); |
781 | 0 | } |
782 | 0 | exec->pid = -1; |
783 | 0 | } |
784 | |
|
785 | 0 | FR_TIMER_DELETE(&exec->ev); |
786 | 0 | } |
787 | | |
788 | | /* |
789 | | * Callback when exec has completed. Record the status and tidy up. |
790 | | */ |
791 | | static void exec_reap(fr_event_list_t *el, pid_t pid, int status, void *uctx) |
792 | 0 | { |
793 | 0 | fr_exec_state_t *exec = uctx; /* may not be talloced */ |
794 | 0 | request_t *request = exec->request; |
795 | 0 | int wait_status = 0; |
796 | 0 | int ret; |
797 | |
|
798 | 0 | if (!fr_cond_assert(pid == exec->pid)) RWDEBUG("Event PID %u and exec->pid %u do not match", pid, exec->pid); |
799 | | |
800 | | /* |
801 | | * Reap the process. This is needed so the processes |
802 | | * don't stick around indefinitely. libkqueue/kqueue |
803 | | * does not do this for us! |
804 | | */ |
805 | 0 | ret = waitpid(exec->pid, &wait_status, WNOHANG); |
806 | 0 | if (ret < 0) { |
807 | 0 | RWDEBUG("Failed reaping PID %i: %s", exec->pid, fr_syserror(errno)); |
808 | | /* |
809 | | * Either something cleaned up the process before us |
810 | | * (bad!), or the notification system is broken |
811 | | * (also bad!) |
812 | | * |
813 | | * This could be caused by 3rd party libraries. |
814 | | */ |
815 | 0 | } else if (ret == 0) { |
816 | 0 | RWDEBUG("Something reaped PID %d before us!", exec->pid); |
817 | 0 | wait_status = status; |
818 | 0 | } |
819 | | |
820 | | /* |
821 | | * kevent should be returning an identical status value |
822 | | * to waitpid. |
823 | | */ |
824 | 0 | if (wait_status != status) RWDEBUG("Exit status from waitpid (%d) and kevent (%d) disagree", |
825 | 0 | wait_status, status); |
826 | |
|
827 | 0 | if (WIFEXITED(wait_status)) { |
828 | 0 | RDEBUG("Program exited with status code %d", WEXITSTATUS(wait_status)); |
829 | 0 | exec->status = WEXITSTATUS(wait_status); |
830 | 0 | } else if (WIFSIGNALED(wait_status)) { |
831 | 0 | RDEBUG("Program exited due to signal with status code %d", WTERMSIG(wait_status)); |
832 | 0 | exec->status = -WTERMSIG(wait_status); |
833 | 0 | } else { |
834 | 0 | RDEBUG("Program exited due to unknown status %d", wait_status); |
835 | 0 | exec->status = -wait_status; |
836 | 0 | } |
837 | 0 | exec->pid = -1; /* pid_t is signed */ |
838 | |
|
839 | 0 | FR_TIMER_DELETE(&exec->ev); |
840 | | |
841 | | /* |
842 | | * Process exit notifications (EV_PROC) and file |
843 | | * descriptor read events (EV_READ) can race. |
844 | | * |
845 | | * So... If the process has exited, trigger the IO |
846 | | * handlers manually. |
847 | | * |
848 | | * This is icky, but the only other option is to |
849 | | * enhance our event loop so we can look for |
850 | | * pending events associated with file |
851 | | * descriptors... |
852 | | * |
853 | | * Even then we might get the file readable |
854 | | * notification and the process exited notification |
855 | | * in different kevent() calls on busy systems. |
856 | | */ |
857 | 0 | if (exec->stdout_fd >= 0) { |
858 | 0 | fr_event_fd_t *ef; |
859 | 0 | fr_event_fd_cb_t cb; |
860 | |
|
861 | 0 | ef = fr_event_fd_handle(el, exec->stdout_fd, FR_EVENT_FILTER_IO); |
862 | 0 | if (!fr_cond_assert_msg(ef, "no event associated with processes's stdout fd (%i)", |
863 | 0 | exec->stdout_fd)) goto close_stdout; |
864 | | |
865 | 0 | cb = fr_event_fd_cb(ef, EVFILT_READ, 0); |
866 | 0 | if (!fr_cond_assert_msg(cb, "no read callback associated with processes's stdout_fd (%i)", |
867 | 0 | exec->stdout_fd)) goto close_stdout; |
868 | | |
869 | | /* |
870 | | * Call the original read callback that |
871 | | * was setup here to ensure that there's |
872 | | * no pending data. |
873 | | */ |
874 | 0 | cb(el, exec->stdout_fd, 0, fr_event_fd_uctx(ef)); |
875 | | |
876 | | /* |
877 | | * ...and delete the event from the event |
878 | | * loop. This should also suppress the |
879 | | * EVFILT_READ event if there was one. |
880 | | */ |
881 | 0 | (void) fr_event_fd_delete(el, exec->stdout_fd, FR_EVENT_FILTER_IO); |
882 | 0 | close_stdout: |
883 | 0 | close(exec->stdout_fd); |
884 | 0 | exec->stdout_fd = -1; |
885 | 0 | } |
886 | | |
887 | 0 | if (exec->stderr_fd >= 0) { |
888 | 0 | fr_event_fd_t *ef; |
889 | 0 | fr_event_fd_cb_t cb; |
890 | |
|
891 | 0 | ef = fr_event_fd_handle(el, exec->stderr_fd, FR_EVENT_FILTER_IO); |
892 | 0 | if (!fr_cond_assert_msg(ef, "no event associated with processes's stderr fd (%i)", |
893 | 0 | exec->stderr_fd)) goto close_stderr; |
894 | | |
895 | 0 | cb = fr_event_fd_cb(ef, EVFILT_READ, 0); |
896 | 0 | if (!fr_cond_assert_msg(cb, "no read callback associated with processes's stderr_fd (%i)", |
897 | 0 | exec->stderr_fd)) goto close_stderr; |
898 | | |
899 | 0 | cb(el, exec->stderr_fd, 0, fr_event_fd_uctx(ef)); |
900 | 0 | (void) fr_event_fd_delete(el, exec->stderr_fd, FR_EVENT_FILTER_IO); |
901 | 0 | close_stderr: |
902 | 0 | close(exec->stderr_fd); |
903 | 0 | exec->stderr_fd = -1; |
904 | 0 | } |
905 | | |
906 | 0 | unlang_interpret_mark_runnable(exec->request); |
907 | 0 | } |
908 | | |
909 | | /* |
910 | | * Callback when an exec times out. |
911 | | */ |
912 | | static void exec_timeout(UNUSED fr_timer_list_t *tl, UNUSED fr_time_t now, void *uctx) |
913 | 0 | { |
914 | 0 | fr_exec_state_t *exec = uctx; /* may not be talloced */ |
915 | 0 | bool exit_timeout; |
916 | | |
917 | | /* |
918 | | * Some race conditions cause fr_exec_oneshot_cleanup to insert |
919 | | * a new event, which calls fr_strerror_clear(), resulting in |
920 | | * inconsistent error messages. |
921 | | * Recording the condition to drive the error message here and |
922 | | * then setting after tidying up keeps things consistent. |
923 | | */ |
924 | 0 | exit_timeout = (exec->stdout_fd < 0); |
925 | |
|
926 | 0 | exec->failed = FR_EXEC_FAIL_TIMEOUT; |
927 | 0 | fr_exec_oneshot_cleanup(exec, SIGKILL); |
928 | |
|
929 | 0 | if (exit_timeout) { |
930 | 0 | fr_strerror_const("Timeout waiting for program to exit"); |
931 | 0 | } else { |
932 | 0 | fr_strerror_const("Timeout running program"); |
933 | 0 | } |
934 | |
|
935 | 0 | unlang_interpret_mark_runnable(exec->request); |
936 | 0 | } |
937 | | |
938 | | /* |
939 | | * Callback to read stdout from an exec into the pre-prepared extensible sbuff |
940 | | */ |
941 | 0 | static void exec_stdout_read(UNUSED fr_event_list_t *el, int fd, int flags, void *uctx) { |
942 | 0 | fr_exec_state_t *exec = uctx; |
943 | 0 | request_t *request = exec->request; |
944 | 0 | ssize_t data_len, remaining; |
945 | 0 | fr_sbuff_marker_t start_m; |
946 | |
|
947 | 0 | fr_sbuff_marker(&start_m, &exec->stdout_buff); |
948 | |
|
949 | 0 | do { |
950 | | /* |
951 | | * Read in 128 byte chunks |
952 | | */ |
953 | 0 | remaining = fr_sbuff_extend_lowat(NULL, &exec->stdout_buff, 128); |
954 | | |
955 | | /* |
956 | | * Ran out of buffer space. |
957 | | */ |
958 | 0 | if (unlikely(!remaining)) { |
959 | 0 | REDEBUG("Too much output from program - killing it and failing the request"); |
960 | |
|
961 | 0 | error: |
962 | 0 | exec->failed = FR_EXEC_FAIL_TOO_MUCH_DATA; |
963 | 0 | fr_exec_oneshot_cleanup(exec, SIGKILL); |
964 | 0 | break; |
965 | 0 | } |
966 | | |
967 | 0 | data_len = read(fd, fr_sbuff_current(&exec->stdout_buff), remaining); |
968 | 0 | if (data_len < 0) { |
969 | 0 | if (errno == EINTR) continue; |
970 | | |
971 | | /* |
972 | | * This can happen when the callback is called |
973 | | * manually when we're reaping the process. |
974 | | * |
975 | | * It's pretty much an identical condition to |
976 | | * data_len == 0. |
977 | | */ |
978 | 0 | if (errno == EWOULDBLOCK) break; |
979 | | |
980 | 0 | REDEBUG("Error reading from child program - %s", fr_syserror(errno)); |
981 | 0 | goto error; |
982 | 0 | } |
983 | | |
984 | | /* |
985 | | * Even if we get 0 now the process may write more data later |
986 | | * before it completes, so we leave the fd handlers in place. |
987 | | */ |
988 | 0 | if (data_len == 0) break; |
989 | | |
990 | 0 | fr_sbuff_advance(&exec->stdout_buff, data_len); |
991 | 0 | } while (remaining == data_len); /* If process returned maximum output, loop again */ |
992 | | |
993 | 0 | if (flags & EV_EOF) { |
994 | | /* |
995 | | * We've received EOF - so the process has finished writing |
996 | | * Remove event and tidy up |
997 | | */ |
998 | 0 | (void) fr_event_fd_delete(unlang_interpret_event_list(exec->request), fd, FR_EVENT_FILTER_IO); |
999 | 0 | close(fd); |
1000 | 0 | exec->stdout_fd = -1; |
1001 | |
|
1002 | 0 | if (exec->pid < 0) { |
1003 | | /* |
1004 | | * Child has already exited - unlang can resume |
1005 | | */ |
1006 | 0 | FR_TIMER_DELETE(&exec->ev); |
1007 | 0 | unlang_interpret_mark_runnable(exec->request); |
1008 | 0 | } |
1009 | 0 | } |
1010 | | |
1011 | | /* |
1012 | | * Only print if we got additional data |
1013 | | */ |
1014 | 0 | if (RDEBUG_ENABLED2 && fr_sbuff_behind(&start_m)) { |
1015 | 0 | RDEBUG2("pid %u (stdout) - %pV", exec->pid, |
1016 | 0 | fr_box_strvalue_len(fr_sbuff_current(&start_m), fr_sbuff_behind(&start_m))); |
1017 | 0 | } |
1018 | |
|
1019 | 0 | fr_sbuff_marker_release(&start_m); |
1020 | 0 | } |
1021 | | |
1022 | | /** Call an child program, optionally reading it's output |
1023 | | * |
1024 | | * @note If the caller set need_stdin = true, it is the caller's |
1025 | | * responsibility to close exec->std_in and remove it from any event loops |
1026 | | * if this function returns 0 (success). |
1027 | | * |
1028 | | * @param[in] ctx to allocate events in. |
1029 | | * @param[in,out] exec structure holding the state of the external call. |
1030 | | * @param[in] request currently being processed, may be NULL. |
1031 | | * @param[in] args to call as a fr_value_box_list_t. Program will |
1032 | | * be the first box and arguments in the subsequent boxes. |
1033 | | * @param[in] env_pairs list of pairs to be presented as environment variables |
1034 | | * to the child. |
1035 | | * @param[in] env_escape Wrap string values in double quotes, and apply doublequote |
1036 | | * escaping to all environmental variable values. |
1037 | | * @param[in] env_inherit Inherit the environment from the current process. |
1038 | | * This will be merged with any variables from env_pairs. |
1039 | | * @param[in] need_stdin If true, allocate a pipe that will allow us to send data to the |
1040 | | * process. |
1041 | | * @param[in] store_stdout if true keep a copy of stdout in addition to logging |
1042 | | * it if RDEBUG_ENABLED2. |
1043 | | * @param[in] stdout_ctx ctx to alloc stdout data in. |
1044 | | * @param[in] timeout to wait for child to complete. |
1045 | | * @return |
1046 | | * - 0 on success |
1047 | | * - -1 on failure |
1048 | | */ |
1049 | | int fr_exec_oneshot(TALLOC_CTX *ctx, fr_exec_state_t *exec, request_t *request, |
1050 | | fr_value_box_list_t *args, |
1051 | | fr_pair_list_t *env_pairs, bool env_escape, bool env_inherit, |
1052 | | bool need_stdin, |
1053 | | bool store_stdout, TALLOC_CTX *stdout_ctx, |
1054 | | fr_time_delta_t timeout) |
1055 | 0 | { |
1056 | 0 | int *stdout_fd = (store_stdout || RDEBUG_ENABLED2) ? &exec->stdout_fd : NULL; |
1057 | 0 | fr_event_list_t *el = unlang_interpret_event_list(request); |
1058 | 0 | char **env = NULL; |
1059 | 0 | char **argv; |
1060 | 0 | int ret; |
1061 | |
|
1062 | 0 | if (unlikely(fr_exec_value_box_list_to_argv(unlang_interpret_frame_talloc_ctx(request), &argv, args) < 0)) { |
1063 | 0 | RPEDEBUG("Failed converting boxes to argument strings"); |
1064 | 0 | return -1; |
1065 | 0 | } |
1066 | | |
1067 | 0 | if (env_pairs) { |
1068 | 0 | env = fr_exec_pair_to_env(request, env_pairs, env_escape); |
1069 | 0 | if (unlikely(!env)) { |
1070 | 0 | RPEDEBUG("Failed creating environment pairs"); |
1071 | 0 | return -1; |
1072 | 0 | } |
1073 | 0 | } |
1074 | | |
1075 | 0 | if (RDEBUG_ENABLED3) exec_debug(request, argv, env, env_inherit); |
1076 | 0 | *exec = (fr_exec_state_t){ |
1077 | 0 | .request = request, |
1078 | 0 | .env_pairs = env_pairs, |
1079 | 0 | .pid = -1, |
1080 | 0 | .stdout_fd = -1, |
1081 | 0 | .stderr_fd = -1, |
1082 | 0 | .stdin_fd = -1, |
1083 | 0 | .status = -1, /* default to program didn't work */ |
1084 | 0 | .stdin_used = need_stdin, |
1085 | 0 | .stdout_used = store_stdout, |
1086 | 0 | .stdout_ctx = stdout_ctx |
1087 | 0 | }; |
1088 | 0 | ret = fr_exec_fork_wait(&exec->pid, |
1089 | 0 | exec->stdin_used ? &exec->stdin_fd : NULL, |
1090 | 0 | stdout_fd, &exec->stderr_fd, |
1091 | 0 | argv, env, |
1092 | 0 | env_inherit, ROPTIONAL_ENABLED(RDEBUG_ENABLED2, DEBUG_ENABLED2)); |
1093 | 0 | talloc_free(argv); |
1094 | 0 | if (ret < 0) { |
1095 | 0 | fail: |
1096 | 0 | RPEDEBUG("Failed executing program"); |
1097 | | |
1098 | | /* |
1099 | | * Not done in fr_exec_oneshot_cleanup as it's |
1100 | | * usually the caller's responsibility. |
1101 | | */ |
1102 | 0 | if (exec->stdin_fd >= 0) { |
1103 | 0 | close(exec->stdin_fd); |
1104 | 0 | exec->stdin_fd = -1; |
1105 | 0 | } |
1106 | 0 | fr_exec_oneshot_cleanup(exec, 0); |
1107 | 0 | return -1; |
1108 | 0 | } |
1109 | | |
1110 | | /* |
1111 | | * First setup I/O events for the child process. This needs |
1112 | | * to be done before we call fr_event_pid_wait, as it may |
1113 | | * immediately trigger the PID callback if there's a race |
1114 | | * between kevent and the child exiting, and that callback |
1115 | | * will expect file descriptor events to have been created. |
1116 | | */ |
1117 | | |
1118 | | /* |
1119 | | * If we need to parse stdout, insert a special IO handler that |
1120 | | * aggregates all stdout data into an expandable buffer. |
1121 | | */ |
1122 | 0 | if (exec->stdout_used) { |
1123 | | /* |
1124 | | * Accept a maximum of 32k of data from the process. |
1125 | | */ |
1126 | 0 | fr_sbuff_init_talloc(exec->stdout_ctx, &exec->stdout_buff, &exec->stdout_tctx, 128, 32 * 1024); |
1127 | 0 | if (fr_event_fd_insert(ctx, NULL, el, exec->stdout_fd, exec_stdout_read, NULL, NULL, exec) < 0) { |
1128 | 0 | RPEDEBUG("Failed adding event listening to stdout"); |
1129 | 0 | goto fail_and_close; |
1130 | 0 | } |
1131 | | |
1132 | | /* |
1133 | | * If the caller doesn't want the output box, we still want to copy stdout |
1134 | | * into the request log if we're logging at a high enough level of verbosity. |
1135 | | */ |
1136 | 0 | } else if (RDEBUG_ENABLED2) { |
1137 | 0 | snprintf(exec->stdout_prefix, sizeof(exec->stdout_prefix), "pid %u (stdout)", exec->pid); |
1138 | 0 | exec->stdout_uctx = (log_fd_event_ctx_t) { |
1139 | 0 | .type = L_DBG, |
1140 | 0 | .lvl = L_DBG_LVL_2, |
1141 | 0 | .request = request, |
1142 | 0 | .prefix = exec->stdout_prefix |
1143 | 0 | }; |
1144 | |
|
1145 | 0 | if (fr_event_fd_insert(ctx, NULL, el, exec->stdout_fd, log_request_fd_event, |
1146 | 0 | NULL, NULL, &exec->stdout_uctx) < 0){ |
1147 | 0 | RPEDEBUG("Failed adding event listening to stdout"); |
1148 | 0 | goto fail_and_close; |
1149 | 0 | } |
1150 | 0 | } |
1151 | | |
1152 | | /* |
1153 | | * Send stderr to the request log as error messages with a custom prefix |
1154 | | */ |
1155 | 0 | snprintf(exec->stderr_prefix, sizeof(exec->stderr_prefix), "pid %u (stderr)", exec->pid); |
1156 | 0 | exec->stderr_uctx = (log_fd_event_ctx_t) { |
1157 | 0 | .type = L_DBG_ERR, |
1158 | 0 | .lvl = L_DBG_LVL_1, |
1159 | 0 | .request = request, |
1160 | 0 | .prefix = exec->stderr_prefix |
1161 | 0 | }; |
1162 | |
|
1163 | 0 | if (fr_event_fd_insert(ctx, NULL, el, exec->stderr_fd, log_request_fd_event, |
1164 | 0 | NULL, NULL, &exec->stderr_uctx) < 0) { |
1165 | 0 | RPEDEBUG("Failed adding event listening to stderr"); |
1166 | 0 | close(exec->stderr_fd); |
1167 | 0 | exec->stderr_fd = -1; |
1168 | 0 | goto fail; |
1169 | 0 | } |
1170 | | |
1171 | | /* |
1172 | | * Tell the event loop that it needs to wait for this PID |
1173 | | */ |
1174 | 0 | if (fr_event_pid_wait(ctx, el, &exec->ev_pid, exec->pid, exec_reap, exec) < 0) { |
1175 | 0 | exec->pid = -1; |
1176 | 0 | RPEDEBUG("Failed adding watcher for child process"); |
1177 | |
|
1178 | 0 | fail_and_close: |
1179 | | /* |
1180 | | * Avoid spurious errors in fr_exec_oneshot_cleanup |
1181 | | * when it tries to remove FDs from the |
1182 | | * event loop that were never added. |
1183 | | */ |
1184 | 0 | if (exec->stdout_fd >= 0) { |
1185 | 0 | close(exec->stdout_fd); |
1186 | 0 | exec->stdout_fd = -1; |
1187 | 0 | } |
1188 | |
|
1189 | 0 | if (exec->stderr_fd >= 0) { |
1190 | 0 | close(exec->stderr_fd); |
1191 | 0 | exec->stderr_fd = -1; |
1192 | 0 | } |
1193 | |
|
1194 | 0 | goto fail; |
1195 | 0 | } |
1196 | | |
1197 | | /* |
1198 | | * Setup event to kill the child process after a period of time. |
1199 | | */ |
1200 | 0 | if (fr_time_delta_ispos(timeout) && |
1201 | 0 | (fr_timer_in(ctx, el->tl, &exec->ev, timeout, true, exec_timeout, exec) < 0)) goto fail_and_close; |
1202 | | |
1203 | 0 | return 0; |
1204 | 0 | } |