3 * $Id: exec.c,v 1.8 2003/11/29 20:36:07 mdw Exp $
5 * Source and target for executable programs
7 * (c) 1999 Straylight/Edgeware
10 /*----- Licensing notice --------------------------------------------------*
12 * This file is part of the `fw' port forwarder.
14 * `fw' is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 2 of the License, or
17 * (at your option) any later version.
19 * `fw' is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
24 * You should have received a copy of the GNU General Public License
25 * along with `fw'; if not, write to the Free Software Foundation,
26 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
29 /*----- Revision history --------------------------------------------------*
32 * Revision 1.8 2003/11/29 20:36:07 mdw
33 * Privileged outgoing connections.
35 * Revision 1.7 2003/01/24 20:12:26 mdw
36 * Correctly cast uid and gid sentinel values. Parse full filenames in
37 * exec arguments (can't do it for program, unfortunately, since the die is
40 * Revision 1.6 2002/02/22 23:43:32 mdw
41 * Call @xfree@ rather than @free@.
43 * Revision 1.5 2002/01/13 14:49:03 mdw
44 * Track @lbuf@ changes in mLib.
46 * Revision 1.4 2001/02/03 20:30:03 mdw
47 * Support re-reading config files on SIGHUP.
49 * Revision 1.3 2000/07/01 11:28:52 mdw
50 * Use new mLib selbuf features.
52 * Revision 1.2 1999/10/22 22:46:17 mdw
53 * When a non-file endpoint is attached to a file, keep the file endpoint
54 * open until the nonfile is done. This stops socket sources from
55 * resetting their connection limits too early.
57 * Revision 1.1 1999/07/26 23:33:32 mdw
58 * New sources and targets.
62 /*----- Header files ------------------------------------------------------*/
75 #include <sys/types.h>
82 # include <sys/resource.h>
86 extern char **environ;
94 #include <mLib/alloc.h>
95 #include <mLib/dstr.h>
97 #include <mLib/fdflags.h>
98 #include <mLib/report.h>
100 #include <mLib/selbuf.h>
101 #include <mLib/sig.h>
102 #include <mLib/sub.h>
103 #include <mLib/sym.h>
115 /*----- Data structures ---------------------------------------------------*/
117 /* --- Resource usage --- */
119 #ifdef HAVE_SETRLIMIT
121 typedef struct xlimit {
122 #define R(r, n) struct rlimit n;
128 /* --- Environment variable modifications --- */
130 typedef struct xenv {
140 /* --- Program options --- */
142 typedef struct xopts {
151 #ifdef HAVE_SETRLIMIT
158 /* --- Executable program arguments --- */
160 typedef struct xargs {
166 #define XARGS_SZ(n) (sizeof(xargs) + sizeof(char *) * (n))
168 /* --- Executable endpoints --- */
170 typedef struct xept {
172 struct xept *next, *prev;
182 #define XEF_CLOSE 16u
185 /* --- Executable program data --- */
187 typedef struct xdata {
192 /* --- Executable source block --- */
194 typedef struct xsource {
199 /* --- Executable target block --- */
201 typedef struct xtarget {
206 /*----- Static variables --------------------------------------------------*/
208 static xopts exec_opts = { 1, 0, 0, 0, -1, -1, 0, &exec_opts.env };
209 static xept *xept_list;
211 static sym_table env;
213 /*----- Fiddling about with resource limits -------------------------------*/
215 #ifdef HAVE_SETRLIMIT
217 /* --- Table mapping user-level names to OS interface bits --- */
219 typedef struct rlimit_ent {
226 static rlimit_ent rlimits[] = {
227 #define R(r, n) { #n, #r, r, offsetof(xlimit, n) },
232 #define RLIMIT(xl, o) ((struct rlimit *)((char *)(xl) + (o)))
234 /* --- @rlimit_get@ --- *
236 * Arguments: @xlimit *xl@ = pointer to limit structure
240 * Use: Initializes a limit structure from the current limits.
243 static void rlimit_get(xlimit *xl)
247 for (r = rlimits; r->name; r++) {
248 if (getrlimit(r->r, RLIMIT(xl, r->off))) {
249 moan("couldn't read %s: %s", r->rname, strerror(errno));
255 /* --- @rlimit_set@ --- *
257 * Arguments: @xlimit *xl@ = pointer to limit structure
261 * Use: Sets resource limits from the supplied limits structure.
264 static void rlimit_set(xlimit *xl)
268 for (r = rlimits; r->name; r++) {
269 if (setrlimit(r->r, RLIMIT(xl, r->off))) {
270 moan("couldn't set %s: %s", r->rname, strerror(errno));
276 /* --- @rlimit_option@ --- */
278 static int rlimit_option(xlimit *xl, scanner *sc)
280 CONF_BEGIN(sc, "rlimit", "resource limit")
281 enum { w_soft, w_hard, w_both } which = w_both;
286 /* --- Find out which resource is being fiddled --- */
292 for (r = rlimits; r->name; r++) {
293 if (strncmp(sc->d.buf, r->name, sc->d.len) == 0) {
294 if (r->name[sc->d.len] == 0) {
298 error(sc, "ambiguous resource limit name `%s'", sc->d.buf);
306 rl = RLIMIT(xl, chosen->off);
309 /* --- Look for hard or soft restrictions --- */
315 if (sc->t == CTOK_WORD) {
316 if ((i = conf_enum(sc, "soft,hard",
317 ENUM_ABBREV | ENUM_NONE, "limit type")) != -1)
322 /* --- Now read the new value --- */
326 if (sc->t != CTOK_WORD)
327 error(sc, "parse error, expected limit value");
329 if (conf_enum(sc, "unlimited,infinity",
330 ENUM_ABBREV | ENUM_NONE, "limit value") > -1)
335 v = strtol(sc->d.buf, &p, 0);
337 error(sc, "parse error, invalid limit value `%s'", sc->d.buf);
338 switch (tolower((unsigned char)*p)) {
340 case 'b': v *= 512; break;
343 case 'k': v *= 1024; break;
344 default: error(sc, "parse error, invalid limit scale `%c'", *p);
349 /* --- Store the limit value away --- */
357 if (v > rl->rlim_max)
358 error(sc, "soft limit %l exceeds hard limit %l for %s",
359 v, rl->rlim_max, chosen->rname);
364 if (rl->rlim_cur > v)
375 /*----- Environment fiddling ----------------------------------------------*/
377 /* --- @xenv_option@ --- *
379 * Arguments: @xopts *xo@ = pointer to options block
380 * @scanner *sc@ = pointer to scanner
382 * Returns: Nonzero if claimed
384 * Use: Parses environment variable assignments.
387 static int xenv_option(xopts *xo, scanner *sc)
389 CONF_BEGIN(sc, "env", "environment")
392 /* --- Unset a variable --- */
394 if (strcmp(sc->d.buf, "unset") == 0) {
396 if (sc->t != CTOK_WORD)
397 error(sc, "parse error, expected environment variable name");
399 xe->name = xstrdup(sc->d.buf);
406 /* --- Clear the entire environment --- */
408 if (strcmp(sc->d.buf, "clear") == 0) {
415 /* --- Allow `set' to be omitted if there's a prefix --- */
417 if (strcmp(sc->d.buf, "set") == 0)
422 /* --- Set a variable --- */
424 if (sc->t != CTOK_WORD)
425 error(sc, "parse error, expected environment variable name");
427 xe->name = xstrdup(sc->d.buf);
431 if (sc->t != CTOK_WORD)
432 error(sc, "parse error, expected environment variable value");
433 xe->value = xstrdup(sc->d.buf);
441 xo->etail = &xe->next;
444 /* --- Nothing else to try --- */
449 /* --- @xenv_apply@ --- *
451 * Arguments: @xenv *xe@ = pointer to a variable change list
455 * Use: Modifies the environment (in @env@) according to the list.
458 static void xenv_apply(xenv *xe)
463 env_put(&env, xe->name, xe->value);
474 /* --- @xenv_destroy@ --- *
476 * Arguments: @xenv *xe@ = pointer to a variable change list
480 * Use: Frees the memory used by an environment variable change list.
483 static void xenv_destroy(xenv *xe)
495 /*----- Miscellaneous good things -----------------------------------------*/
497 /* --- @x_tidy@ --- *
499 * Arguments: @xargs *xa@ = pointer to an arguments block
500 * @xopts *xo@ = pointer to an options block
504 * Use: Releases a reference to argument and options blocks.
507 static void x_tidy(xargs *xa, xopts *xo)
515 xenv_destroy(xo->env);
520 /*----- Executable endpoints ----------------------------------------------*/
522 /* --- @attach@ --- */
524 static void xept_error(char */*p*/, size_t /*len*/, void */*v*/);
526 static void xept_attach(endpt *e, reffd *in, reffd *out)
528 xept *xe = (xept *)e;
532 /* --- Make a pipe for standard error --- */
535 fw_log(-1, "[%s] couldn't create pipe: %s", xe->desc, strerror(errno));
538 fdflags(fd[0], O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
540 /* --- Fork a child, and handle an error if there was one --- */
542 if ((kid = fork()) == -1) {
543 fw_log(-1, "[%s] couldn't fork: %s", xe->desc, strerror(errno));
549 /* --- Do the child thing --- */
554 /* --- Fiddle with the file descriptors --- *
556 * Attach the other endpoint's descriptors to standard input and output.
557 * Attach my pipe to standard error. Mark everything as blocking and
558 * not-to-be-closed-on-exec at this end.
562 if (dup2(in->fd, STDIN_FILENO) < 0 ||
563 dup2(out->fd, STDOUT_FILENO) < 0 ||
564 dup2(fd[1], STDERR_FILENO) < 0) {
565 moan("couldn't manipulate file descriptors: %s", strerror(errno));
574 fdflags(STDIN_FILENO, O_NONBLOCK, 0, FD_CLOEXEC, 0);
575 fdflags(STDOUT_FILENO, O_NONBLOCK, 0, FD_CLOEXEC, 0);
576 fdflags(STDERR_FILENO, O_NONBLOCK, 0, FD_CLOEXEC, 0);
578 /* --- First of all set the @chroot@ prison --- */
580 if (xo->root && chroot(xo->root)) {
581 moan("couldn't set `%s' as filesystem root: %s",
582 xo->root, strerror(errno));
586 /* --- Now set the current directory --- */
588 if (xo->dir ? chdir(xo->dir) : xo->root ? chdir("/") : 0) {
589 moan("couldn't set `%s' as current directory: %s",
590 xo->dir ? xo->dir : "/", strerror(errno));
594 /* --- Set the resource limits --- */
596 #ifdef HAVE_SETRLIMIT
600 /* --- Set group id --- */
602 if (xo->gid != (gid_t)-1) {
603 if (setgid(xo->gid)) {
604 moan("couldn't set gid %i: %s", xo->gid, strerror(errno));
607 #ifdef HAVE_SETGROUPS
608 if (setgroups(1, &xo->gid))
609 moan("warning: couldn't set group list to %i: %s", xo->gid,
614 /* --- Set uid --- */
616 if (xo->uid != (uid_t)-1) {
617 if (setuid(xo->uid)) {
618 moan("couldn't set uid %i: %s", xo->uid, strerror(errno));
623 /* --- Play with signal dispositions --- */
625 signal(SIGPIPE, SIG_DFL);
627 /* --- Fiddle with the environment --- */
629 xenv_apply(exec_opts.env);
630 xenv_apply(xe->xo->env);
631 environ = env_export(&env);
633 /* --- Run the program --- */
635 execvp(xe->xa->file, xe->xa->argv);
636 moan("couldn't execute `%s': %s", xe->xa->file, strerror(errno));
640 /* --- The child's done; see to the parent --- */
643 selbuf_init(&xe->err, sel, fd[0], xept_error, xe);
645 xe->next = xept_list;
648 xept_list->prev = xe;
650 if (!(xe->xo->f & XF_NOLOG))
651 fw_log(-1, "[%s] started with pid %i", xe->desc, kid);
656 /* --- @xept_file@ --- */
658 static void xept_file(endpt *e, endpt *f)
660 xept *xe = (xept *)e;
664 /* --- @xept_close@ --- */
666 static void xept_close(endpt *e)
668 xept *xe = (xept *)e;
671 xe->f->ops->close(xe->f);
672 x_tidy(xe->xa, xe->xo);
678 /* --- @xept_destroy@ --- */
680 static void xept_destroy(xept *xe)
682 /* --- First emit the news about the process --- */
684 if (xe->xo->f & XF_NOLOG)
686 else if (WIFEXITED(xe->st)) {
687 if (WEXITSTATUS(xe->st) == 0)
688 fw_log(-1, "[%s] pid %i exited successfully", xe->desc, xe->kid);
690 fw_log(-1, "[%s] pid %i failed: status %i",
691 xe->desc, xe->kid, WEXITSTATUS(xe->st));
693 } else if (WIFSIGNALED(xe->st)) {
695 #ifdef HAVE_STRSIGNAL
696 s = strsignal(WTERMSIG(xe->st));
697 #elif HAVE__SYS_SIGLIST
698 s = _sys_siglist[WTERMSIG(xe->st)];
701 sprintf(buf, "signal %i", WTERMSIG(xe->st));
704 fw_log(-1, "[%s] pid %i failed: %s", xe->desc, xe->kid, s);
706 fw_log(-1, "[%s] pid %i failed: unrecognized status", xe->desc, xe->kid);
708 /* --- Free up the parent-side resources --- */
711 xe->next->prev = xe->prev;
713 xe->prev->next = xe->next;
715 xept_list = xe->next;
719 xe->f->ops->close(xe->f);
720 x_tidy(xe->xa, xe->xo);
725 /* --- @xept_chld@ --- *
727 * Arguments: @int n@ = signal number
728 * @void *p@ = uninteresting pointer
732 * Use: Deals with child death situations.
735 static void xept_chld(int n, void *p)
740 while ((kid = waitpid(-1, &st, WNOHANG)) > 0) {
741 xept *xe = xept_list;
745 if (kid == xxe->kid) {
747 xxe->e.f |= XEF_EXIT;
748 if (xxe->e.f & XEF_CLOSE)
756 /* --- @xept_error@ --- *
758 * Arguments: @char *p@ = pointer to string read from stderr
759 * @size_t len@ = length of the string
760 * @void *v@ = pointer to by endpoint
764 * Use: Handles error reports from a child process.
767 static void xept_error(char *p, size_t len, void *v)
771 fw_log(-1, "[%s] pid %i: %s", xe->desc, xe->kid, p);
773 close(xe->err.reader.fd);
774 selbuf_destroy(&xe->err);
775 xe->e.f |= XEF_CLOSE;
776 if (xe->e.f & XEF_EXIT)
781 /* --- Endpoint operations --- */
783 static endpt_ops xept_ops = { xept_attach, xept_file, 0, xept_close };
785 /*----- General operations on sources and targets -------------------------*/
787 /* --- @exec_init@ --- *
793 * Use: Initializes the executable problem source and target.
798 #ifdef HAVE_SETRLIMIT
799 rlimit_get(&exec_opts.xl);
801 sig_add(&xept_sig, SIGCHLD, xept_chld, 0);
803 env_import(&env, environ);
806 /* --- @exec_option@ --- */
808 static int exec_option(xdata *x, scanner *sc)
810 xopts *xo = x ? x->xo : &exec_opts;
812 CONF_BEGIN(sc, "exec", "executable");
814 /* --- Logging settings --- */
816 if (strcmp(sc->d.buf, "logging") == 0 ||
817 strcmp(sc->d.buf, "log") == 0) {
821 if (conf_enum(sc, "no,yes", ENUM_ABBREV, "logging status"))
828 /* --- Current directory setting --- *
830 * Lots of possibilities to guard against possible brainoes.
833 if (strcmp(sc->d.buf, "dir") == 0 ||
834 strcmp(sc->d.buf, "cd") == 0 ||
835 strcmp(sc->d.buf, "chdir") == 0 ||
836 strcmp(sc->d.buf, "cwd") == 0) {
841 conf_name(sc, '/', &d);
842 xo->dir = xstrdup(d.buf);
847 /* --- Set a chroot prison --- */
849 if (strcmp(sc->d.buf, "root") == 0 ||
850 strcmp(sc->d.buf, "chroot") == 0) {
855 conf_name(sc, '/', &d);
856 xo->root = xstrdup(d.buf);
861 /* --- Set the target user id --- */
863 if (strcmp(sc->d.buf, "uid") == 0 ||
864 strcmp(sc->d.buf, "user") == 0) {
868 if (sc->t != CTOK_WORD)
869 error(sc, "parse error, expected user name or uid");
870 if (isdigit((unsigned char)*sc->d.buf))
871 xo->uid = atoi(sc->d.buf);
873 struct passwd *pw = getpwnam(sc->d.buf);
875 error(sc, "unknown user name `%s'", sc->d.buf);
876 xo->uid = pw->pw_uid;
882 /* --- Set the target group id --- */
884 if (strcmp(sc->d.buf, "gid") == 0 ||
885 strcmp(sc->d.buf, "group") == 0) {
889 if (sc->t != CTOK_WORD)
890 error(sc, "parse error, expected group name or gid");
891 if (isdigit((unsigned char)*sc->d.buf))
892 xo->gid = atoi(sc->d.buf);
894 struct group *gr = getgrnam(sc->d.buf);
896 error(sc, "unknown user name `%s'", sc->d.buf);
897 xo->gid = gr->gr_gid;
903 /* --- Now try resource limit settings --- */
905 #ifdef HAVE_SETRLIMIT
906 if (rlimit_option(&xo->xl, sc))
910 /* --- And then environment settings --- */
912 if (xenv_option(xo, sc))
915 /* --- Nothing found --- */
920 /* --- @exec_desc@ --- */
922 static void exec_desc(xdata *x, dstr *d)
926 dstr_puts(d, "exec ");
927 if (strcmp(x->xa->file, x->xa->argv[0]) != 0) {
928 dstr_puts(d, x->xa->file);
931 for (p = x->xa->argv; *p; p++) {
940 /* --- @exec_read@ --- */
942 static void exec_read(xdata *x, scanner *sc)
948 /* --- Read the first word --- *
950 * This is either a shell command or the actual program to run.
953 if (sc->t == CTOK_WORD) {
954 dstr_putd(&d, &sc->d); d.len++;
959 /* --- See if there's a list of arguments --- *
961 * If not, then the thing I saw was a shell command, so build the proper
962 * arguments for that.
968 error(sc, "parse error, expected shell command or argument list");
969 xa = xmalloc(XARGS_SZ(3) + 8 + 3 + d.len);
970 p = (char *)(xa->argv + 4);
973 xa->argv[0] = p; memcpy(p, "/bin/sh", 8); p += 8;
974 xa->argv[1] = p; memcpy(p, "-c", 3); p += 3;
975 xa->argv[2] = p; memcpy(p, d.buf, d.len); p += d.len;
979 /* --- Snarf in a list of arguments --- */
986 /* --- Strip off the leading `[' --- *
988 * Allow various handy filename characters to be entered without quoting.
991 conf_undelim(sc, "=:/.", "=:/.");
994 /* --- Read a sequence of arguments --- */
996 while (sc->t == CTOK_WORD) {
997 dstr_putd(&d, &sc->d); d.len++;
1001 conf_undelim(sc, 0, 0);
1003 /* --- Expect the closing `]' --- */
1006 error(sc, "parse error, missing `]'");
1009 /* --- If there are no arguments, whinge --- */
1012 error(sc, "must specify at least one argument");
1014 /* --- Allocate a lump of memory for the array --- */
1016 xa = xmalloc(XARGS_SZ(argc) + d.len);
1019 p = (char *)(v + argc + 1);
1020 memcpy(p, d.buf, d.len);
1025 /* --- Start dumping addresses into the @argv@ array --- */
1029 while (*p++ && p < q)
1037 /* --- Do some other setting up --- */
1041 x->xo = CREATE(xopts);
1047 /* --- @exec_endpt@ --- */
1049 static endpt *exec_endpt(xdata *x, const char *desc)
1051 xept *xe = CREATE(xept);
1052 xe->e.ops = &xept_ops;
1056 xe->xa = x->xa; xe->xa->ref++;
1057 xe->xo = x->xo; xe->xo->ref++;
1060 xe->desc = xstrdup(desc);
1064 /* --- @exec_destroy@ --- */
1066 static void exec_destroy(xdata *x)
1068 x_tidy(x->xa, x->xo);
1071 /*----- Source definition -------------------------------------------------*/
1073 /* --- @option@ --- */
1075 static int xsource_option(source *s, scanner *sc)
1077 xsource *xs = (xsource *)s;
1078 return (exec_option(xs ? &xs->x : 0, sc));
1081 /* --- @read@ --- */
1083 static source *xsource_read(scanner *sc)
1087 if (!conf_prefix(sc, "exec"))
1089 xs = CREATE(xsource);
1090 xs->s.ops = &xsource_ops;
1092 exec_read(&xs->x, sc);
1096 /* --- @attach@ --- */
1098 static void xsource_destroy(source */*s*/);
1100 static void xsource_attach(source *s, scanner *sc, target *t)
1102 xsource *xs = (xsource *)s;
1105 /* --- Set up the source description string --- */
1109 exec_desc(&xs->x, &d);
1110 dstr_puts(&d, " -> ");
1111 dstr_puts(&d, t->desc);
1112 xs->s.desc = xstrdup(d.buf);
1116 /* --- Create the endpoints --- */
1118 if ((ee = t->ops->create(t, xs->s.desc)) == 0)
1120 if ((e = exec_endpt(&xs->x, xs->s.desc)) == 0) {
1126 /* --- Dispose of source and target --- */
1130 xsource_destroy(&xs->s);
1133 /* --- @destroy@ --- */
1135 static void xsource_destroy(source *s)
1137 xsource *xs = (xsource *)s;
1139 exec_destroy(&xs->x);
1143 /* --- Executable source operations --- */
1145 source_ops xsource_ops = {
1147 xsource_option, xsource_read, xsource_attach, xsource_destroy
1150 /*----- Exec target description -------------------------------------------*/
1152 /* --- @option@ --- */
1154 static int xtarget_option(target *t, scanner *sc)
1156 xtarget *xt = (xtarget *)t;
1157 return (exec_option(xt ? &xt->x : 0, sc));
1160 /* --- @read@ --- */
1162 static target *xtarget_read(scanner *sc)
1167 if (!conf_prefix(sc, "exec"))
1169 xt = CREATE(xtarget);
1170 xt->t.ops = &xtarget_ops;
1171 exec_read(&xt->x, sc);
1172 exec_desc(&xt->x, &d);
1173 xt->t.desc = xstrdup(d.buf);
1178 /* --- @create@ --- */
1180 static endpt *xtarget_create(target *t, const char *desc)
1182 xtarget *xt = (xtarget *)t;
1183 endpt *e = exec_endpt(&xt->x, desc);
1187 /* --- @destroy@ --- */
1189 static void xtarget_destroy(target *t)
1191 xtarget *xt = (xtarget *)t;
1193 exec_destroy(&xt->x);
1197 /* --- Exec target operations --- */
1199 target_ops xtarget_ops = {
1201 xtarget_option, xtarget_read, 0, xtarget_create, xtarget_destroy
1204 /*----- That's all, folks -------------------------------------------------*/