Coverage Report

Created: 2026-04-12 06:36

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