Coverage Report

Created: 2026-08-14 07:17

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}