1 /*
2 * dhcpcd - DHCP client daemon
3 * Copyright (c) 2006-2015 Roy Marples <roy@marples.name>
4 * All rights reserved
5
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 * SUCH DAMAGE.
26 */
27
28 #include <sys/stat.h>
29 #include <sys/uio.h>
30 #include <sys/wait.h>
31
32 #include <netinet/in.h>
33 #include <arpa/inet.h>
34
35 #include <ctype.h>
36 #include <errno.h>
37 #include <signal.h>
38 /* We can't include spawn.h here because it may not exist.
39 * config.h will pull it in, or our compat one. */
40 #include <stdlib.h>
41 #include <string.h>
42 #include <unistd.h>
43
44 #include "config.h"
45 #include "common.h"
46 #include "dhcp.h"
47 #include "dhcp6.h"
48 #include "if.h"
49 #include "if-options.h"
50 #include "ipv6nd.h"
51 #include "script.h"
52
53 #ifdef HAVE_SPAWN_H
54 #include <spawn.h>
55 #else
56 #include "compat/posix_spawn.h"
57 #endif
58
59 /* Allow the OS to define another script env var name */
60 #ifndef RC_SVCNAME
61 #define RC_SVCNAME "RC_SVCNAME"
62 #endif
63
64 #define DEFAULT_PATH "PATH=/usr/bin:/usr/sbin:/bin:/sbin"
65
66 static const char * const if_params[] = {
67 "interface",
68 "reason",
69 "pid",
70 "ifcarrier",
71 "ifmetric",
72 "ifwireless",
73 "ifflags",
74 "ssid",
75 "profile",
76 "interface_order",
77 NULL
78 };
79
80 void
if_printoptions(void)81 if_printoptions(void)
82 {
83 const char * const *p;
84
85 for (p = if_params; *p; p++)
86 printf(" - %s\n", *p);
87 }
88
89 static int
exec_script(const struct dhcpcd_ctx * ctx,char * const * argv,char * const * env)90 exec_script(const struct dhcpcd_ctx *ctx, char *const *argv, char *const *env)
91 {
92 pid_t pid;
93 posix_spawnattr_t attr;
94 int i;
95 #ifdef USE_SIGNALS
96 short flags;
97 sigset_t defsigs;
98 #else
99 UNUSED(ctx);
100 #endif
101
102 /* posix_spawn is a safe way of executing another image
103 * and changing signals back to how they should be. */
104 if (posix_spawnattr_init(&attr) == -1)
105 return -1;
106 #ifdef USE_SIGNALS
107 flags = POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSIGDEF;
108 posix_spawnattr_setflags(&attr, flags);
109 sigemptyset(&defsigs);
110 for (i = 0; dhcpcd_handlesigs[i]; i++)
111 sigaddset(&defsigs, dhcpcd_handlesigs[i]);
112 posix_spawnattr_setsigdefault(&attr, &defsigs);
113 posix_spawnattr_setsigmask(&attr, &ctx->sigset);
114 #endif
115 errno = 0;
116 i = posix_spawn(&pid, argv[0], NULL, &attr, argv, env);
117 if (i) {
118 errno = i;
119 return -1;
120 }
121 return pid;
122 }
123
124 #ifdef INET
125 static char *
make_var(struct dhcpcd_ctx * ctx,const char * prefix,const char * var)126 make_var(struct dhcpcd_ctx *ctx, const char *prefix, const char *var)
127 {
128 size_t len;
129 char *v;
130
131 len = strlen(prefix) + strlen(var) + 2;
132 v = malloc(len);
133 if (v == NULL) {
134 logger(ctx, LOG_ERR, "%s: %m", __func__);
135 return NULL;
136 }
137 snprintf(v, len, "%s_%s", prefix, var);
138 return v;
139 }
140
141
142 static int
append_config(struct dhcpcd_ctx * ctx,char *** env,size_t * len,const char * prefix,const char * const * config)143 append_config(struct dhcpcd_ctx *ctx, char ***env, size_t *len,
144 const char *prefix, const char *const *config)
145 {
146 size_t i, j, e1;
147 char **ne, *eq, **nep, *p;
148 int ret;
149
150 if (config == NULL)
151 return 0;
152
153 ne = *env;
154 ret = 0;
155 for (i = 0; config[i] != NULL; i++) {
156 eq = strchr(config[i], '=');
157 e1 = (size_t)(eq - config[i] + 1);
158 for (j = 0; j < *len; j++) {
159 if (strncmp(ne[j] + strlen(prefix) + 1,
160 config[i], e1) == 0)
161 {
162 p = make_var(ctx, prefix, config[i]);
163 if (p == NULL) {
164 ret = -1;
165 break;
166 }
167 free(ne[j]);
168 ne[j] = p;
169 break;
170 }
171 }
172 if (j == *len) {
173 j++;
174 p = make_var(ctx, prefix, config[i]);
175 if (p == NULL) {
176 ret = -1;
177 break;
178 }
179 nep = realloc(ne, sizeof(char *) * (j + 1));
180 if (nep == NULL) {
181 logger(ctx, LOG_ERR, "%s: %m", __func__);
182 free(p);
183 ret = -1;
184 break;
185 }
186 ne = nep;
187 ne[j - 1] = p;
188 *len = j;
189 }
190 }
191 *env = ne;
192 return ret;
193 }
194 #endif
195
196 static ssize_t
arraytostr(const char * const * argv,char ** s)197 arraytostr(const char *const *argv, char **s)
198 {
199 const char *const *ap;
200 char *p;
201 size_t len, l;
202
203 if (*argv == NULL)
204 return 0;
205 len = 0;
206 ap = argv;
207 while (*ap)
208 len += strlen(*ap++) + 1;
209 *s = p = malloc(len);
210 if (p == NULL)
211 return -1;
212 ap = argv;
213 while (*ap) {
214 l = strlen(*ap) + 1;
215 memcpy(p, *ap, l);
216 p += l;
217 ap++;
218 }
219 return (ssize_t)len;
220 }
221
222 static ssize_t
make_env(const struct interface * ifp,const char * reason,char *** argv)223 make_env(const struct interface *ifp, const char *reason, char ***argv)
224 {
225 char **env, **nenv, *p;
226 size_t e, elen, l;
227 #if defined(INET) || defined(INET6)
228 ssize_t n;
229 #endif
230 const struct if_options *ifo = ifp->options;
231 const struct interface *ifp2;
232 #ifdef INET
233 int dhcp;
234 const struct dhcp_state *state;
235 #endif
236 #ifdef INET6
237 const struct dhcp6_state *d6_state;
238 int dhcp6, ra;
239 #endif
240
241 #ifdef INET
242 dhcp = 0;
243 state = D_STATE(ifp);
244 #endif
245 #ifdef INET6
246 dhcp6 = ra = 0;
247 d6_state = D6_CSTATE(ifp);
248 #endif
249 if (strcmp(reason, "TEST") == 0) {
250 if (1 == 2) {}
251 #ifdef INET6
252 else if (d6_state && d6_state->new)
253 dhcp6 = 1;
254 else if (ipv6nd_hasra(ifp))
255 ra = 1;
256 #endif
257 #ifdef INET
258 else
259 dhcp = 1;
260 #endif
261 }
262 #ifdef INET6
263 else if (reason[strlen(reason) - 1] == '6')
264 dhcp6 = 1;
265 else if (strcmp(reason, "ROUTERADVERT") == 0)
266 ra = 1;
267 #endif
268 else if (strcmp(reason, "PREINIT") == 0 ||
269 strcmp(reason, "CARRIER") == 0 ||
270 strcmp(reason, "NOCARRIER") == 0 ||
271 strcmp(reason, "UNKNOWN") == 0 ||
272 strcmp(reason, "DEPARTED") == 0 ||
273 strcmp(reason, "STOPPED") == 0)
274 {
275 /* This space left intentionally blank */
276 }
277 #ifdef INET
278 else
279 dhcp = 1;
280 #endif
281
282 /* When dumping the lease, we only want to report interface and
283 reason - the other interface variables are meaningless */
284 if (ifp->ctx->options & DHCPCD_DUMPLEASE)
285 elen = 2;
286 else
287 elen = 13;
288
289 #define EMALLOC(i, l) if ((env[(i)] = malloc((l))) == NULL) goto eexit;
290 /* Make our env + space for profile, wireless and debug */
291 env = calloc(1, sizeof(char *) * (elen + 3 + 1));
292 if (env == NULL)
293 goto eexit;
294 e = strlen("interface") + strlen(ifp->name) + 2;
295 EMALLOC(0, e);
296 snprintf(env[0], e, "interface=%s", ifp->name);
297 e = strlen("reason") + strlen(reason) + 2;
298 EMALLOC(1, e);
299 snprintf(env[1], e, "reason=%s", reason);
300 if (ifp->ctx->options & DHCPCD_DUMPLEASE)
301 goto dumplease;
302 e = 20;
303 EMALLOC(2, e);
304 snprintf(env[2], e, "pid=%d", getpid());
305 EMALLOC(3, e);
306 snprintf(env[3], e, "ifcarrier=%s",
307 ifp->carrier == LINK_UNKNOWN ? "unknown" :
308 ifp->carrier == LINK_UP ? "up" : "down");
309 EMALLOC(4, e);
310 snprintf(env[4], e, "ifmetric=%d", ifp->metric);
311 EMALLOC(5, e);
312 snprintf(env[5], e, "ifwireless=%d", ifp->wireless);
313 EMALLOC(6, e);
314 snprintf(env[6], e, "ifflags=%u", ifp->flags);
315 EMALLOC(7, e);
316 snprintf(env[7], e, "ifmtu=%d", if_getmtu(ifp->name));
317 l = e = strlen("interface_order=");
318 TAILQ_FOREACH(ifp2, ifp->ctx->ifaces, next) {
319 if (!(ifp2->options->options & DHCPCD_PFXDLGONLY))
320 e += strlen(ifp2->name) + 1;
321 }
322 EMALLOC(8, e);
323 p = env[8];
324 strlcpy(p, "interface_order=", e);
325 e -= l;
326 p += l;
327 TAILQ_FOREACH(ifp2, ifp->ctx->ifaces, next) {
328 if (!(ifp2->options->options & DHCPCD_PFXDLGONLY)) {
329 l = strlcpy(p, ifp2->name, e);
330 p += l;
331 e -= l;
332 *p++ = ' ';
333 e--;
334 }
335 }
336 *--p = '\0';
337 if (strcmp(reason, "STOPPED") == 0) {
338 env[9] = strdup("if_up=false");
339 if (ifo->options & DHCPCD_RELEASE)
340 env[10] = strdup("if_down=true");
341 else
342 env[10] = strdup("if_down=false");
343 } else if (strcmp(reason, "TEST") == 0 ||
344 strcmp(reason, "PREINIT") == 0 ||
345 strcmp(reason, "CARRIER") == 0 ||
346 strcmp(reason, "UNKNOWN") == 0)
347 {
348 env[9] = strdup("if_up=false");
349 env[10] = strdup("if_down=false");
350 } else if (1 == 2 /* appease ifdefs */
351 #ifdef INET
352 || (dhcp && state && state->new)
353 #endif
354 #ifdef INET6
355 || (dhcp6 && d6_state && d6_state->new)
356 || (ra && ipv6nd_hasra(ifp))
357 #endif
358 )
359 {
360 env[9] = strdup("if_up=true");
361 env[10] = strdup("if_down=false");
362 } else {
363 env[9] = strdup("if_up=false");
364 env[10] = strdup("if_down=true");
365 }
366 if (env[9] == NULL || env[10] == NULL)
367 goto eexit;
368 if (dhcpcd_oneup(ifp->ctx))
369 env[11] = strdup("if_oneup=true");
370 else
371 env[11] = strdup("if_oneup=false");
372 if (env[11] == NULL)
373 goto eexit;
374 if (dhcpcd_ipwaited(ifp->ctx))
375 env[12] = strdup("if_ipwaited=true");
376 else
377 env[12] = strdup("if_ipwaited=false");
378 if (env[12] == NULL)
379 goto eexit;
380 if (ifo->options & DHCPCD_DEBUG) {
381 e = strlen("syslog_debug=true") + 1;
382 EMALLOC(elen, e);
383 snprintf(env[elen++], e, "syslog_debug=true");
384 }
385 if (*ifp->profile) {
386 e = strlen("profile=") + strlen(ifp->profile) + 1;
387 EMALLOC(elen, e);
388 snprintf(env[elen++], e, "profile=%s", ifp->profile);
389 }
390 if (ifp->wireless) {
391 static const char *pfx = "ifssid=";
392 size_t pfx_len;
393 ssize_t psl;
394
395 pfx_len = strlen(pfx);
396 psl = print_string(NULL, 0, ESCSTRING,
397 (const uint8_t *)ifp->ssid, ifp->ssid_len);
398 if (psl != -1) {
399 EMALLOC(elen, pfx_len + (size_t)psl + 1);
400 memcpy(env[elen], pfx, pfx_len);
401 print_string(env[elen] + pfx_len, (size_t)psl + 1,
402 ESCSTRING,
403 (const uint8_t *)ifp->ssid, ifp->ssid_len);
404 elen++;
405 }
406 }
407 #ifdef INET
408 if (dhcp && state && state->old) {
409 n = dhcp_env(NULL, NULL, state->old, ifp);
410 if (n == -1)
411 goto eexit;
412 if (n > 0) {
413 nenv = realloc(env, sizeof(char *) *
414 (elen + (size_t)n + 1));
415 if (nenv == NULL)
416 goto eexit;
417 env = nenv;
418 n = dhcp_env(env + elen, "old", state->old, ifp);
419 if (n == -1)
420 goto eexit;
421 elen += (size_t)n;
422 }
423 if (append_config(ifp->ctx, &env, &elen, "old",
424 (const char *const *)ifo->config) == -1)
425 goto eexit;
426 }
427 #endif
428 #ifdef INET6
429 if (dhcp6 && d6_state && ifo->options & DHCPCD_PFXDLGONLY) {
430 nenv = realloc(env, sizeof(char *) * (elen + 2));
431 if (nenv == NULL)
432 goto eexit;
433 env = nenv;
434 env[elen] = strdup("ifclass=pd");
435 if (env[elen] == NULL)
436 goto eexit;
437 elen++;
438 }
439 if (dhcp6 && d6_state && d6_state->old) {
440 n = dhcp6_env(NULL, NULL, ifp,
441 d6_state->old, d6_state->old_len);
442 if (n > 0) {
443 nenv = realloc(env, sizeof(char *) *
444 (elen + (size_t)n + 1));
445 if (nenv == NULL)
446 goto eexit;
447 env = nenv;
448 n = dhcp6_env(env + elen, "old", ifp,
449 d6_state->old, d6_state->old_len);
450 if (n == -1)
451 goto eexit;
452 elen += (size_t)n;
453 }
454 }
455 #endif
456
457 dumplease:
458 #ifdef INET
459 if (dhcp && state && state->new) {
460 n = dhcp_env(NULL, NULL, state->new, ifp);
461 if (n > 0) {
462 nenv = realloc(env, sizeof(char *) *
463 (elen + (size_t)n + 1));
464 if (nenv == NULL)
465 goto eexit;
466 env = nenv;
467 n = dhcp_env(env + elen, "new",
468 state->new, ifp);
469 if (n == -1)
470 goto eexit;
471 elen += (size_t)n;
472 }
473 if (append_config(ifp->ctx, &env, &elen, "new",
474 (const char *const *)ifo->config) == -1)
475 goto eexit;
476 }
477 #endif
478 #ifdef INET6
479 if (dhcp6 && D6_STATE_RUNNING(ifp)) {
480 n = dhcp6_env(NULL, NULL, ifp,
481 d6_state->new, d6_state->new_len);
482 if (n > 0) {
483 nenv = realloc(env, sizeof(char *) *
484 (elen + (size_t)n + 1));
485 if (nenv == NULL)
486 goto eexit;
487 env = nenv;
488 n = dhcp6_env(env + elen, "new", ifp,
489 d6_state->new, d6_state->new_len);
490 if (n == -1)
491 goto eexit;
492 elen += (size_t)n;
493 }
494 }
495 if (ra) {
496 n = ipv6nd_env(NULL, NULL, ifp);
497 if (n > 0) {
498 nenv = realloc(env, sizeof(char *) *
499 (elen + (size_t)n + 1));
500 if (nenv == NULL)
501 goto eexit;
502 env = nenv;
503 n = ipv6nd_env(env + elen, NULL, ifp);
504 if (n == -1)
505 goto eexit;
506 elen += (size_t)n;
507 }
508 }
509 #endif
510
511 /* Add our base environment */
512 if (ifo->environ) {
513 e = 0;
514 while (ifo->environ[e++])
515 ;
516 nenv = realloc(env, sizeof(char *) * (elen + e + 1));
517 if (nenv == NULL)
518 goto eexit;
519 env = nenv;
520 e = 0;
521 while (ifo->environ[e]) {
522 env[elen + e] = strdup(ifo->environ[e]);
523 if (env[elen + e] == NULL)
524 goto eexit;
525 e++;
526 }
527 elen += e;
528 }
529 env[elen] = NULL;
530
531 *argv = env;
532 return (ssize_t)elen;
533
534 eexit:
535 logger(ifp->ctx, LOG_ERR, "%s: %m", __func__);
536 if (env) {
537 nenv = env;
538 while (*nenv)
539 free(*nenv++);
540 free(env);
541 }
542 return -1;
543 }
544
545 static int
send_interface1(struct fd_list * fd,const struct interface * iface,const char * reason)546 send_interface1(struct fd_list *fd, const struct interface *iface,
547 const char *reason)
548 {
549 char **env, **ep, *s;
550 size_t elen;
551 int retval;
552
553 if (make_env(iface, reason, &env) == -1)
554 return -1;
555 s = NULL;
556 elen = (size_t)arraytostr((const char *const *)env, &s);
557 if ((ssize_t)elen == -1) {
558 free(s);
559 return -1;
560 }
561 retval = control_queue(fd, s, elen, 1);
562 ep = env;
563 while (*ep)
564 free(*ep++);
565 free(env);
566 return retval;
567 }
568
569 int
send_interface(struct fd_list * fd,const struct interface * ifp)570 send_interface(struct fd_list *fd, const struct interface *ifp)
571 {
572 const char *reason;
573 int retval = 0;
574 #ifdef INET
575 const struct dhcp_state *d;
576 #endif
577 #ifdef INET6
578 const struct dhcp6_state *d6;
579 #endif
580
581 switch (ifp->carrier) {
582 case LINK_UP:
583 reason = "CARRIER";
584 break;
585 case LINK_DOWN:
586 reason = "NOCARRIER";
587 break;
588 default:
589 reason = "UNKNOWN";
590 break;
591 }
592 if (send_interface1(fd, ifp, reason) == -1)
593 retval = -1;
594 #ifdef INET
595 if (D_STATE_RUNNING(ifp)) {
596 d = D_CSTATE(ifp);
597 if (send_interface1(fd, ifp, d->reason) == -1)
598 retval = -1;
599 }
600 #endif
601
602 #ifdef INET6
603 if (RS_STATE_RUNNING(ifp)) {
604 if (send_interface1(fd, ifp, "ROUTERADVERT") == -1)
605 retval = -1;
606 }
607 if (D6_STATE_RUNNING(ifp)) {
608 d6 = D6_CSTATE(ifp);
609 if (send_interface1(fd, ifp, d6->reason) == -1)
610 retval = -1;
611 }
612 #endif
613
614 return retval;
615 }
616
617 int
script_runreason(const struct interface * ifp,const char * reason)618 script_runreason(const struct interface *ifp, const char *reason)
619 {
620 char *argv[2];
621 char **env = NULL, **ep;
622 char *svcname, *path, *bigenv;
623 size_t e, elen = 0;
624 pid_t pid;
625 int status = 0;
626 struct fd_list *fd;
627
628 if (ifp->options->script &&
629 (ifp->options->script[0] == '\0' ||
630 strcmp(ifp->options->script, "/dev/null") == 0))
631 return 0;
632
633 argv[0] = ifp->options->script ? ifp->options->script : UNCONST(SCRIPT);
634 argv[1] = NULL;
635 logger(ifp->ctx, LOG_DEBUG, "%s: executing `%s' %s",
636 ifp->name, argv[0], reason);
637
638 /* Make our env */
639 elen = (size_t)make_env(ifp, reason, &env);
640 if (elen == (size_t)-1) {
641 logger(ifp->ctx, LOG_ERR, "%s: make_env: %m", ifp->name);
642 return -1;
643 }
644 /* Resize for PATH and RC_SVCNAME */
645 svcname = getenv(RC_SVCNAME);
646 ep = realloc(env, sizeof(char *) * (elen + 2 + (svcname ? 1 : 0)));
647 if (ep == NULL) {
648 elen = 0;
649 goto out;
650 }
651 env = ep;
652 /* Add path to it */
653 path = getenv("PATH");
654 if (path) {
655 e = strlen("PATH") + strlen(path) + 2;
656 env[elen] = malloc(e);
657 if (env[elen] == NULL) {
658 elen = 0;
659 goto out;
660 }
661 snprintf(env[elen], e, "PATH=%s", path);
662 } else {
663 env[elen] = strdup(DEFAULT_PATH);
664 if (env[elen] == NULL) {
665 elen = 0;
666 goto out;
667 }
668 }
669 if (svcname) {
670 e = strlen(RC_SVCNAME) + strlen(svcname) + 2;
671 env[++elen] = malloc(e);
672 if (env[elen] == NULL) {
673 elen = 0;
674 goto out;
675 }
676 snprintf(env[elen], e, "%s=%s", RC_SVCNAME, svcname);
677 }
678 env[++elen] = NULL;
679
680 pid = exec_script(ifp->ctx, argv, env);
681 if (pid == -1)
682 logger(ifp->ctx, LOG_ERR, "%s: %s: %m", __func__, argv[0]);
683 else if (pid != 0) {
684 /* Wait for the script to finish */
685 while (waitpid(pid, &status, 0) == -1) {
686 if (errno != EINTR) {
687 logger(ifp->ctx, LOG_ERR, "waitpid: %m");
688 status = 0;
689 break;
690 }
691 }
692 if (WIFEXITED(status)) {
693 if (WEXITSTATUS(status))
694 logger(ifp->ctx, LOG_ERR,
695 "%s: %s: WEXITSTATUS %d",
696 __func__, argv[0], WEXITSTATUS(status));
697 } else if (WIFSIGNALED(status))
698 logger(ifp->ctx, LOG_ERR, "%s: %s: %s",
699 __func__, argv[0], strsignal(WTERMSIG(status)));
700 }
701
702 /* Send to our listeners */
703 bigenv = NULL;
704 status = 0;
705 TAILQ_FOREACH(fd, &ifp->ctx->control_fds, next) {
706 if (!(fd->flags & FD_LISTEN))
707 continue;
708 if (bigenv == NULL) {
709 elen = (size_t)arraytostr((const char *const *)env,
710 &bigenv);
711 if ((ssize_t)elen == -1) {
712 logger(ifp->ctx, LOG_ERR, "%s: arraytostr: %m",
713 ifp->name);
714 break;
715 }
716 }
717 if (control_queue(fd, bigenv, elen, 1) == -1)
718 logger(ifp->ctx, LOG_ERR,
719 "%s: control_queue: %m", __func__);
720 else
721 status = 1;
722 }
723 if (!status)
724 free(bigenv);
725
726 out:
727 /* Cleanup */
728 ep = env;
729 while (*ep)
730 free(*ep++);
731 free(env);
732 if (elen == 0)
733 return -1;
734 return WEXITSTATUS(status);
735 }
736