From d11e7febbcf5a83cc581fc565c7049a2ed67fd69 Mon Sep 17 00:00:00 2001 From: "Todd C. Miller" Date: Wed, 25 Jan 2012 14:58:02 -0500 Subject: [PATCH] Refactor disable_execute() and my_execve() into exec_common.c for use by sesh.c. This fixes NOEXEC when SELinux is used. Instead of disabling exec in exec_setup(), disable it immediately before executing the command. Adapted from a diff by Arno Schuring. --- MANIFEST | 1 + src/Makefile.in | 21 +++++-- src/exec.c | 40 ++++--------- src/exec_common.c | 149 ++++++++++++++++++++++++++++++++++++++++++++++ src/exec_pty.c | 12 ++-- src/selinux.c | 18 ++++-- src/sesh.c | 47 +++++++++++---- src/sudo.c | 83 +------------------------- src/sudo.h | 5 +- src/sudo_exec.h | 4 +- 10 files changed, 240 insertions(+), 140 deletions(-) create mode 100644 src/exec_common.c diff --git a/MANIFEST b/MANIFEST index 2447ab812..4ee460482 100644 --- a/MANIFEST +++ b/MANIFEST @@ -267,6 +267,7 @@ src/Makefile.in src/conversation.c src/error.c src/exec.c +src/exec_common.c src/exec_pty.c src/get_pty.c src/load_plugins.c diff --git a/src/Makefile.in b/src/Makefile.in index 7542d5aa3..5315a29f1 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -73,8 +73,8 @@ SHELL = @SHELL@ PROGS = @PROGS@ -OBJS = conversation.o error.o exec.o exec_pty.o get_pty.o net_ifs.o \ - load_plugins.o parse_args.o sudo.o sudo_edit.o tgetpass.o \ +OBJS = conversation.o error.o exec.o exec_common.o exec_pty.o get_pty.o \ + net_ifs.o load_plugins.o parse_args.o sudo.o sudo_edit.o tgetpass.o \ ttyname.o ttysize.o utmp.o @SUDO_OBJS@ LIBOBJDIR = $(top_builddir)/@ac_config_libobj_dir@/ @@ -100,8 +100,8 @@ sudo: $(OBJS) $(LT_LIBS) libsudo_noexec.la: sudo_noexec.lo $(LIBTOOL) --mode=link $(CC) $(LDFLAGS) $(LTLDFLAGS) -o $@ sudo_noexec.lo -avoid-version -rpath $(noexecdir) -sesh: sesh.o - $(CC) -o $@ sesh.o +sesh: sesh.o error.o exec_common.o @LIBINTL@ $(LT_LIBS) + $(LIBTOOL) --mode=link $(CC) -o $@ sesh.o error.o exec_common.o @LIBINTL@ $(LIBS) -static-libtool-libs pre-install: @@ -170,6 +170,13 @@ exec.o: $(srcdir)/exec.c $(top_builddir)/config.h $(srcdir)/sudo.h \ $(srcdir)/sudo_exec.h $(incdir)/sudo_plugin.h \ $(srcdir)/sudo_plugin_int.h $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(srcdir)/exec.c +exec_common.o: $(srcdir)/exec_common.c $(top_builddir)/config.h \ + $(srcdir)/sudo.h $(top_builddir)/pathnames.h \ + $(top_srcdir)/compat/stdbool.h $(incdir)/missing.h \ + $(incdir)/alloc.h $(incdir)/error.h $(incdir)/fileops.h \ + $(incdir)/list.h $(incdir)/sudo_conf.h $(incdir)/list.h \ + $(incdir)/sudo_debug.h $(incdir)/gettext.h $(srcdir)/sudo_exec.h + $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(srcdir)/exec_common.c exec_pty.o: $(srcdir)/exec_pty.c $(top_builddir)/config.h $(srcdir)/sudo.h \ $(top_builddir)/pathnames.h $(top_srcdir)/compat/stdbool.h \ $(incdir)/missing.h $(incdir)/alloc.h $(incdir)/error.h \ @@ -212,8 +219,10 @@ selinux.o: $(srcdir)/selinux.c $(top_builddir)/config.h $(srcdir)/sudo.h \ $(incdir)/fileops.h $(incdir)/list.h $(incdir)/sudo_conf.h \ $(incdir)/list.h $(incdir)/sudo_debug.h $(incdir)/gettext.h $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(srcdir)/selinux.c -sesh.o: $(srcdir)/sesh.c $(top_builddir)/config.h $(incdir)/missing.h \ - $(incdir)/gettext.h +sesh.o: $(srcdir)/sesh.c $(top_builddir)/config.h \ + $(top_srcdir)/compat/stdbool.h $(incdir)/missing.h $(incdir)/gettext.h \ + $(incdir)/error.h $(incdir)/sudo_conf.h $(incdir)/list.h \ + $(incdir)/sudo_debug.h $(srcdir)/sudo_exec.h $(incdir)/sudo_plugin.h $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(srcdir)/sesh.c sudo.o: $(srcdir)/sudo.c $(top_builddir)/config.h $(srcdir)/sudo.h \ $(top_builddir)/pathnames.h $(top_srcdir)/compat/stdbool.h \ diff --git a/src/exec.c b/src/exec.c index 9deb5a25e..9b63eae5a 100644 --- a/src/exec.c +++ b/src/exec.c @@ -82,30 +82,6 @@ static void schedule_signal(int signo); static void handler_nofwd(int s, siginfo_t *info, void *context); #endif -/* - * Like execve(2) but falls back to running through /bin/sh - * ala execvp(3) if we get ENOEXEC. - */ -int -my_execve(const char *path, char *const argv[], char *const envp[]) -{ - execve(path, argv, envp); - if (errno == ENOEXEC) { - int argc; - char **nargv; - - for (argc = 0; argv[argc] != NULL; argc++) - continue; - nargv = emalloc2(argc + 2, sizeof(char *)); - nargv[0] = "sh"; - nargv[1] = (char *)path; - memcpy(nargv + 2, argv + 1, argc * sizeof(char *)); - execve(_PATH_BSHELL, nargv, envp); - efree(nargv); - } - return -1; -} - /* * Fork and execute a command, returns the child's pid. * Sends errno back on sv[1] if execve() fails. @@ -149,11 +125,15 @@ static int fork_cmnd(struct command_details *details, int sv[2]) closefrom(maxfd); } #ifdef HAVE_SELINUX - if (ISSET(details->flags, CD_RBAC_ENABLED)) - selinux_execve(details->command, details->argv, details->envp); - else + if (ISSET(details->flags, CD_RBAC_ENABLED)) { + selinux_execve(details->command, details->argv, details->envp, + ISSET(details->flags, CD_NOEXEC)); + } else #endif - my_execve(details->command, details->argv, details->envp); + { + sudo_execve(details->command, details->argv, details->envp, + ISSET(details->flags, CD_NOEXEC)); + } sudo_debug_printf(SUDO_DEBUG_ERROR, "unable to exec %s: %s", details->command, strerror(errno)); } @@ -222,7 +202,7 @@ restore_signals(void) * we fact that we have two different controlling terminals to deal with. */ int -sudo_execve(struct command_details *details, struct command_status *cstat) +sudo_execute(struct command_details *details, struct command_status *cstat) { int maxfd, n, nready, sv[2]; const char *utmp_user = NULL; @@ -230,7 +210,7 @@ sudo_execve(struct command_details *details, struct command_status *cstat) fd_set *fdsr, *fdsw; sigaction_t sa; pid_t child; - debug_decl(sudo_execve, SUDO_DEBUG_EXEC) + debug_decl(sudo_execute, SUDO_DEBUG_EXEC) /* If running in background mode, fork and exit. */ if (ISSET(details->flags, CD_BACKGROUND)) { diff --git a/src/exec_common.c b/src/exec_common.c new file mode 100644 index 000000000..660bda7d0 --- /dev/null +++ b/src/exec_common.c @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2009-2012 Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include +#include +#include +#ifdef STDC_HEADERS +# include +# include +#else +# ifdef HAVE_STDLIB_H +# include +# endif +#endif /* STDC_HEADERS */ +#ifdef HAVE_STRING_H +# include +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include +#endif /* HAVE_STRINGS_H */ +#ifdef HAVE_UNISTD_H +# include +#endif /* HAVE_UNISTD_H */ +#ifdef HAVE_PRIV_SET +# include +#endif +#include + +#include "sudo.h" +#include "sudo_exec.h" + +/* + * Disable execution of child processes in the command we are about + * to run. On systems with privilege sets, we can remove the exec + * privilege. On other systems we use LD_PRELOAD and the like. + */ +static char * const * +disable_execute(char *const envp[]) +{ +#ifdef _PATH_SUDO_NOEXEC + char * const *ev; + char *cp, **nenvp; + int env_len = 0, env_size = 128; +#endif /* _PATH_SUDO_NOEXEC */ + debug_decl(disable_execute, SUDO_DEBUG_UTIL) + +#ifdef HAVE_PRIV_SET + /* Solaris privileges, remove PRIV_PROC_EXEC post-execve. */ + if (priv_set(PRIV_OFF, PRIV_LIMIT, "PRIV_PROC_EXEC", NULL) == 0) + debug_return_ptr(envp); + warning(_("unable to remove PRIV_PROC_EXEC from PRIV_LIMIT")); +#endif /* HAVE_PRIV_SET */ + +#ifdef _PATH_SUDO_NOEXEC + nenvp = emalloc2(env_size, sizeof(char *)); + for (ev = envp; *ev != NULL; ev++) { + if (env_len + 2 > env_size) { + env_size += 128; + nenvp = erealloc3(nenvp, env_size, sizeof(char *)); + } + /* + * Prune out existing preloaded libraries. + * XXX - should save and append instead of replacing. + */ +# if defined(__darwin__) || defined(__APPLE__) + if (strncmp(*ev, "DYLD_INSERT_LIBRARIES=", sizeof("DYLD_INSERT_LIBRARIES=") - 1) == 0) + continue; + if (strncmp(*ev, "DYLD_FORCE_FLAT_NAMESPACE=", sizeof("DYLD_INSERT_LIBRARIES=") - 1) == 0) + continue; +# elif defined(__osf__) || defined(__sgi) + if (strncmp(*ev, "_RLD_LIST=", sizeof("_RLD_LIST=") - 1) == 0) + continue; +# elif defined(_AIX) + if (strncmp(*ev, "LDR_PRELOAD=", sizeof("LDR_PRELOAD=") - 1) == 0) + continue; +# else + if (strncmp(*ev, "LD_PRELOAD=", sizeof("LD_PRELOAD=") - 1) == 0) + continue; +# endif + nenvp[env_len++] = *ev; + } + + /* + * Preload a noexec file? For a list of LD_PRELOAD-alikes, see + * http://www.fortran-2000.com/ArnaudRecipes/sharedlib.html + * XXX - need to support 32-bit and 64-bit variants + */ +# if defined(__darwin__) || defined(__APPLE__) + nenvp[env_len++] = "DYLD_FORCE_FLAT_NAMESPACE="; + cp = fmt_string("DYLD_INSERT_LIBRARIES", sudo_conf_noexec_path()); +# elif defined(__osf__) || defined(__sgi) + easprintf(&cp, "_RLD_LIST=%s:DEFAULT", sudo_conf_noexec_path()); +# elif defined(_AIX) + cp = fmt_string("LDR_PRELOAD", sudo_conf_noexec_path()); +# else + cp = fmt_string("LD_PRELOAD", sudo_conf_noexec_path()); +# endif + if (cp == NULL) + errorx(1, _("unable to allocate memory")); + nenvp[env_len++] = cp; + nenvp[env_len] = NULL; + envp = nenvp; +#endif /* _PATH_SUDO_NOEXEC */ + + debug_return_ptr(envp); +} + +/* + * Like execve(2) but falls back to running through /bin/sh + * ala execvp(3) if we get ENOEXEC. + */ +int +sudo_execve(const char *path, char *const argv[], char *const envp[], int noexec) +{ + /* Modify the environment as needed to disable further execve(). */ + if (noexec) + envp = disable_execute(envp); + + execve(path, argv, envp); + if (errno == ENOEXEC) { + int argc; + char **nargv; + + for (argc = 0; argv[argc] != NULL; argc++) + continue; + nargv = emalloc2(argc + 2, sizeof(char *)); + nargv[0] = "sh"; + nargv[1] = (char *)path; + memcpy(nargv + 2, argv + 1, argc * sizeof(char *)); + execve(_PATH_BSHELL, nargv, envp); + efree(nargv); + } + return -1; +} diff --git a/src/exec_pty.c b/src/exec_pty.c index 29ad9775e..3175813be 100644 --- a/src/exec_pty.c +++ b/src/exec_pty.c @@ -1190,11 +1190,15 @@ exec_pty(struct command_details *details, int *errfd) closefrom(maxfd); } #ifdef HAVE_SELINUX - if (ISSET(details->flags, CD_RBAC_ENABLED)) - selinux_execve(details->command, details->argv, details->envp); - else + if (ISSET(details->flags, CD_RBAC_ENABLED)) { + selinux_execve(details->command, details->argv, details->envp, + ISSET(details->flags, CD_NOEXEC)); + } else #endif - my_execve(details->command, details->argv, details->envp); + { + sudo_execve(details->command, details->argv, details->envp, + ISSET(details->flags, CD_NOEXEC)); + } sudo_debug_printf(SUDO_DEBUG_ERROR, "unable to exec %s: %s", details->command, strerror(errno)); debug_return; diff --git a/src/selinux.c b/src/selinux.c index a842aadcc..23ffbd0f7 100644 --- a/src/selinux.c +++ b/src/selinux.c @@ -361,7 +361,8 @@ done: } void -selinux_execve(const char *path, char *argv[], char *envp[]) +selinux_execve(const char *path, char *const argv[], char *const envp[], + int noexec) { char **nargv; int argc, serrno; @@ -381,16 +382,23 @@ selinux_execve(const char *path, char *argv[], char *envp[]) } #endif /* HAVE_SETKEYCREATECON */ + /* + * Build new argv with sesh as argv[0]. + * If argv[0] ends in -noexec, sesh will disable execute + * for the command it runs. + */ for (argc = 0; argv[argc] != NULL; argc++) continue; - - /* Build new argv with sesh as argv[0]. */ nargv = emalloc2(argc + 2, sizeof(char *)); - nargv[0] = *argv[0] == '-' ? "-sesh" : "sesh"; + if (noexec) + nargv[0] = *argv[0] == '-' ? "-sesh-noexec" : "sesh-noexec"; + else + nargv[0] = *argv[0] == '-' ? "-sesh" : "sesh"; nargv[1] = (char *)path; memcpy(&nargv[2], &argv[1], argc * sizeof(char *)); /* copies NULL */ - execve(_PATH_SUDO_SESH, nargv, envp); + /* sesh will handle noexec for us. */ + sudo_execve(_PATH_SUDO_SESH, nargv, envp, 0); serrno = errno; free(nargv); errno = serrno; diff --git a/src/sesh.c b/src/sesh.c index f6818ce60..0c0eb25de 100644 --- a/src/sesh.c +++ b/src/sesh.c @@ -19,8 +19,6 @@ #include #include -#include -#include #include #include #include @@ -29,14 +27,37 @@ #ifdef HAVE_SETLOCALE # include #endif +#ifdef HAVE_STDBOOL_H +# include +#else +# include "compat/stdbool.h" +#endif /* HAVE_STDBOOL_H */ #include "missing.h" #include "gettext.h" +#include "error.h" +#include "sudo_conf.h" +#include "sudo_debug.h" +#include "sudo_exec.h" +#include "sudo_plugin.h" + +sudo_conv_t sudo_conv; /* NULL in non-plugin */ + +/* + * Cleanup hook for error()/errorx() + */ +void +cleanup(int gotsignal) +{ + return; +} int -main (int argc, char *argv[]) +main(int argc, char *argv[], char *envp[]) { char *cp, *cmnd; + int noexec = 0; + debug_decl(main, SUDO_DEBUG_MAIN) #ifdef HAVE_SETLOCALE setlocale(LC_ALL, ""); @@ -45,22 +66,28 @@ main (int argc, char *argv[]) textdomain(PACKAGE_NAME); if (argc < 2) - errx(EXIT_FAILURE, _("requires at least one argument")); + errorx(EXIT_FAILURE, _("requires at least one argument")); + + /* Read sudo.conf. */ + sudo_conf_read(); + + /* If argv[0] ends in -noexec, pass the flag to sudo_execve() */ + if ((cp = strrchr(argv[0], '-')) != NULL && cp != argv[0]) + noexec = strcmp(cp, "-noexec") == 0; /* Shift argv and make a copy of the command to execute. */ argv++; argc--; - cmnd = strdup(argv[0]); - if (cmnd == NULL) - err(EXIT_FAILURE, NULL); + cmnd = estrdup(argv[0]); /* If invoked as a login shell, modify argv[0] accordingly. */ - if (argv[0][0] == '-') { + if (argv[-1][0] == '-') { if ((cp = strrchr(argv[0], '/')) == NULL) cp = argv[0]; *cp = '-'; } - execv(cmnd, argv); - warn(_("unable to execute %s"), argv[0]); + sudo_execve(cmnd, argv, envp, noexec); + warning(_("unable to execute %s"), argv[0]); + sudo_debug_exit_int(__func__, __FILE__, __LINE__, sudo_debug_subsys, EXIT_FAILURE); _exit(EXIT_FAILURE); } diff --git a/src/sudo.c b/src/sudo.c index 5aeb7ec71..49d042e36 100644 --- a/src/sudo.c +++ b/src/sudo.c @@ -83,9 +83,6 @@ # endif /* __hpux */ # include #endif /* HAVE_GETPRPWNAM && HAVE_SET_AUTH_PARAMETERS */ -#ifdef HAVE_PRIV_SET -# include -#endif #if defined(HAVE_STRUCT_KINFO_PROC_P_TDEV) || defined (HAVE_STRUCT_KINFO_PROC_KP_EPROC_E_TDEV) # include #elif defined(HAVE_STRUCT_KINFO_PROC_KI_TDEV) @@ -829,81 +826,6 @@ set_project(struct passwd *pw) } #endif /* HAVE_PROJECT_H */ -/* - * Disable execution of child processes in the command we are about - * to run. On systems with privilege sets, we can remove the exec - * privilege. On other systems we use LD_PRELOAD and the like. - */ -static void -disable_execute(struct command_details *details) -{ -#ifdef _PATH_SUDO_NOEXEC - char *cp, **ev, **nenvp; - int env_len = 0, env_size = 128; -#endif /* _PATH_SUDO_NOEXEC */ - debug_decl(disable_execute, SUDO_DEBUG_UTIL) - -#ifdef HAVE_PRIV_SET - /* Solaris privileges, remove PRIV_PROC_EXEC post-execve. */ - if (priv_set(PRIV_OFF, PRIV_LIMIT, "PRIV_PROC_EXEC", NULL) == 0) - debug_return; - warning(_("unable to remove PRIV_PROC_EXEC from PRIV_LIMIT")); -#endif /* HAVE_PRIV_SET */ - -#ifdef _PATH_SUDO_NOEXEC - nenvp = emalloc2(env_size, sizeof(char *)); - for (ev = details->envp; *ev != NULL; ev++) { - if (env_len + 2 > env_size) { - env_size += 128; - nenvp = erealloc3(nenvp, env_size, sizeof(char *)); - } - /* - * Prune out existing preloaded libraries. - * XXX - should save and append instead of replacing. - */ -# if defined(__darwin__) || defined(__APPLE__) - if (strncmp(*ev, "DYLD_INSERT_LIBRARIES=", sizeof("DYLD_INSERT_LIBRARIES=") - 1) == 0) - continue; - if (strncmp(*ev, "DYLD_FORCE_FLAT_NAMESPACE=", sizeof("DYLD_INSERT_LIBRARIES=") - 1) == 0) - continue; -# elif defined(__osf__) || defined(__sgi) - if (strncmp(*ev, "_RLD_LIST=", sizeof("_RLD_LIST=") - 1) == 0) - continue; -# elif defined(_AIX) - if (strncmp(*ev, "LDR_PRELOAD=", sizeof("LDR_PRELOAD=") - 1) == 0) - continue; -# else - if (strncmp(*ev, "LD_PRELOAD=", sizeof("LD_PRELOAD=") - 1) == 0) - continue; -# endif - nenvp[env_len++] = *ev; - } - - /* - * Preload a noexec file? For a list of LD_PRELOAD-alikes, see - * http://www.fortran-2000.com/ArnaudRecipes/sharedlib.html - * XXX - need to support 32-bit and 64-bit variants - */ -# if defined(__darwin__) || defined(__APPLE__) - nenvp[env_len++] = "DYLD_FORCE_FLAT_NAMESPACE="; - cp = fmt_string("DYLD_INSERT_LIBRARIES", sudo_conf_noexec_path()); -# elif defined(__osf__) || defined(__sgi) - easprintf(&cp, "_RLD_LIST=%s:DEFAULT", sudo_conf_noexec_path()); -# elif defined(_AIX) - cp = fmt_string("LDR_PRELOAD", sudo_conf_noexec_path()); -# else - cp = fmt_string("LD_PRELOAD", sudo_conf_noexec_path()); -# endif - if (cp == NULL) - errorx(1, _("unable to allocate memory")); - nenvp[env_len++] = cp; - nenvp[env_len] = NULL; - - details->envp = nenvp; -#endif /* _PATH_SUDO_NOEXEC */ - debug_return; -} - /* * Setup the execution environment immediately prior to the call to execve() * Returns true on success and false on failure. @@ -1024,9 +946,6 @@ exec_setup(struct command_details *details, const char *ptyname, int ptyfd) } } - if (ISSET(details->flags, CD_NOEXEC)) - disable_execute(details); - #ifdef HAVE_SETRESUID if (setresuid(details->uid, details->euid, details->euid) != 0) { warning(_("unable to change to runas uid (%u, %u)"), details->uid, @@ -1096,7 +1015,7 @@ run_command(struct command_details *details) cstat.type = CMD_INVALID; cstat.val = 0; - sudo_execve(details, &cstat); + sudo_execute(details, &cstat); switch (cstat.type) { case CMD_ERRNO: diff --git a/src/sudo.h b/src/sudo.h index 0aea65bb7..a5e29bcd8 100644 --- a/src/sudo.h +++ b/src/sudo.h @@ -170,7 +170,7 @@ extern const char *noexec_path; void zero_bytes(volatile void *, size_t); /* exec.c */ -int sudo_execve(struct command_details *details, struct command_status *cstat); +int sudo_execute(struct command_details *details, struct command_status *cstat); void save_signals(void); void restore_signals(void); @@ -214,7 +214,8 @@ void usage(int); int selinux_restore_tty(void); int selinux_setup(const char *role, const char *type, const char *ttyn, int ttyfd); -void selinux_execve(const char *path, char *argv[], char *envp[]); +void selinux_execve(const char *path, char *const argv[], char *const envp[], + int noexec); /* aix.c */ void aix_prep_user(char *user, const char *tty); diff --git a/src/sudo_exec.h b/src/sudo_exec.h index afbcb7c65..d9ce2e85d 100644 --- a/src/sudo_exec.h +++ b/src/sudo_exec.h @@ -28,10 +28,12 @@ */ /* exec.c */ -int my_execve(const char *path, char *const argv[], char *const envp[]); +int sudo_execve(const char *path, char *const argv[], char *const envp[], int noexec); int pipe_nonblock(int fds[2]); /* exec_pty.c */ +struct command_details; +struct command_status; int fork_pty(struct command_details *details, int sv[], int *maxfd); int perform_io(fd_set *fdsr, fd_set *fdsw, struct command_status *cstat); int suspend_parent(int signo); -- 2.40.0