Modular sudo front-end which loads policy and I/O plugins that do
authorTodd C. Miller <Todd.Miller@courtesan.com>
Sat, 20 Feb 2010 14:41:49 +0000 (09:41 -0500)
committerTodd C. Miller <Todd.Miller@courtesan.com>
Sat, 20 Feb 2010 14:41:49 +0000 (09:41 -0500)
most the actual work.  Currently relies on dynamic loading using
dlopen().  See doc/plugin.pod for the plugin API.

18 files changed:
Makefile [new file with mode: 0644]
configure
configure.in
install-sh [moved from plugins/sudoers/install-sh with 100% similarity]
pathnames.h.in
src/Makefile.in [new file with mode: 0644]
src/conversation.c [new file with mode: 0644]
src/fileops.c
src/fmt_string.c [new file with mode: 0644]
src/load_plugins.c [new file with mode: 0644]
src/parse_args.c [new file with mode: 0644]
src/pty.c
src/script.c
src/sudo.c [new file with mode: 0644]
src/sudo.h [new file with mode: 0644]
src/sudo_plugin_int.h [new file with mode: 0644]
src/sudo_usage.h.in [moved from sudo_usage.h.in with 100% similarity]
src/tgetpass.c

diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..96246e8
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,26 @@
+#
+# Copyright (c) 2010 Todd C. Miller <Todd.Miller@courtesan.com>
+#
+# 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.
+# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+# XXX - add sudoers, doc, compat?
+SUBDIRS = src plugins/sample
+
+all install:
+       @if [ ! -s config.status ]; then \
+               echo "Please run configure first"; \
+               exit 1; \
+       fi
+       for d in $(SUBDIRS); do (cd $$d && $(MAKE) $@); done
index 2077d3682b46ed9af9d8055c6e537de3bbb521be..55a6341a95cadb991b81cfda4d28c95eb5b39f73 100755 (executable)
--- a/configure
+++ b/configure
@@ -1,6 +1,6 @@
 #! /bin/sh
 # Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.61 for sudo 1.7.3b2.
+# Generated by GNU Autoconf 2.61 for sudo 1.8.0a1.
 #
 # Report bugs to <http://www.sudo.ws/bugs/>.
 #
@@ -724,10 +724,11 @@ SHELL=${CONFIG_SHELL-/bin/sh}
 # Identity of this package.
 PACKAGE_NAME='sudo'
 PACKAGE_TARNAME='sudo'
-PACKAGE_VERSION='1.7.3b2'
-PACKAGE_STRING='sudo 1.7.3b2'
+PACKAGE_VERSION='1.8.0a1'
+PACKAGE_STRING='sudo 1.8.0a1'
 PACKAGE_BUGREPORT='http://www.sudo.ws/bugs/'
 
+ac_config_libobj_dir=compat
 # Factoring default headers for most tests.
 ac_includes_default="\
 #include <stdio.h>
@@ -830,6 +831,7 @@ mansectform
 mansrcdir
 NOEXECFILE
 NOEXECDIR
+PLUGINDIR
 noexec_file
 INSTALL_NOEXEC
 DONT_LEAK_PATH_INFO
@@ -1418,7 +1420,7 @@ if test "$ac_init_help" = "long"; then
   # Omit some internal or obsolete options to make the list less imposing.
   # This message is too long to be a string in the A/UX 3.1 sh.
   cat <<_ACEOF
-\`configure' configures sudo 1.7.3b2 to adapt to many kinds of systems.
+\`configure' configures sudo 1.8.0a1 to adapt to many kinds of systems.
 
 Usage: $0 [OPTION]... [VAR=VALUE]...
 
@@ -1483,7 +1485,7 @@ fi
 
 if test -n "$ac_init_help"; then
   case $ac_init_help in
-     short | recursive ) echo "Configuration of sudo 1.7.3b2:";;
+     short | recursive ) echo "Configuration of sudo 1.8.0a1:";;
    esac
   cat <<\_ACEOF
 
@@ -1602,6 +1604,7 @@ Optional Packages:
                           (default=libvas.so)
   --with-libvas-rpath=PATH
                           Path to look for libvas in [default=/opt/quest/lib]
+  --with-plugin_dir       set directory to load plugins from
   --with-selinux          enable SELinux support
   --with-gnu-ld           assume the C compiler uses GNU ld [default=no]
   --with-pic              try to use only PIC/non-PIC objects [default=use
@@ -1688,7 +1691,7 @@ fi
 test -n "$ac_init_help" && exit $ac_status
 if $ac_init_version; then
   cat <<\_ACEOF
-sudo configure 1.7.3b2
+sudo configure 1.8.0a1
 generated by GNU Autoconf 2.61
 
 Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001,
@@ -1702,7 +1705,7 @@ cat >config.log <<_ACEOF
 This file contains any messages produced by compilers while
 running configure, to aid debugging if configure makes a mistake.
 
-It was created by sudo $as_me 1.7.3b2, which was
+It was created by sudo $as_me 1.8.0a1, which was
 generated by GNU Autoconf 2.61.  Invocation command line was
 
   $ $0 $@
@@ -2127,6 +2130,7 @@ echo "$as_me: Configuring Sudo version $PACKAGE_VERSION" >&6;}
 
 
 
+
 
 
 timeout=5
@@ -2193,6 +2197,8 @@ test "$sysconfdir" = '${prefix}/etc' -a X"$with_stow" != X"yes" && sysconfdir='/
 
 
 
+
+
 # Check whether --with-otp-only was given.
 if test "${with_otp_only+set}" = set; then
   withval=$with_otp_only; case $with_otp_only in
@@ -3847,6 +3853,23 @@ fi
 fi
 
 
+PLUGINDIR="$libexecdir"
+
+# Check whether --with-plugin_dir was given.
+if test "${with_plugin_dir+set}" = set; then
+  withval=$with_plugin_dir; case $with_plugin_dir in
+    yes)       ;;
+    no)                ;;
+    *)         PLUGINDIR="$with_plugin_dir"
+               ;;
+esac
+fi
+
+cat >>confdefs.h <<EOF
+#define _PATH_SUDO_PLUGIN_DIR "$PLUGINDIR"
+EOF
+
+
 
 { echo "$as_me:$LINENO: checking whether to do user authentication by default" >&5
 echo $ECHO_N "checking whether to do user authentication by default... $ECHO_C" >&6; }
@@ -6471,7 +6494,7 @@ ia64-*-hpux*)
   ;;
 *-*-irix6*)
   # Find out which ABI we are using.
-  echo '#line 6474 "configure"' > conftest.$ac_ext
+  echo '#line 6497 "configure"' > conftest.$ac_ext
   if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5
   (eval $ac_compile) 2>&5
   ac_status=$?
@@ -8335,11 +8358,11 @@ else
    -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
    -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
    -e 's:$: $lt_compiler_flag:'`
-   (eval echo "\"\$as_me:8338: $lt_compile\"" >&5)
+   (eval echo "\"\$as_me:8361: $lt_compile\"" >&5)
    (eval "$lt_compile" 2>conftest.err)
    ac_status=$?
    cat conftest.err >&5
-   echo "$as_me:8342: \$? = $ac_status" >&5
+   echo "$as_me:8365: \$? = $ac_status" >&5
    if (exit $ac_status) && test -s "$ac_outfile"; then
      # The compiler can only warn and ignore the option if not recognized
      # So say no if there are warnings other than the usual output.
@@ -8625,11 +8648,11 @@ else
    -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
    -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
    -e 's:$: $lt_compiler_flag:'`
-   (eval echo "\"\$as_me:8628: $lt_compile\"" >&5)
+   (eval echo "\"\$as_me:8651: $lt_compile\"" >&5)
    (eval "$lt_compile" 2>conftest.err)
    ac_status=$?
    cat conftest.err >&5
-   echo "$as_me:8632: \$? = $ac_status" >&5
+   echo "$as_me:8655: \$? = $ac_status" >&5
    if (exit $ac_status) && test -s "$ac_outfile"; then
      # The compiler can only warn and ignore the option if not recognized
      # So say no if there are warnings other than the usual output.
@@ -8729,11 +8752,11 @@ else
    -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
    -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
    -e 's:$: $lt_compiler_flag:'`
-   (eval echo "\"\$as_me:8732: $lt_compile\"" >&5)
+   (eval echo "\"\$as_me:8755: $lt_compile\"" >&5)
    (eval "$lt_compile" 2>out/conftest.err)
    ac_status=$?
    cat out/conftest.err >&5
-   echo "$as_me:8736: \$? = $ac_status" >&5
+   echo "$as_me:8759: \$? = $ac_status" >&5
    if (exit $ac_status) && test -s out/conftest2.$ac_objext
    then
      # The compiler can only warn and ignore the option if not recognized
@@ -11089,7 +11112,7 @@ else
   lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
   lt_status=$lt_dlunknown
   cat > conftest.$ac_ext <<EOF
-#line 11092 "configure"
+#line 11115 "configure"
 #include "confdefs.h"
 
 #if HAVE_DLFCN_H
@@ -11189,7 +11212,7 @@ else
   lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
   lt_status=$lt_dlunknown
   cat > conftest.$ac_ext <<EOF
-#line 11192 "configure"
+#line 11215 "configure"
 #include "confdefs.h"
 
 #if HAVE_DLFCN_H
@@ -26182,7 +26205,7 @@ _ACEOF
     exec_prefix="$oexec_prefix"
 fi
 
-ac_config_files="$ac_config_files Makefile sudo.man visudo.man sudoers.man sudoers.ldap.man sudoreplay.man sudo_usage.h"
+ac_config_files="$ac_config_files src/sudo_usage.h src/Makefile plugins/sample/Makefile"
 
 cat >confcache <<\_ACEOF
 # This file is a shell script that caches the results of configure
@@ -26580,7 +26603,7 @@ exec 6>&1
 # report actual input values of CONFIG_FILES etc. instead of their
 # values after options handling.
 ac_log="
-This file was extended by sudo $as_me 1.7.3b2, which was
+This file was extended by sudo $as_me 1.8.0a1, which was
 generated by GNU Autoconf 2.61.  Invocation command line was
 
   CONFIG_FILES    = $CONFIG_FILES
@@ -26629,7 +26652,7 @@ Report bugs to <bug-autoconf@gnu.org>."
 _ACEOF
 cat >>$CONFIG_STATUS <<_ACEOF
 ac_cs_version="\\
-sudo config.status 1.7.3b2
+sudo config.status 1.8.0a1
 configured by $0, generated by GNU Autoconf 2.61,
   with options \\"`echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`\\"
 
   case $ac_config_target in
     "config.h") CONFIG_HEADERS="$CONFIG_HEADERS config.h" ;;
     "pathnames.h") CONFIG_HEADERS="$CONFIG_HEADERS pathnames.h" ;;
-    "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;;
-    "sudo.man") CONFIG_FILES="$CONFIG_FILES sudo.man" ;;
-    "visudo.man") CONFIG_FILES="$CONFIG_FILES visudo.man" ;;
-    "sudoers.man") CONFIG_FILES="$CONFIG_FILES sudoers.man" ;;
-    "sudoers.ldap.man") CONFIG_FILES="$CONFIG_FILES sudoers.ldap.man" ;;
-    "sudoreplay.man") CONFIG_FILES="$CONFIG_FILES sudoreplay.man" ;;
-    "sudo_usage.h") CONFIG_FILES="$CONFIG_FILES sudo_usage.h" ;;
+    "src/sudo_usage.h") CONFIG_FILES="$CONFIG_FILES src/sudo_usage.h" ;;
+    "src/Makefile") CONFIG_FILES="$CONFIG_FILES src/Makefile" ;;
+    "plugins/sample/Makefile") CONFIG_FILES="$CONFIG_FILES plugins/sample/Makefile" ;;
 
   *) { { echo "$as_me:$LINENO: error: invalid argument: $ac_config_target" >&5
 echo "$as_me: error: invalid argument: $ac_config_target" >&2;}
@@ -26872,6 +26891,7 @@ mansectform!$mansectform$ac_delim
 mansrcdir!$mansrcdir$ac_delim
 NOEXECFILE!$NOEXECFILE$ac_delim
 NOEXECDIR!$NOEXECDIR$ac_delim
+PLUGINDIR!$PLUGINDIR$ac_delim
 noexec_file!$noexec_file$ac_delim
 INSTALL_NOEXEC!$INSTALL_NOEXEC$ac_delim
 DONT_LEAK_PATH_INFO!$DONT_LEAK_PATH_INFO$ac_delim
@@ -26902,7 +26922,6 @@ mailsub!$mailsub$ac_delim
 badpass_message!$badpass_message$ac_delim
 fqdn!$fqdn$ac_delim
 runas_default!$runas_default$ac_delim
-env_editor!$env_editor$ac_delim
 _ACEOF
 
   if test `sed -n "s/.*$ac_delim\$/X/p" conf$$subs.sed | grep -c X` = 97; then
@@ -26944,6 +26963,7 @@ _ACEOF
 ac_delim='%!_!# '
 for ac_last_try in false false false false false :; do
   cat >conf$$subs.sed <<_ACEOF
+env_editor!$env_editor$ac_delim
 passwd_tries!$passwd_tries$ac_delim
 tty_tickets!$tty_tickets$ac_delim
 insults!$insults$ac_delim
@@ -26989,7 +27009,7 @@ KRB5CONFIG!$KRB5CONFIG$ac_delim
 LTLIBOBJS!$LTLIBOBJS$ac_delim
 _ACEOF
 
-  if test `sed -n "s/.*$ac_delim\$/X/p" conf$$subs.sed | grep -c X` = 43; then
+  if test `sed -n "s/.*$ac_delim\$/X/p" conf$$subs.sed | grep -c X` = 44; then
     break
   elif $ac_last_try; then
     { { echo "$as_me:$LINENO: error: could not make $CONFIG_STATUS" >&5
index 765240ab45a9f3132693331318e6d2848760e52f..1a15fb456200b7fff97241cbf764ffe832c5d97d 100644 (file)
@@ -3,7 +3,7 @@ dnl Process this file with GNU autoconf to produce a configure script.
 dnl
 dnl Copyright (c) 1994-1996,1998-2009 Todd C. Miller <Todd.Miller@courtesan.com>
 dnl
-AC_INIT([sudo], [1.7.3b2], [http://www.sudo.ws/bugs/], [sudo])
+AC_INIT([sudo], [1.8.0a1], [http://www.sudo.ws/bugs/], [sudo])
 AC_CONFIG_HEADER(config.h pathnames.h)
 dnl
 dnl This won't work before AC_INIT
@@ -42,6 +42,7 @@ AC_SUBST(mansectform)
 AC_SUBST(mansrcdir)
 AC_SUBST(NOEXECFILE)
 AC_SUBST(NOEXECDIR)
+AC_SUBST(PLUGINDIR)
 AC_SUBST(noexec_file)
 AC_SUBST(INSTALL_NOEXEC)
 AC_SUBST(DONT_LEAK_PATH_INFO)
@@ -161,6 +162,11 @@ test "$bindir" = '${exec_prefix}/bin' && bindir='$(exec_prefix)/bin'
 test "$sbindir" = '${exec_prefix}/sbin' && sbindir='$(exec_prefix)/sbin'
 test "$sysconfdir" = '${prefix}/etc' -a X"$with_stow" != X"yes" && sysconfdir='/etc'
 
+dnl
+dnl libc replacement functions live in compat
+dnl
+AC_CONFIG_LIBOBJ_DIR(compat)
+
 dnl
 dnl Deprecated --with options (these all warn or generate an error)
 dnl
@@ -1061,6 +1067,16 @@ if test X"$with_libvas" != X"no"; then
 fi
 ])
 
+PLUGINDIR="$libexecdir"
+AC_ARG_WITH(plugin_dir, [AS_HELP_STRING([--with-plugin_dir], [set directory to load plugins from])],
+[case $with_plugin_dir in
+    yes)       ;;
+    no)                ;;
+    *)         PLUGINDIR="$with_plugin_dir"
+               ;;
+esac])
+SUDO_DEFINE_UNQUOTED(_PATH_SUDO_PLUGIN_DIR, "$PLUGINDIR")
+
 dnl
 dnl Options for --enable
 dnl
@@ -2701,7 +2717,8 @@ fi
 dnl
 dnl Substitute into the Makefile and man pages
 dnl
-AC_CONFIG_FILES([Makefile sudo.man visudo.man sudoers.man sudoers.ldap.man sudoreplay.man sudo_usage.h])
+dnl AC_CONFIG_FILES([doc/sudo.man doc/visudo.man doc/sudoers.man doc/sudoers.ldap.man doc/sudoreplay.man src/Makefile src/sudo_usage.h])
+AC_CONFIG_FILES([src/sudo_usage.h src/Makefile plugins/sample/Makefile])
 AC_OUTPUT
 
 dnl
similarity index 100%
rename from plugins/sudoers/install-sh
rename to install-sh
index ccacfb49feeb4a339c4073ef46f4f01f9c547e8e..8402c1585d0be4423fbc0677e1bd8fd32e05e647 100644 (file)
 #define _PATH_ENVIRONMENT      "/etc/environment"
 #endif /* _PATH_ENVIRONMENT */
 
+/*
+ * NOTE: _PATH_SUDO_CONF is usually overridden by the Makefile.
+ */
+#ifndef _PATH_SUDO_CONF
+#define _PATH_SUDO_CONF                "/etc/sudo.conf"
+#endif /* _PATH_SUDO_CONF */
+
 /*
  * NOTE: _PATH_SUDOERS is usually overridden by the Makefile.
  */
 #undef _PATH_SUDO_ASKPASS
 #endif /* _PATH_SUDO_ASKPASS */
 
+#ifndef _PATH_SUDO_PLUGIN_DIR
+#undef _PATH_SUDO_PLUGIN_DIR
+#endif /* _PATH_SUDO_PLUGIN_DIR */
+
 #ifndef _PATH_VI
 #undef _PATH_VI
 #endif /* _PATH_VI */
diff --git a/src/Makefile.in b/src/Makefile.in
new file mode 100644 (file)
index 0000000..77a8a6c
--- /dev/null
@@ -0,0 +1,208 @@
+#
+# Copyright (c) 2010 Todd C. Miller <Todd.Miller@courtesan.com>
+#
+# 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.
+# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+# @configure_input@
+#
+
+#### Start of system configuration section. ####
+
+srcdir = @srcdir@
+devdir = @devdir@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+incdir = $(top_srcdir)/include
+
+# Compiler & tools to use
+CC = @CC@
+NROFF = nroff -Tascii
+
+# Our install program supports extra flags...
+INSTALL = $(SHELL) $(top_srcdir)/install-sh -c
+
+# Libraries
+LIBS = @LIBS@ @SUDO_LIBS@ @GETGROUPS_LIB@ @NET_LIBS@ 
+
+# C preprocessor flags
+CPPFLAGS = -I$(incdir) -I$(top_builddir) -I$(srcdir) -I. @CPPFLAGS@
+
+# Usually -O and/or -g
+CFLAGS = @CFLAGS@
+
+# Flags to pass to the link stage
+LDFLAGS = @LDFLAGS@
+
+# Where to install things...
+prefix = @prefix@
+exec_prefix = @exec_prefix@
+bindir = @bindir@
+sbindir = @sbindir@
+sysconfdir = @sysconfdir@
+libexecdir = @libexecdir@
+datarootdir = @datarootdir@
+noexecfile = @NOEXECFILE@
+noexecdir = @NOEXECDIR@
+
+# User and group ids the installed files should be "owned" by
+install_uid = 0
+install_gid = 0
+
+# Pass in paths and OS dependent defines
+DEFS = @OSDEFS@
+
+#### End of system configuration section. ####
+
+SHELL = /bin/sh
+
+PROGS = sudo
+
+# XXX - add back missing ones:
+#      sudo_edit.c selinux.c
+OBJS = sudo.o parse_args.o lbuf.o alloc.o error.o zero_bytes.o \
+       load_plugins.o conversation.o list.o fmt_string.o tgetpass.o \
+       fileops.o term.o atobool.o script.o pty.o
+
+LIB_OBJS = @LIBOBJS@
+
+VERSION = @PACKAGE_VERSION@
+
+# XXX - update
+SUDODEP = $(srcdir)/sudo.h $(incdir)/alloc.h \
+         $(incdir)/compat.h $(incdir)/error.h \
+         $(incdir)/list.h $(incdir)/missing.h \
+         $(top_builddir)/pathnames.h $(top_builddir)/config.h
+
+all: $(PROGS)
+
+.SUFFIXES: .o .c .h .l .y .man .cat .lo
+
+.c.o:
+       $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $<
+
+.c.lo:
+       $(LIBTOOL) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $<
+
+sudo: $(OBJS)
+       $(CC) -o $@ $(OBJS) $(LDFLAGS) $(LIBS)
+
+sudo_noexec.lo: $(srcdir)/sudo_noexec.c
+       $(LIBTOOL) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/sudo_noexec.c
+
+sudo_noexec.la: sudo_noexec.lo
+       $(LIBTOOL) --mode=link $(CC) $(LDFLAGS) -o $@ sudo_noexec.lo -avoid-version -rpath $(noexecdir)
+
+# Dependencies (not counting auth functions)
+aix.o: $(srcdir)/aix.c
+       $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/aix.c
+alloc.o: $(srcdir)/alloc.c $(SUDODEP)
+       $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/alloc.c
+audit.o: $(srcdir)/audit.c $(SUDODEP)
+       $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/audit.c
+bsm_audit.o: $(srcdir)/bsm_audit.c $(SUDODEP) bsm_audit.h
+       $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/bsm_audit.c
+closefrom.o: $(srcdir)/closefrom.c $(top_builddir)/config.h
+       $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/closefrom.c
+error.o: $(srcdir)/error.c $(incdir)/compat.h $(incdir)/error.h $(top_builddir)/config.h
+       $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/error.c
+fileops.o: $(srcdir)/fileops.c $(SUDODEP)
+       $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/fileops.c
+getcwd.o: $(srcdir)/getcwd.c $(incdir)/compat.h $(top_builddir)/config.h
+       $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/getcwd.c
+getline.o: $(srcdir)/getline.c $(top_builddir)/config.h
+       $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/getline.c
+getprogname.o: $(srcdir)/getprogname.c $(top_builddir)/config.h
+       $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/getprogname.c
+isblank.o: $(srcdir)/isblank.c $(incdir)/compat.h $(top_builddir)/config.h
+       $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/isblank.c
+lbuf.o: $(srcdir)/lbuf.c $(SUDODEP)
+       $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/lbuf.c
+list.o: $(srcdir)/list.c $(SUDODEP)
+       $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/list.c
+memrchr.o: $(srcdir)/memrchr.c $(SUDODEP)
+       $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/memrchr.c
+mkstemp.o: $(srcdir)/mkstemp.c $(SUDODEP)
+       $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/mkstemp.c
+nanosleep.o: $(srcdir)/nanosleep.c $(incdir)/compat.h $(top_builddir)/config.h
+       $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/nanosleep.c
+pty.o: $(srcdir)/pty.c $(SUDODEP)
+       $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/pty.c
+script.o: $(srcdir)/script.c $(SUDODEP)
+       $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/script.c
+sigaction.o: $(srcdir)/sigaction.c $(incdir)/compat.h
+       $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/sigaction.c
+snprintf.o: $(srcdir)/snprintf.c $(incdir)/compat.h $(top_builddir)/config.h
+       $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/snprintf.c
+strcasecmp.o: $(srcdir)/strcasecmp.c $(incdir)/compat.h  $(top_builddir)/config.h
+       $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/strcasecmp.c
+strerror.o: $(srcdir)/strerror.c $(incdir)/compat.h $(top_builddir)/config.h
+       $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/strerror.c
+strlcat.o: $(srcdir)/strlcat.c $(incdir)/compat.h $(top_builddir)/config.h
+       $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/strlcat.c
+strlcpy.o: $(srcdir)/strlcpy.c $(incdir)/compat.h $(top_builddir)/config.h
+       $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/strlcpy.c
+strsignal.o: $(srcdir)/strsignal.c $(incdir)/compat.h $(top_builddir)/config.h
+       $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/strsignal.c
+selinux.o: $(srcdir)/selinux.c $(SUDODEP)
+       $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/selinux.c
+sudo.o: $(srcdir)/sudo.c $(SUDODEP) sudo_usage.h
+       $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/sudo.c
+sudo_edit.o: $(srcdir)/sudo_edit.c $(SUDODEP)
+       $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/sudo_edit.c
+sudo_noexec.o: $(srcdir)/sudo_noexec.c $(incdir)/compat.h $(top_builddir)/config.h
+       $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/sudo_noexec.c
+term.o: $(srcdir)/term.c $(SUDODEP)
+       $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/term.c
+tgetpass.o: $(srcdir)/tgetpass.c $(SUDODEP)
+       $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/tgetpass.c
+utimes.o: $(srcdir)/utimes.c $(incdir)/compat.h $(srcdir)/emul/utime.h $(top_builddir)/config.h
+       $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/utimes.c
+zero_bytes.o: $(srcdir)/zero_bytes.c $(incdir)/compat.h $(top_builddir)/config.h
+       $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/zero_bytes.c
+
+install: install-dirs install-binaries @INSTALL_NOEXEC@
+
+install-dirs:
+       $(SHELL) $(top_srcdir)/mkinstalldirs $(DESTDIR)$(sudodir) \
+           $(DESTDIR)$(noexecdir)
+
+install-binaries: install-dirs $(PROGS)
+       $(INSTALL) -O $(install_uid) -G $(install_gid) -M 4111 -s sudo $(DESTDIR)$(sudodir)/sudo
+       rm -f $(DESTDIR)$(sudodir)/sudoedit
+       ln $(DESTDIR)$(sudodir)/sudo $(DESTDIR)$(sudodir)/sudoedit
+       if [ -f sesh ]; then $(INSTALL) -O $(install_uid) -G $(install_gid) -M 0111 -s sesh $(DESTDIR)$(libexecdir)/sesh; fi
+
+install-noexec: install-dirs sudo_noexec.la
+       if [ -f .libs/$(noexecfile) ]; then $(INSTALL) -O $(install_uid) -G $(install_gid) -M 0755 .libs/$(noexecfile) $(DESTDIR)$(noexecdir); fi
+
+check:
+       @echo nothing to check
+
+clean:
+       -rm -f *.a *.o *.lo stamp-* $(PROGS) core *.core core.*
+
+mostlyclean: clean
+
+# XXX - remove some
+distclean: clean
+       -rm -rf Makefile pathnames.h config.h config.status config.cache \
+               config.log libtool sudo_noexec.lo .libs $(GENERATED) \
+               sudo_usage.h
+
+clobber: distclean
+
+realclean: distclean
+       rm -f TAGS tags
+
+cleandir: realclean
diff --git a/src/conversation.c b/src/conversation.c
new file mode 100644 (file)
index 0000000..16d83b2
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 1999-2005, 2007-2009 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * 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.
+ *
+ * Sponsored in part by the Defense Advanced Research Projects
+ * Agency (DARPA) and Air Force Research Laboratory, Air Force
+ * Materiel Command, USAF, under agreement number F39502-99-1-0512.
+ */
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <stdio.h>
+#ifdef STDC_HEADERS
+# include <stdlib.h>
+# include <stddef.h>
+#else
+# ifdef HAVE_STDLIB_H
+#  include <stdlib.h>
+# endif
+#endif /* STDC_HEADERS */
+#ifdef HAVE_STRING_H
+# if defined(HAVE_MEMORY_H) && !defined(STDC_HEADERS)
+#  include <memory.h>
+# endif
+# include <string.h>
+#else
+# ifdef HAVE_STRINGS_H
+#  include <strings.h>
+# endif
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif /* HAVE_UNISTD_H */
+
+#include "sudo.h"
+#include "sudo_plugin.h"
+
+extern int tgetpass_flags; /* XXX */
+
+/*
+ * Sudo conversation function.
+ */
+int
+sudo_conversation(int num_msgs, const struct sudo_conv_message msgs[],
+    struct sudo_conv_reply replies[])
+{
+    struct sudo_conv_reply *repl;
+    const struct sudo_conv_message *msg;
+    char *pass;
+    int n, flags = tgetpass_flags;
+
+    for (n = 0; n < num_msgs; n++) {
+       msg = &msgs[n];
+       repl = &replies[n];
+       switch (msg->msg_type) {
+           case SUDO_CONV_PROMPT_ECHO_ON:
+               SET(flags, TGP_ECHO);
+           case SUDO_CONV_PROMPT_ECHO_OFF:
+               /* Read the password unless interrupted. */
+               /* XXX - look up passwd timeout and pass in XXX */
+               pass = tgetpass(msg->msg, 0, flags);
+               if (pass == NULL)
+                   goto err;
+               repl->reply = estrdup(pass);
+               zero_bytes(pass, strlen(pass));
+               break;
+           case SUDO_CONV_INFO_MSG:
+               if (msg->msg)
+                   (void) puts(msg->msg);
+               break;
+           case SUDO_CONV_ERROR_MSG:
+               if (msg->msg) {
+                   (void) fputs(msg->msg, stderr);
+                   (void) fputc('\n', stderr);
+               }
+               break;
+           default:
+               goto err;
+       }
+    }
+
+    return(0);
+
+err:
+    /* Zero and free allocated memory and return an error. */
+    do {
+       repl = &replies[n];
+       if (repl->reply != NULL) {
+           zero_bytes(repl->reply, strlen(repl->reply));
+           free(repl->reply);
+           repl->reply = NULL;
+       }
+    } while (n--);
+
+    return(-1);
+}
index 1d119ced706a06b5a23d0a6df3f2b0e77644cf55..5e9d31dd67c6323c5a7ebf5da58f40735f7929fb 100644 (file)
@@ -44,7 +44,7 @@
 # include <time.h>
 #endif
 #ifndef HAVE_TIMESPEC
-# include <compat/timespec.h>
+# include <emul/timespec.h>
 #endif
 
 #include "sudo.h"
diff --git a/src/fmt_string.c b/src/fmt_string.c
new file mode 100644 (file)
index 0000000..e2aabb6
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2010 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * 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 <config.h>
+
+#include <sys/types.h>
+#include <sys/param.h>
+
+#include <stdio.h>
+#ifdef STDC_HEADERS
+# include <stdlib.h>
+# include <stddef.h>
+#else
+# ifdef HAVE_STDLIB_H
+#  include <stdlib.h>
+# endif
+#endif /* STDC_HEADERS */
+#ifdef HAVE_STRING_H
+# if defined(HAVE_MEMORY_H) && !defined(STDC_HEADERS)
+#  include <memory.h>
+# endif
+# include <string.h>
+#else
+# ifdef HAVE_STRINGS_H
+#  include <strings.h>
+# endif
+#endif /* HAVE_STRING_H */
+
+#include "sudo.h"
+
+/*
+ * Allocate storage for a name=value string and return it.
+ */
+char *
+fmt_string(const char *var, const char *val)
+{
+    size_t var_len = strlen(var);
+    size_t val_len = strlen(val);
+    char *cp, *str;
+
+    cp = str = emalloc(var_len + 1 + val_len + 1);
+    memcpy(cp, var, var_len);
+    cp += var_len;
+    *cp++ = '=';
+    memcpy(cp, val, val_len);
+    cp += val_len;
+    *cp = '\0';
+
+    return(str);
+}
diff --git a/src/load_plugins.c b/src/load_plugins.c
new file mode 100644 (file)
index 0000000..fc3cbc9
--- /dev/null
@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) 2009 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * 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 <config.h>
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#ifdef STDC_HEADERS
+# include <stdlib.h>
+# include <stddef.h>
+#else
+# ifdef HAVE_STDLIB_H
+#  include <stdlib.h>
+# endif
+#endif /* STDC_HEADERS */
+#ifdef HAVE_STRING_H
+# include <string.h>
+#else
+# ifdef HAVE_STRINGS_H
+#  include <strings.h>
+# endif
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif /* HAVE_UNISTD_H */
+#include <errno.h>
+#include <dlfcn.h>
+
+#include "sudo.h"
+#include "sudo_plugin.h"
+#include "sudo_plugin_int.h"
+
+/*
+ * Read in /etc/sudo.conf
+ * Returns a list of plugins.
+ */
+static struct plugin_info_list *
+sudo_read_conf(const char *conf_file)
+{
+    FILE *fp;
+    char *cp, *name, *path;
+    struct plugin_info *info;
+    static struct plugin_info_list pil; /* XXX */
+
+    if ((fp = fopen(conf_file, "r")) == NULL) {
+       /* Default values */
+       info = emalloc(sizeof(*info));
+       info->symbol_name = "sudoers";
+       info->path = "sudoers_policy";
+       info->prev = info;
+       info->next = NULL;
+       tq_append(&pil, info);
+       /* XXX - io plugin too */
+       goto done;
+    }
+
+    while ((cp = sudo_parseln(fp)) != NULL) {
+       /* Skip blank or comment lines */
+       if (*cp == '\0')
+           continue;
+
+       /* Look for a line starting with "Plugin" */
+       if (strncasecmp(cp, "Plugin", 6) != 0)
+           continue;
+
+       /* Parse line */
+       if ((name = strtok(cp + 6, " \t")) == NULL ||
+           (path = strtok(NULL, " \t")) == NULL) {
+           continue;
+       }
+
+       info = emalloc(sizeof(*info));
+       info->symbol_name = estrdup(name);
+       info->path = estrdup(path);
+       info->prev = info;
+       info->next = NULL;
+       tq_append(&pil, info);
+    }
+    fclose(fp);
+
+done:
+
+    return(&pil);
+}
+
+/*
+ * Load the plugins listed in conf_file.
+ */
+void
+sudo_load_plugins(const char *conf_file,
+    struct plugin_container *policy_plugin,
+    struct plugin_container_list *io_plugins)
+{
+    struct generic_plugin *plugin;
+    struct plugin_container *container;
+    struct plugin_info *info;
+    struct plugin_info_list *plugin_list;
+    struct stat sb;
+    void *handle;
+    char path[PATH_MAX];
+
+    /* Parse sudo.conf */
+    plugin_list = sudo_read_conf(conf_file);
+    if (tq_empty(plugin_list))
+       errorx(1, "no plugins defined in %s", conf_file);
+
+    tq_foreach_fwd(plugin_list, info) {
+       if (info->path[0] == '/') {
+           if (strlcpy(path, info->path, sizeof(path) >= sizeof(path)))
+               errorx(1, "%s: %s", info->path, strerror(ENAMETOOLONG));
+       } else {
+           if (snprintf(path, sizeof(path), "%s/%s", _PATH_SUDO_PLUGIN_DIR,
+               info->path) >= sizeof(path)) {
+               errorx(1, "%s/%s: %s", _PATH_SUDO_PLUGIN_DIR, info->path,
+                   strerror(ENAMETOOLONG));
+           }
+       }
+       if (stat(path, &sb) != 0)
+           error(1, "%s", path);
+       if (sb.st_uid != ROOT_UID)
+           errorx(1, "%s must be owned by uid %d", path, ROOT_UID);
+       if ((sb.st_mode & (S_IWGRP|S_IWOTH)) != 0)
+           errorx(1, "%s must be only be writable by owner", path);
+
+       /* Open plugin and map in symbol */
+       handle = dlopen(path, RTLD_NOW);
+       if (!handle)
+           errorx(1, "unable to dlopen %s: %s", path, dlerror());
+       plugin = dlsym(handle, info->symbol_name);
+       if (!plugin)
+           errorx(1, "unable to find symbol %s in %s", info->symbol_name, path);
+
+       if (plugin->type != SUDO_POLICY_PLUGIN && plugin->type != SUDO_IO_PLUGIN) {
+           errorx(1, "%s: unknown policy type %d", path, plugin->type);
+       }
+       if (SUDO_API_VERSION_GET_MAJOR(plugin->version) != SUDO_API_VERSION_MAJOR) {
+           errorx(1, "%s: incompatible policy major version %d, expected %d",
+               path, SUDO_API_VERSION_GET_MAJOR(plugin->version),
+               SUDO_API_VERSION_MAJOR);
+       }
+       if (plugin->type == SUDO_POLICY_PLUGIN) {
+           if (policy_plugin->handle)
+               errorx(1, "only a single policy plugin may be loaded");
+           policy_plugin->handle = handle;
+           policy_plugin->name = info->symbol_name;
+           policy_plugin->u.generic = plugin;
+       } else if (plugin->type == SUDO_IO_PLUGIN) {
+           container = emalloc(sizeof(*container));
+           container->prev = container;
+           container->next = NULL;
+           container->handle = handle;
+           container->name = info->symbol_name;
+           container->u.generic = plugin;
+           tq_append(io_plugins, container);
+       }
+    }
+    if (policy_plugin->handle == NULL)
+       errorx(1, "%s: at least one policy plugin must be specified", conf_file);
+    if (policy_plugin->u.policy->check_policy == NULL)
+       errorx(1, "policy plugin %s does not include a check_policy method");
+}
diff --git a/src/parse_args.c b/src/parse_args.c
new file mode 100644 (file)
index 0000000..9ff4582
--- /dev/null
@@ -0,0 +1,431 @@
+/*
+ * Copyright (c) 1993-1996, 1998-2009 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * 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.
+ *
+ * Sponsored in part by the Defense Advanced Research Projects
+ * Agency (DARPA) and Air Force Research Laboratory, Air Force
+ * Materiel Command, USAF, under agreement number F39502-99-1-0512.
+ */
+
+#include <config.h>
+
+/* XXX - prune this */
+
+#include <sys/types.h>
+#include <sys/param.h>
+
+#include <stdio.h>
+#ifdef STDC_HEADERS
+# include <stdlib.h>
+# include <stddef.h>
+#else
+# ifdef HAVE_STDLIB_H
+#  include <stdlib.h>
+# endif
+#endif /* STDC_HEADERS */
+#ifdef HAVE_STRING_H
+# if defined(HAVE_MEMORY_H) && !defined(STDC_HEADERS)
+#  include <memory.h>
+# endif
+# include <string.h>
+#else
+# ifdef HAVE_STRINGS_H
+#  include <strings.h>
+# endif
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif /* HAVE_UNISTD_H */
+#include <pwd.h>
+#include <grp.h>
+
+#include <sudo_usage.h>
+#include "sudo.h"
+#include "lbuf.h" /* XXX */
+
+/* For getopt(3) */
+extern char *optarg;
+extern int optind;
+
+/* XXX - better home for these and extern in header file */
+int tgetpass_flags;
+int user_closefrom = -1;
+const char *list_user, *runas_user, *runas_group;
+
+/*
+ * Local functions.
+ */
+static void usage(int) __attribute__((__noreturn__));
+static void usage_excl(int) __attribute__((__noreturn__));
+
+/*
+ * Mapping of command line flags to name/value settings.
+ */
+struct name_value_pair {
+    const char *name;
+    const char *value;
+};
+static struct sudo_settings {
+    struct name_value_pair a;
+    struct name_value_pair c;
+    struct name_value_pair D;
+    struct name_value_pair E;
+    struct name_value_pair g;
+    struct name_value_pair H;
+    struct name_value_pair i;
+    struct name_value_pair k;
+    struct name_value_pair p;
+    struct name_value_pair r;
+    struct name_value_pair t;
+    struct name_value_pair U;
+    struct name_value_pair u;
+} sudo_settings = {
+    { "bsdauth_type" },
+    { "login_class" },
+    { "debug_level" },
+    { "preserve_environment" },
+    { "runas_group" },
+    { "set_home" },
+    { "login_shell" },
+    { "ignore_ticket" },
+    { "prompt" },
+    { "selinux_role" },
+    { "selinux_type" },
+    { "runas_user" }
+};
+static struct name_value_pair *setting_pairs[] = {
+    &sudo_settings.a,
+    &sudo_settings.c,
+    &sudo_settings.D,
+    &sudo_settings.E,
+    &sudo_settings.g,
+    &sudo_settings.H,
+    &sudo_settings.i,
+    &sudo_settings.p,
+    &sudo_settings.r,
+    &sudo_settings.t,
+    &sudo_settings.u
+};
+#define settings_size (sizeof(setting_pairs) / sizeof(struct name_value_pair))
+
+/*
+ * Command line argument parsing.
+ * Sets nargc and nargv which corresponds to the argc/argv we'll use
+ * for the command to be run (if we are running one).
+ */
+int
+parse_args(int argc, char **argv, int *nargc, char ***nargv, char ***settingsp,
+    char ***env_addp)
+{
+    int mode = 0;              /* what mode is sudo to be run in? */
+    int flags = 0;             /* mode flags */
+    int valid_flags, ch;
+    int i, j;
+    char **settings;
+    char **env_add;
+    int nenv = 0;
+    int env_size = 32;
+
+    env_add = emalloc2(env_size, sizeof(char *));
+    env_add[0] = NULL;
+
+    /* First, check to see if we were invoked as "sudoedit". */
+    if (strcmp(getprogname(), "sudoedit") == 0)
+       mode = MODE_EDIT;
+
+    /* Returns true if the last option string was "--" */
+#define got_end_of_args        (optind > 1 && argv[optind - 1][0] == '-' && \
+           argv[optind - 1][1] == '-' && argv[optind - 1][2] == '\0')
+
+    /* Returns true if next option is an environment variable */
+#define is_envar (optind < argc && argv[optind][0] != '/' && \
+           strchr(argv[optind], '=') != NULL)
+
+    /* Flags allowed when running a command */
+    valid_flags = MODE_BACKGROUND|MODE_PRESERVE_ENV|MODE_RESET_HOME|
+                 MODE_LOGIN_SHELL|MODE_INVALIDATE|MODE_NONINTERACTIVE|
+                 MODE_PRESERVE_GROUPS|MODE_SHELL;
+    /* XXX - should fill in settings at the end to avoid dupes */
+    for (;;) {
+       /*
+        * We disable arg permutation for GNU getopt().
+        * Some trickiness is required to allow environment variables
+        * to be interspersed with command line options.
+        */
+       if ((ch = getopt(argc, argv, "+Aa:bC:c:D:Eeg:HhiKkLlnPp:r:Sst:U:u:Vv")) != -1) {
+           switch (ch) {
+               case 'A':
+                   SET(tgetpass_flags, TGP_ASKPASS);
+                   break;
+#ifdef HAVE_BSD_AUTH_H
+               case 'a':
+                   sudo_settings.a.value = optarg;
+                   break;
+#endif
+               case 'b':
+                   SET(flags, MODE_BACKGROUND);
+                   break;
+               case 'C':
+                   if ((user_closefrom = atoi(optarg)) < 3) {
+                       warningx("the argument to -C must be at least 3");
+                       usage(1);
+                   }
+                   break;
+#ifdef HAVE_LOGIN_CAP_H
+               case 'c':
+                   sudo_settings.c.value = optarg;
+                   break;
+#endif
+               case 'D':
+                   sudo_settings.D.value = optarg;
+                   break;
+               case 'E':
+                   sudo_settings.c.value = "true";
+                   break;
+               case 'e':
+                   if (mode && mode != MODE_EDIT)
+                       usage_excl(1);
+                   mode = MODE_EDIT;
+                   valid_flags = MODE_INVALIDATE|MODE_NONINTERACTIVE;
+                   break;
+               case 'g':
+                   runas_group = optarg;
+                   sudo_settings.g.value = optarg;
+                   break;
+               case 'H':
+                   sudo_settings.H.value = optarg;
+                   break;
+               case 'h':
+                   if (mode && mode != MODE_HELP) {
+                       if (strcmp(getprogname(), "sudoedit") != 0)
+                           usage_excl(1);
+                   }
+                   mode = MODE_HELP;
+                   valid_flags = 0;
+                   break;
+               case 'i':
+                   sudo_settings.i.value = "true";
+                   SET(flags, MODE_LOGIN_SHELL);
+                   break;
+               case 'k':
+                   sudo_settings.k.value = "true";
+                   SET(flags, MODE_INVALIDATE);
+                   break;
+               case 'K':
+                   sudo_settings.k.value = "true";
+                   if (mode && mode != MODE_KILL)
+                       usage_excl(1);
+                   mode = MODE_KILL;
+                   valid_flags = 0;
+                   break;
+               case 'l':
+                   if (mode) {
+                       if (mode == MODE_LIST)
+                           SET(flags, MODE_LONG_LIST);
+                       else
+                           usage_excl(1);
+                   }
+                   mode = MODE_LIST;
+                   valid_flags = MODE_INVALIDATE|MODE_NONINTERACTIVE|MODE_LONG_LIST;
+                   break;
+               case 'n':
+                   SET(flags, MODE_NONINTERACTIVE);
+                   break;
+               case 'P':
+                   SET(flags, MODE_PRESERVE_GROUPS);
+                   break;
+               case 'p':
+                   sudo_settings.i.value = optarg;
+                   break;
+#ifdef HAVE_SELINUX
+               case 'r':
+                   sudo_settings.r.value = optarg;
+                   break;
+               case 't':
+                   sudo_settings.t.value = optarg;
+                   break;
+#endif
+               case 'S':
+                   SET(tgetpass_flags, TGP_STDIN);
+                   break;
+               case 's':
+                   SET(flags, MODE_SHELL);
+                   break;
+               case 'U':
+                   /* XXX - sudo_getpwnam */
+                   if ((getpwnam(optarg)) == NULL)
+                       errorx(1, "unknown user: %s", optarg);
+                   list_user = optarg;
+                   break;
+               case 'u':
+                   runas_user = optarg;
+                   sudo_settings.u.value = optarg;
+                   break;
+               case 'v':
+                   if (mode && mode != MODE_VALIDATE)
+                       usage_excl(1);
+                   mode = MODE_VALIDATE;
+                   valid_flags = MODE_INVALIDATE|MODE_NONINTERACTIVE;
+                   break;
+               case 'V':
+                   if (mode && mode != MODE_VERSION)
+                       usage_excl(1);
+                   mode = MODE_VERSION;
+                   valid_flags = 0;
+                   break;
+               default:
+                   usage(1);
+           }
+       } else if (!got_end_of_args && is_envar) {
+           if (nenv == env_size - 2) {
+               env_size *= 2;
+               env_add = erealloc3(env_add, env_size, sizeof(char *));
+           }
+           env_add[nenv++] = argv[optind];
+
+           /* Crank optind and resume getopt. */
+           optind++;
+       } else {
+           /* Not an option or an environment variable -- we're done. */
+           break;
+       }
+    }
+
+    *nargc = argc - optind;
+    *nargv = argv + optind;
+
+    if (!mode) {
+       /* Defer -k mode setting until we know whether it is a flag or not */
+       if (ISSET(flags, MODE_INVALIDATE) && *nargc == 0) {
+           mode = MODE_INVALIDATE;     /* -k by itself */
+           CLR(flags, MODE_INVALIDATE);
+           sudo_settings.k.value = NULL;
+           valid_flags = 0;
+       } else {
+           mode = MODE_RUN;            /* running a command */
+       }
+    }
+
+    if (*nargc > 0 && mode == MODE_LIST)
+       mode = MODE_CHECK;
+
+    if (ISSET(flags, MODE_LOGIN_SHELL)) {
+       if (ISSET(flags, MODE_SHELL)) {
+           warningx("you may not specify both the `-i' and `-s' options");
+           usage(1);
+       }
+       if (ISSET(flags, MODE_PRESERVE_ENV)) {
+           warningx("you may not specify both the `-i' and `-E' options");
+           usage(1);
+       }
+       SET(flags, MODE_SHELL);
+    }
+    if ((flags & valid_flags) != flags)
+       usage(1);
+    if (mode == MODE_EDIT &&
+       (ISSET(flags, MODE_PRESERVE_ENV) || env_add[0] != NULL)) {
+       if (ISSET(mode, MODE_PRESERVE_ENV))
+           warningx("the `-E' option is not valid in edit mode");
+       if (env_add[0] != NULL)
+           warningx("you may not specify environment variables in edit mode");
+       usage(1);
+    }
+    if ((runas_user != NULL || runas_group != NULL) &&
+       !ISSET(mode, MODE_EDIT | MODE_RUN | MODE_CHECK | MODE_VALIDATE)) {
+       usage(1);
+    }
+    if (list_user != NULL && mode != MODE_LIST && mode != MODE_CHECK) {
+       warningx("the `-U' option may only be used with the `-l' option");
+       usage(1);
+    }
+    if (ISSET(tgetpass_flags, TGP_STDIN) && ISSET(tgetpass_flags, TGP_ASKPASS)) {
+       warningx("the `-A' and `-S' options may not be used together");
+       usage(1);
+    }
+    if ((*nargc == 0 && mode == MODE_EDIT) ||
+       (*nargc > 0 && !ISSET(mode, MODE_RUN | MODE_EDIT | MODE_CHECK)))
+       usage(1);
+    if (*nargc == 0 && mode == MODE_RUN && !ISSET(flags, MODE_SHELL))
+       SET(flags, (MODE_IMPLIED_SHELL | MODE_SHELL));
+
+    if (mode == MODE_HELP)
+       usage(0);
+
+    /*
+     * Format setting_pairs into settings array.
+     */
+    settings = emalloc2(settings_size + 1, sizeof (char *));
+    for (i = 0, j = 0; i < settings_size; i++) {
+       if (setting_pairs[i]->value) {
+           settings[j++] = fmt_string(setting_pairs[i]->name,
+               setting_pairs[i]->value);
+       }
+    }
+    settings[j] = NULL;
+
+    *settingsp = settings;
+    *env_addp = env_add;
+    return(mode | flags);
+}
+
+/*
+ * Give usage message and exit.
+ * The actual usage strings are in sudo_usage.h for configure substitution.
+ * XXX - avoid lbuf usage
+ */
+static void
+usage(int exit_val)
+{
+    struct lbuf lbuf;
+    char *uvec[6];
+    int i, ulen;
+
+    /*
+     * Use usage vectors appropriate to the progname.
+     */
+    if (strcmp(getprogname(), "sudoedit") == 0) {
+       uvec[0] = SUDO_USAGE5 + 3;
+       uvec[1] = NULL;
+    } else {
+       uvec[0] = SUDO_USAGE1;
+       uvec[1] = SUDO_USAGE2;
+       uvec[2] = SUDO_USAGE3;
+       uvec[3] = SUDO_USAGE4;
+       uvec[4] = SUDO_USAGE5;
+       uvec[5] = NULL;
+    }
+
+    /*
+     * Print usage and wrap lines as needed, depending on the
+     * tty width.
+     */
+    ulen = (int)strlen(getprogname()) + 8;
+    lbuf_init(&lbuf, NULL, ulen, 0);
+    for (i = 0; uvec[i] != NULL; i++) {
+       lbuf_append(&lbuf, "usage: ", getprogname(), uvec[i], NULL);
+       lbuf_print(&lbuf);
+    }
+    lbuf_destroy(&lbuf);
+    exit(exit_val);
+}
+
+/*
+ * Tell which options are mutually exclusive and exit.
+ */
+static void
+usage_excl(int exit_val)
+{
+    warningx("Only one of the -e, -h, -i, -K, -l, -s, -v or -V options may be specified");
+    usage(exit_val);
+}
index 7d4f15a9de717d888e700cce942dd461ff1b08f9..bbb22f4e5492ba9ea12756ead78fbfcb46da98f7 100644 (file)
--- a/src/pty.c
+++ b/src/pty.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009 Todd C. Miller <Todd.Miller@courtesan.com>
+ * Copyright (c) 2009-2010 Todd C. Miller <Todd.Miller@courtesan.com>
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
 
 #if defined(HAVE_OPENPTY)
 int
-get_pty(master, slave, name, namesz)
-    int *master;
-    int *slave;
-    char *name;
-    size_t namesz;
+get_pty(int *master, int *slave, char *name, size_t namesz, uid_t uid)
 {
     struct group *gr;
     gid_t ttygid = -1;
 
-    if ((gr = sudo_getgrnam("tty")) != NULL)
+    if ((gr = getgrnam("tty")) != NULL)
        ttygid = gr->gr_gid;
 
     if (openpty(master, slave, name, NULL, NULL) != 0)
        return(0);
-    (void) chown(name, runas_pw->pw_uid, ttygid);
+    (void) chown(name, uid, ttygid);
     return(1);
 }
 
 #elif defined(HAVE__GETPTY)
 int
-get_pty(master, slave, name, namesz)
-    int *master;
-    int *slave;
-    char *name;
-    size_t namesz;
+get_pty(int *master, int *slave, char *name, size_t namesz, uid_t uid)
 {
     char *line;
 
@@ -95,7 +87,7 @@ get_pty(master, slave, name, namesz)
        close(*master);
        return(0);
     }
-    (void) chown(line, runas_pw->pw_uid, -1);
+    (void) chown(line, uid, -1);
     strlcpy(name, line, namesz);
     return(1);
 }
@@ -117,11 +109,7 @@ posix_openpt(oflag)
 # endif /* HAVE_POSIX_OPENPT */
 
 int
-get_pty(master, slave, name, namesz)
-    int *master;
-    int *slave;
-    char *name;
-    size_t namesz;
+get_pty(int *master, int *slave, char *name, size_t namesz, uid_t uid)
 {
     char *line;
 
@@ -148,7 +136,7 @@ get_pty(master, slave, name, namesz)
     ioctl(*slave, I_PUSH, "ptem");     /* pseudo tty emulation module */
     ioctl(*slave, I_PUSH, "ldterm");   /* line discipline module */
 # endif
-    (void) chown(line, runas_pw->pw_uid, -1);
+    (void) chown(line, uid, -1);
     strlcpy(name, line, namesz);
     return(1);
 }
@@ -158,17 +146,13 @@ get_pty(master, slave, name, namesz)
 static char line[] = "/dev/ptyXX";
 
 int
-get_pty(master, slave, name, namesz)
-    int *master;
-    int *slave;
-    char *name;
-    size_t namesz;
+get_pty(int *master, int *slave, char *name, size_t namesz, uid_t uid)
 {
     char *bank, *cp;
     struct group *gr;
     gid_t ttygid = -1;
 
-    if ((gr = sudo_getgrnam("tty")) != NULL)
+    if ((gr = getgrnam("tty")) != NULL)
        ttygid = gr->gr_gid;
 
     for (bank = "pqrs"; *bank != '\0'; bank++) {
@@ -182,7 +166,7 @@ get_pty(master, slave, name, namesz)
                continue; /* already in use */
            }
            line[sizeof("/dev/p") - 2] = 't';
-           (void) chown(line, runas_pw->pw_uid, ttygid);
+           (void) chown(line, uid, ttygid);
            (void) chmod(line, S_IRUSR|S_IWUSR|S_IWGRP);
 # ifdef HAVE_REVOKE
            (void) revoke(line);
index 91c92ad8c957bc6f5fd199c4dd9ae9fc98554751..7c022ce9bbaa63798934576c2f0156295530f8b4 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009 Todd C. Miller <Todd.Miller@courtesan.com>
+ * Copyright (c) 2009-2010 Todd C. Miller <Todd.Miller@courtesan.com>
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -33,7 +33,7 @@
 #endif /* HAVE_TERMIOS_H */
 #include <sys/ioctl.h>
 #ifdef HAVE_SYS_SELECT_H
-#include <sys/select.h>
+# include <sys/select.h>
 #endif /* HAVE_SYS_SELECT_H */
 #include <stdio.h>
 #ifdef STDC_HEADERS
 #ifdef HAVE_SELINUX
 # include <selinux/selinux.h>
 #endif
-#ifdef HAVE_ZLIB
-# include <zlib.h>
-#endif
 
-#include "sudo.h"
+#include "sudo.h" /* XXX? */
+#include "sudo_plugin.h"
+#include "sudo_plugin_int.h"
 
 #define SFD_MASTER     0
 #define SFD_SLAVE      1
-#define SFD_LOG                2
-#define SFD_OUTPUT     3
-#define SFD_TIMING     4
-#define SFD_USERTTY    5
+#define SFD_USERTTY    2
 
 #define TERM_COOKED    0
 #define TERM_CBREAK    1
 #define TERM_RAW       2
 
-union script_fd {
-    FILE *f;
-#ifdef HAVE_ZLIB
-    gzFile g;
-#endif
-};
-
 struct script_buf {
     int len; /* buffer length (how much read in) */
     int off; /* write position (how much already consumed) */
     char buf[16 * 1024];
 };
 
-static int script_fds[6];
+static int script_fds[3];
 static int ttyout;
 
+/* XXX - use an array of signals instead of just a single variable */
 static sig_atomic_t alive = 1;
 static sig_atomic_t recvsig = 0;
 static sig_atomic_t ttymode = TERM_COOKED;
@@ -114,247 +104,68 @@ static int foreground;
 
 static char slavename[PATH_MAX];
 
-static int suspend_parent __P((int signo, struct script_buf *output,
-    struct timeval *then, struct timeval *now, union script_fd ofile,
-    union script_fd tfile));
-static void flush_output __P((struct script_buf *output, struct timeval *then,
-    struct timeval *now, union script_fd ofile, union script_fd tfile));
-static void handler __P((int s));
-static void script_child __P((char *path, char *argv[], int, int));
-static void script_run __P((char *path, char *argv[], int));
-static void sigchild __P((int s));
-static void sigwinch __P((int s));
-static void sync_winsize __P((int src, int dst));
-
-extern int get_pty __P((int *master, int *slave, char *name, size_t namesz));
-
-/*
- * TODO: run monitor as root?
- */
-
-static int
-fdcompar(v1, v2)
-    const void *v1;
-    const void *v2;
-{
-    int i = *(int *)v1;
-    int j = *(int *)v2;
-
-    return(script_fds[i] - script_fds[j]);
-}
-
-void
-script_nextid()
-{
-    struct stat sb;
-    char buf[32], *ep;
-    int fd, i, ch;
-    unsigned long id = 0;
-    int len;
-    ssize_t nread;
-    char pathbuf[PATH_MAX];
-
-    /*
-     * Create _PATH_SUDO_TRANSCRIPT if it doesn't already exist.
-     */
-    if (stat(_PATH_SUDO_TRANSCRIPT, &sb) != 0) {
-       if (mkdir(_PATH_SUDO_TRANSCRIPT, S_IRWXU) != 0)
-           log_error(USE_ERRNO, "Can't mkdir %s", _PATH_SUDO_TRANSCRIPT);
-    } else if (!S_ISDIR(sb.st_mode)) {
-       log_error(0, "%s exists but is not a directory (0%o)",
-           _PATH_SUDO_TRANSCRIPT, (unsigned int) sb.st_mode);
-    }
-
-    /*
-     * Open sequence file
-     */
-    len = snprintf(pathbuf, sizeof(pathbuf), "%s/seq", _PATH_SUDO_TRANSCRIPT);
-    if (len <= 0 || len >= sizeof(pathbuf)) {
-       errno = ENAMETOOLONG;
-       log_error(USE_ERRNO, "%s/seq", pathbuf);
-    }
-    fd = open(pathbuf, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR);
-    if (fd == -1)
-       log_error(USE_ERRNO, "cannot open %s", pathbuf);
-    lock_file(fd, SUDO_LOCK);
-
-    /* Read seq number (base 36). */
-    nread = read(fd, buf, sizeof(buf));
-    if (nread != 0) {
-       if (nread == -1)
-           log_error(USE_ERRNO, "cannot read %s", pathbuf);
-       id = strtoul(buf, &ep, 36);
-       if (buf == ep || id >= 2176782336U)
-           log_error(0, "invalid sequence number %s", pathbuf);
-    }
-    id++;
-
-    /*
-     * Convert id to a string and stash in sudo_user.sessid.
-     * Note that that least significant digits go at the end of the string.
-     */
-    for (i = 5; i >= 0; i--) {
-       ch = id % 36;
-       id /= 36;
-       buf[i] = ch < 10 ? ch + '0' : ch - 10 + 'A';
-    }
-    buf[6] = '\n';
-
-    /* Stash id logging purposes */
-    memcpy(sudo_user.sessid, buf, 6);
-    sudo_user.sessid[6] = '\0';
-
-    /* Rewind and overwrite old seq file. */
-    if (lseek(fd, 0, SEEK_SET) == (off_t)-1 || write(fd, buf, 7) != 7)
-       log_error(USE_ERRNO, "Can't write to %s", pathbuf);
-    close(fd);
-}
-
-static int
-build_idpath(pathbuf)
-    char *pathbuf;
-{
-    struct stat sb;
-    int i, len;
-
-    if (sudo_user.sessid[0] == '\0')
-       log_error(0, "tried to build a session id path without a session id");
-
-    /*
-     * Path is of the form /var/log/sudo-session/00/00/01.
-     */
-    len = snprintf(pathbuf, PATH_MAX, "%s/%c%c/%c%c/%c%c", _PATH_SUDO_TRANSCRIPT,
-       sudo_user.sessid[0], sudo_user.sessid[1], sudo_user.sessid[2],
-       sudo_user.sessid[3], sudo_user.sessid[4], sudo_user.sessid[5]);
-    if (len <= 0 && len >= PATH_MAX) {
-       errno = ENAMETOOLONG;
-       log_error(USE_ERRNO, "%s/%s", _PATH_SUDO_TRANSCRIPT, sudo_user.sessid);
-    }
-
-    /*
-     * Create the intermediate subdirs as needed.
-     */
-    for (i = 6; i > 0; i -= 3) {
-       pathbuf[len - i] = '\0';
-       if (stat(pathbuf, &sb) != 0) {
-           if (mkdir(pathbuf, S_IRWXU) != 0)
-               log_error(USE_ERRNO, "Can't mkdir %s", pathbuf);
-       } else if (!S_ISDIR(sb.st_mode)) {
-           log_error(0, "%s: %s", pathbuf, strerror(ENOTDIR));
-       }
-       pathbuf[len - i] = '/';
-    }
+static int suspend_parent(int signo, struct script_buf *output);
+static void flush_output(struct script_buf *output);
+static void handler(int s);
+static int script_child(const char *path, char *argv[], char *envp[], int, int);
+static void script_run(const char *path, char *argv[], char *envp[], int);
+static void sigchild(int s);
+static void sigwinch(int s);
+static void sync_winsize(int src, int dst);
 
-    return(len);
-}
+/* sudo.c */
+extern struct plugin_container_list io_plugins;
 
 void
-script_setup()
+script_setup(uid_t uid)
 {
-    char pathbuf[PATH_MAX];
-    int len;
-
     script_fds[SFD_USERTTY] = open(_PATH_TTY, O_RDWR|O_NOCTTY, 0);
     if (script_fds[SFD_USERTTY] == -1)
-       log_error(0, "tty required for transcript support"); /* XXX */
+       errorx(1, "tty required for transcript support");
 
     if (!get_pty(&script_fds[SFD_MASTER], &script_fds[SFD_SLAVE],
-       slavename, sizeof(slavename)))
-       log_error(USE_ERRNO, "Can't get pty");
-
-    /*
-     * Build a path containing the session id split into two-digit subdirs,
-     * so ID 000001 becomes /var/log/sudo-session/00/00/01.
-     */
-    len = build_idpath(pathbuf);
-
-    /*
-     * We create 3 files: a log file, one for the raw session data,
-     * and one for the timing info.
-     */
-    script_fds[SFD_LOG] = open(pathbuf, O_CREAT|O_EXCL|O_WRONLY, S_IRUSR|S_IWUSR);
-    if (script_fds[SFD_LOG] == -1)
-       log_error(USE_ERRNO, "Can't create %s", pathbuf);
-
-    strlcat(pathbuf, ".scr", sizeof(pathbuf));
-    script_fds[SFD_OUTPUT] = open(pathbuf, O_CREAT|O_EXCL|O_WRONLY,
-       S_IRUSR|S_IWUSR);
-    if (script_fds[SFD_OUTPUT] == -1)
-       log_error(USE_ERRNO, "Can't create %s", pathbuf);
-
-    pathbuf[len] = '\0';
-    strlcat(pathbuf, ".tim", sizeof(pathbuf));
-    script_fds[SFD_TIMING] = open(pathbuf, O_CREAT|O_EXCL|O_WRONLY, S_IRUSR|S_IWUSR);
-    if (script_fds[SFD_TIMING] == -1)
-       log_error(USE_ERRNO, "Can't create %s", pathbuf);
+       slavename, sizeof(slavename), uid))
+       error(1, "Can't get pty");
 }
 
-int
-script_duplow(fd)
-    int fd;
+/* Call I/O plugin input method. */
+static void
+log_input(char *buf, unsigned int n)
 {
-    int i, j, indices[6];
-
-    /* sort fds so we can dup them safely */
-    for (i = 0; i < 6; i++)
-       indices[i] = i;
-    qsort(indices, 6, sizeof(int), fdcompar);
-
-    /* Move pty master/slave and session fds to low numbered fds. */
-    for (i = 0; i < 6; i++) {
-       j = indices[i];
-       if (script_fds[j] != fd) {
-#ifdef HAVE_DUP2
-           dup2(script_fds[j], fd);
-#else
-           close(fd);
-           dup(script_fds[j]);
-           close(script_fds[j]);
-#endif
-       }
-       script_fds[j] = fd++;
+    struct plugin_container *plugin;
+    sigset_t omask;
+
+    sigprocmask(SIG_BLOCK, &ttyblock, &omask);
+
+    tq_foreach_fwd(&io_plugins, plugin) {
+       /* XXX - die if return != TRUE */
+       if (plugin->u.io->log_input)
+           plugin->u.io->log_input(buf, n);
     }
-    return(fd);
+
+    sigprocmask(SIG_SETMASK, &omask, NULL);
 }
 
-/* Update output and timing files. */
+/* Call I/O plugin output method. */
 static void
-log_output(buf, n, then, now, ofile, tfile)
-    char *buf;
-    int n;
-    struct timeval *then;
-    struct timeval *now;
-    union script_fd ofile;
-    union script_fd tfile;
+log_output(char *buf, unsigned int n)
 {
-    struct timeval tv;
+    struct plugin_container *plugin;
     sigset_t omask;
 
     sigprocmask(SIG_BLOCK, &ttyblock, &omask);
 
-#ifdef HAVE_ZLIB
-    if (def_compress_transcript)
-       gzwrite(ofile.g, buf, n);
-    else
-#endif
-       fwrite(buf, 1, n, ofile.f);
-    timersub(now, then, &tv);
-#ifdef HAVE_ZLIB
-    if (def_compress_transcript)
-       gzprintf(tfile.g, "%f %d\n",
-           tv.tv_sec + ((double)tv.tv_usec / 1000000), n);
-    else
-#endif
-       fprintf(tfile.f, "%f %d\n",
-           tv.tv_sec + ((double)tv.tv_usec / 1000000), n);
-    then->tv_sec = now->tv_sec;
-    then->tv_usec = now->tv_usec;
+    tq_foreach_fwd(&io_plugins, plugin) {
+       /* XXX - die if return != TRUE */
+       if (plugin->u.io->log_output)
+           plugin->u.io->log_output(buf, n);
+    }
 
     sigprocmask(SIG_SETMASK, &omask, NULL);
 }
 
 static void
-check_foreground()
+check_foreground(void)
 {
     foreground = tcgetpgrp(script_fds[SFD_USERTTY]) == parent;
     if (foreground && !tty_initialized) {
@@ -370,13 +181,7 @@ check_foreground()
  * Returns SIGUSR1 if the child should be resume in foreground else SIGUSR2.
  */
 static int
-suspend_parent(signo, output, then, now, ofile, tfile)
-    int signo;
-    struct script_buf *output;
-    struct timeval *then;
-    struct timeval *now;
-    union script_fd ofile;
-    union script_fd tfile;
+suspend_parent(int signo, struct script_buf *output)
 {
     sigaction_t sa, osa;
     int n, oldmode = ttymode, rval = 0;
@@ -404,7 +209,7 @@ suspend_parent(signo, output, then, now, ofile, tfile)
        /* FALLTHROUGH */
     case SIGTSTP:
        /* Flush any remaining output to master tty. */
-       flush_output(output, then, now, ofile, tfile);
+       flush_output(output);
 
        /* Restore original tty mode before suspending. */
        if (oldmode != TERM_COOKED) {
@@ -469,19 +274,18 @@ suspend_parent(signo, output, then, now, ofile, tfile)
  *     controlling tty, belongs to child's session but has its own pgrp.
  */
 int
-script_execv(path, argv)
-    char *path;
-    char *argv[];
+script_execve(struct command_details *details, char *argv[], char *envp[],
+    struct command_status *cstat)
 {
     sigaction_t sa;
     struct script_buf input, output;
-    struct timeval now, then;
-    int n, nready, exitcode = 1;
+    int n, nready;
     int relaysig, sv[2];
     fd_set *fdsr, *fdsw;
-    FILE *idfile;
-    union script_fd ofile, tfile;
     int rbac_enabled = 0;
+    int maxfd;
+
+    cstat->type = 0; /* XXX */
 
 #ifdef HAVE_SELINUX
     rbac_enabled = is_selinux_enabled() > 0 && user_role != NULL;
@@ -491,7 +295,7 @@ script_execv(path, argv)
        close(script_fds[SFD_SLAVE]);
        script_fds[SFD_SLAVE] = open(slavename, O_RDWR|O_NOCTTY, 0);
        if (script_fds[SFD_SLAVE] == -1)
-           log_error(USE_ERRNO, "cannot open %s", slavename);
+           error(1, "cannot open %s", slavename);
     }
 #endif
 
@@ -542,8 +346,8 @@ script_execv(path, argv)
      * We communicate with the child over a bi-directional pipe.
      * Parent sends signal info to child and child sends back wait status.
      */
-    if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) != 0)
-       log_error(USE_ERRNO, "cannot create sockets");
+    if (socketpair(PF_UNIX, SOCK_DGRAM, 0, sv) != 0)
+       error(1, "cannot create sockets");
 
     if (foreground) {
        /* Copy terminal attrs from user tty -> pty slave. */
@@ -559,7 +363,7 @@ script_execv(path, argv)
                ttymode == TERM_CBREAK);
        } while (!n && errno == EINTR);
        if (!n)
-           log_error(USE_ERRNO, "Can't set terminal to raw mode");
+           error(1, "Can't set terminal to raw mode");
     }
 
     /*
@@ -569,43 +373,22 @@ script_execv(path, argv)
     child = fork();
     switch (child) {
     case -1:
-       log_error(USE_ERRNO, "fork");
+       error(1, "fork");
        break;
     case 0:
+       /* child */
        close(sv[0]);
-       script_child(path, argv, sv[1], rbac_enabled);
-       /* NOTREACHED */
-       break;
+       if (exec_setup(details) == 0) {
+           /* headed for execve() */
+           script_child(details->command, argv, envp, sv[1], rbac_enabled);
+       }
+       cstat->type = CMD_ERRNO;
+       cstat->val = errno;
+       send(sv[1], cstat, sizeof(*cstat), 0);
+       _exit(1);
     }
     close(sv[1]);
 
-    if ((idfile = fdopen(script_fds[SFD_LOG], "w")) == NULL)
-       log_error(USE_ERRNO, "fdopen");
-#ifdef HAVE_ZLIB
-    if (def_compress_transcript) {
-       if ((ofile.g = gzdopen(script_fds[SFD_OUTPUT], "w")) == NULL)
-           log_error(USE_ERRNO, "gzdopen");
-       if ((tfile.g = gzdopen(script_fds[SFD_TIMING], "w")) == NULL)
-           log_error(USE_ERRNO, "gzdopen");
-    } else
-#endif
-    {
-       if ((ofile.f = fdopen(script_fds[SFD_OUTPUT], "w")) == NULL)
-           log_error(USE_ERRNO, "fdopen");
-       if ((tfile.f = fdopen(script_fds[SFD_TIMING], "w")) == NULL)
-           log_error(USE_ERRNO, "fdopen");
-    }
-
-    gettimeofday(&then, NULL);
-
-    /* XXX - log more stuff?  window size? environment? */
-    fprintf(idfile, "%ld:%s:%s:%s:%s\n", then.tv_sec, user_name,
-       runas_pw->pw_name, runas_gr ? runas_gr->gr_name : "", user_tty);
-    fprintf(idfile, "%s\n", user_cwd);
-    fprintf(idfile, "%s%s%s\n", user_cmnd, user_args ? " " : "",
-       user_args ? user_args : "");
-    fclose(idfile);
-
     n = fcntl(script_fds[SFD_MASTER], F_GETFL, 0);
     if (n != -1) {
        n |= O_NONBLOCK;
@@ -622,13 +405,19 @@ script_execv(path, argv)
        (void) fcntl(STDOUT_FILENO, F_SETFL, n);
     }
 
+    /* Max fd we will be selecting on. */
+    maxfd = sv[0];
+    if (maxfd < script_fds[SFD_MASTER])
+       maxfd = script_fds[SFD_MASTER];
+    if (maxfd < script_fds[SFD_USERTTY])
+       maxfd = script_fds[SFD_USERTTY];
+
     /*
      * In the event loop we pass input from user tty to master
-     * and pass output from master to stdout and ofile.  Note that
-     * we've set things up such that master is > 3 (see sudo.c).
+     * and pass output from master to stdout and IO plugin.
      */
-    fdsr = (fd_set *)emalloc2(howmany(sv[0] + 1, NFDBITS), sizeof(fd_mask));
-    fdsw = (fd_set *)emalloc2(howmany(sv[0] + 1, NFDBITS), sizeof(fd_mask));
+    fdsr = (fd_set *)emalloc2(howmany(maxfd + 1, NFDBITS), sizeof(fd_mask));
+    fdsw = (fd_set *)emalloc2(howmany(maxfd + 1, NFDBITS), sizeof(fd_mask));
     zero_bytes(&input, sizeof(input));
     zero_bytes(&output, sizeof(output));
     while (alive) {
@@ -643,8 +432,8 @@ script_execv(path, argv)
        if (output.off == output.len)
            output.off = output.len = 0;
 
-       zero_bytes(fdsw, howmany(sv[0] + 1, NFDBITS) * sizeof(fd_mask));
-       zero_bytes(fdsr, howmany(sv[0] + 1, NFDBITS) * sizeof(fd_mask));
+       zero_bytes(fdsw, howmany(maxfd + 1, NFDBITS) * sizeof(fd_mask));
+       zero_bytes(fdsr, howmany(maxfd + 1, NFDBITS) * sizeof(fd_mask));
 
        if (ttymode == TERM_RAW && input.len != sizeof(input.buf))
            FD_SET(script_fds[SFD_USERTTY], fdsr);
@@ -658,51 +447,50 @@ script_execv(path, argv)
        if (relaysig)
            FD_SET(sv[0], fdsw);
 
-       nready = select(sv[0] + 1, fdsr, fdsw, NULL, NULL);
+       nready = select(maxfd + 1, fdsr, fdsw, NULL, NULL);
        if (nready == -1) {
            if (errno == EINTR)
                continue;
-           log_error(USE_ERRNO, "select failed");
+           error(1, "select failed");
        }
        if (FD_ISSET(sv[0], fdsr)) {
            /* read child status */
-           n = read(sv[0], &child_status, sizeof(child_status));
+           n = recv(sv[0], cstat, sizeof(*cstat), 0);
            if (n == -1) {
                if (errno == EINTR)
                    continue;
                if (errno != EAGAIN)
                    break;
-           } else if (n != sizeof(child_status)) {
+           } else if (n != sizeof(*cstat)) {
                break; /* EOF? */
            }
-           if (WIFSTOPPED(child_status)) {
-               /* Suspend parent and tell child how to resume on return. */
+           if (cstat->type == CMD_WSTATUS) {
+               if (WIFSTOPPED(cstat->val)) {
+                   /* Suspend parent and tell child how to resume on return. */
 #ifdef SCRIPT_DEBUG
-               warningx("child stopped, suspending parent");
+                   warningx("child stopped, suspending parent");
 #endif
-               relaysig = suspend_parent(WSTOPSIG(child_status),
-                   &output, &then, &now, ofile, tfile);
-               /* XXX - write relaysig immediately? */
-               continue;
-           } else {
-               /* Child exited or was killed, either way we are done. */
-               if (WIFEXITED(child_status))
-                   exitcode = WEXITSTATUS(child_status);
-               else if (WIFSIGNALED(child_status))
-                   exitcode = WTERMSIG(child_status) | 128;
+                   relaysig = suspend_parent(WSTOPSIG(cstat->val), &output);
+                   /* XXX - write relaysig immediately? */
+                   continue;
+               } else {
+                   /* Child exited or was killed, either way we are done. */
+                   break;
+               }
+           } else if (cstat->type == CMD_ERRNO) {
+               /* Child was unable to execute command. */
                break;
            }
        }
        if (FD_ISSET(sv[0], fdsw)) {
            /* XXX - we rely on child to be suspended before we suspend us */
-           n = write(sv[0], &relaysig, sizeof(relaysig));
+           cstat->type = CMD_SIGNO;
+           cstat->val = relaysig;
            relaysig = 0;
-           if (n == -1) {
-               if (errno == EINTR)
-                   continue;
-               if (errno != EAGAIN)
-                   break;
-           } else if (n != sizeof(relaysig)) {
+           do {
+               n = send(sv[0], cstat, sizeof(*cstat), 0);
+           } while (n == -1 && errno == EINTR);
+           if (n != sizeof(relaysig)) {
                break; /* should not happen */
            }
        }
@@ -717,6 +505,7 @@ script_execv(path, argv)
            } else {
                if (n == 0)
                    break; /* got EOF */
+               log_input(input.buf + input.len, n);
                input.len += n;
            }
        }
@@ -733,7 +522,6 @@ script_execv(path, argv)
            }
        }
        if (FD_ISSET(script_fds[SFD_MASTER], fdsr)) {
-           gettimeofday(&now, NULL);
            n = read(script_fds[SFD_MASTER], output.buf + output.len,
                sizeof(output.buf) - output.len);
            if (n == -1) {
@@ -744,9 +532,7 @@ script_execv(path, argv)
            } else {
                if (n == 0)
                    break; /* got EOF */
-
-               /* Update output and timing files. */
-               log_output(output.buf + output.len, n, &then, &now, ofile, tfile);
+               log_output(output.buf + output.len, n);
                output.len += n;
            }
        }
@@ -770,26 +556,15 @@ script_execv(path, argv)
        n &= ~O_NONBLOCK;
        (void) fcntl(STDOUT_FILENO, F_SETFL, n);
     }
-    flush_output(&output, &then, &now, ofile, tfile);
-
-#ifdef HAVE_ZLIB
-    if (def_compress_transcript) {
-       gzclose(ofile.g);
-       gzclose(tfile.g);
-    } else
-#endif
-    {
-       fclose(ofile.f);
-       fclose(tfile.f);
-    }
+    flush_output(&output);
 
 #ifdef HAVE_STRSIGNAL
-    if (WIFSIGNALED(child_status)) {
-       int signo = WTERMSIG(child_status);
+    if (cstat->type == CMD_WSTATUS && WIFSIGNALED(cstat->val)) {
+       int signo = WTERMSIG(cstat->val);
        if (signo && signo != SIGINT && signo != SIGPIPE) {
            char *reason = strsignal(signo);
            write(STDOUT_FILENO, reason, strlen(reason));
-           if (WCOREDUMP(child_status))
+           if (WCOREDUMP(cstat->val))
                write(STDOUT_FILENO, " (core dumped)", 14);
            write(STDOUT_FILENO, "\n", 1);
        }
@@ -800,30 +575,22 @@ script_execv(path, argv)
        n = term_restore(script_fds[SFD_USERTTY], 0);
     } while (!n && errno == EINTR);
 
-    exit(exitcode);
+    return cstat->type == CMD_ERRNO ? -1 : 0;
 }
 
-void
-script_child(path, argv, backchannel, rbac_enabled)
-    char *path;
-    char *argv[];
-    int backchannel;
-    int rbac_enabled;
+int
+script_child(const char *path, char *argv[], char *envp[], int backchannel, int rbac)
 {
+    struct command_status cstat;
+    fd_set *fdsr;
     sigaction_t sa;
     pid_t pid, self = getpid();
-    int nread, signo, status;
-#ifndef TIOCSCTTY
-    int n;
-#endif
+    int n, signo, status;
 
     recvsig = 0;
 
     /* Close unused fds. */
     close(script_fds[SFD_MASTER]);
-    close(script_fds[SFD_LOG]);
-    close(script_fds[SFD_OUTPUT]);
-    close(script_fds[SFD_TIMING]);
     close(script_fds[SFD_USERTTY]);
 
     /* Reset signal handlers. */
@@ -840,8 +607,7 @@ script_child(path, argv, backchannel, rbac_enabled)
     sigaction(SIGTTIN, &sa, NULL);
     sigaction(SIGTTOU, &sa, NULL);
 
-    /* We want SIGCHLD to interrupt us. */
-    sa.sa_flags = 0; /* do not restart syscalls for these signals. */
+    /* SIGCHLD will interrupt select. */
     sa.sa_handler = handler;
     sigaction(SIGCHLD, &sa, NULL);
 
@@ -853,7 +619,7 @@ script_child(path, argv, backchannel, rbac_enabled)
 #ifdef HAVE_SETSID
     if (setsid() == -1) {
        warning("setsid");
-       _exit(1);
+       goto bad;
     }
 #else
 # ifdef TIOCNOTTY
@@ -869,7 +635,7 @@ script_child(path, argv, backchannel, rbac_enabled)
 #endif
 #ifdef TIOCSCTTY
     if (ioctl(script_fds[SFD_SLAVE], TIOCSCTTY, NULL) != 0)
-       log_error(USE_ERRNO, "unable to set controlling tty");
+       error(1, "unable to set controlling tty");
 #else
     /* Set controlling tty by reopening slave. */
     if ((n = open(slavename, O_RDWR)) >= 0)
@@ -880,10 +646,11 @@ script_child(path, argv, backchannel, rbac_enabled)
        foreground = 0;
 
     /* Start command and wait for it to stop or exit */
+    /* XXX - use details->timeout */
     child = fork();
     if (child == -1) {
        warning("Can't fork");
-       _exit(1);
+       goto bad;
     }
     if (child == 0) {
        /* Reset signal handlers. */
@@ -900,9 +667,9 @@ script_child(path, argv, backchannel, rbac_enabled)
        sigaction(SIGUSR2, &sa, NULL);
 
        /* setup tty and exec command */
-       script_run(path, argv, rbac_enabled);
-       warning("unable to execute %s", path);
-       _exit(127);
+       script_run(path, argv, envp, rbac);
+       warning("unable to execute %s", path); /* XXX - leave this to plugin? */
+       goto bad;
     }
 
     /*
@@ -917,6 +684,9 @@ script_child(path, argv, backchannel, rbac_enabled)
     }
 
     /* Wait for signal on backchannel or for SIGCHLD */
+    fdsr = (fd_set *)emalloc2(howmany(backchannel + 1, NFDBITS), sizeof(fd_mask));
+    zero_bytes(fdsr, howmany(backchannel + 1, NFDBITS) * sizeof(fd_mask));
+    FD_SET(backchannel, fdsr);
     for (;;) {
        /* Read child status, assumes recvsig can only be SIGCHLD */
        while (recvsig) {
@@ -934,26 +704,43 @@ script_child(path, argv, backchannel, rbac_enabled)
                else
                    warningx("command exited?");
 #endif
-               if (write(backchannel, &status, sizeof(status)) != sizeof(status))
+               cstat.type = CMD_WSTATUS;
+               cstat.val = status;
+               do {
+                   n = send(backchannel, &cstat, sizeof(cstat), 0);
+               } while (n == -1 && errno == EINTR);
+               if (n != sizeof(cstat))
                    break; /* XXX - error, kill child and exit */
 #ifdef SCRIPT_DEBUG
                warningx("sent signo to parent");
 #endif
                if (!WIFSTOPPED(status)) {
+                   /* XXX */
                    _exit(1); /* child dead */
                }
            }
        }
-       nread = read(backchannel, &signo, sizeof(signo));
-       if (nread == -1) {
-           if (errno != EINTR)
-               break; /* XXX - error, kill child and exit */
-           continue;
+       n = select(backchannel + 1, fdsr, NULL, NULL, NULL);
+       if (n == -1) {
+           if (errno == EINTR)
+               continue;
+           error(1, "select failed");
        }
-       if (nread != sizeof(signo)) {
-           /* EOF? */
+
+       /* read child status */
+       n = recv(backchannel, &cstat, sizeof(cstat), 0);
+       if (n == -1) {
+           if (errno == EINTR)
+               continue;
+       } else if (n != sizeof(cstat)) {
+           warningx("error reading command status");
            break;
        }
+       if (cstat.type != CMD_SIGNO) {
+           warningx("unexpected reply type on backchannel: %d", cstat.type);
+           continue;
+       }
+       signo = cstat.val;
 
        /* Handle signal from parent. */
 #ifdef SCRIPT_DEBUG
@@ -961,7 +748,7 @@ script_child(path, argv, backchannel, rbac_enabled)
 #endif
        switch (signo) {
        case SIGKILL:
-           _exit(1);
+           _exit(1); /* XXX */
            /* NOTREACHED */
        case SIGHUP:
        case SIGTERM:
@@ -991,16 +778,14 @@ script_child(path, argv, backchannel, rbac_enabled)
        }
     }
 
-    _exit(1);
+    _exit(1); /* XXX */
+
+bad:
+    return errno;
 }
 
 static void
-flush_output(output, then, now, ofile, tfile)
-    struct script_buf *output;
-    struct timeval *then;
-    struct timeval *now;
-    union script_fd ofile;
-    union script_fd tfile;
+flush_output(struct script_buf *output)
 {
     int n;
 
@@ -1017,7 +802,8 @@ flush_output(output, then, now, ofile, tfile)
        n = read(script_fds[SFD_MASTER], output->buf, sizeof(output->buf));
        if (n <= 0)
            break;
-       log_output(output->buf, n, &then, &now, ofile, tfile);
+       /* XXX */
+       log_output(output->buf, n);
        output->off = 0;
        output->len = n;
        do {
@@ -1031,10 +817,7 @@ flush_output(output, then, now, ofile, tfile)
 }
 
 static void
-script_run(path, argv, rbac_enabled)
-    char *path;
-    char *argv[];
-    int rbac_enabled;
+script_run(const char *path, char *argv[], char *envp[], int rbac_enabled)
 {
     pid_t self = getpid();
 
@@ -1058,16 +841,14 @@ script_run(path, argv, rbac_enabled)
 
 #ifdef HAVE_SELINUX
     if (rbac_enabled)
-       selinux_execv(path, argv);
+       selinux_execve(path, argv, envp);
     else
 #endif
-    execv(path, argv);
+    execve(path, argv, envp);
 }
 
 static void
-sync_winsize(src, dst)
-    int src;
-    int dst;
+sync_winsize(int src, int dst)
 {
 #ifdef TIOCGWINSZ
     struct winsize win;
@@ -1087,8 +868,7 @@ sync_winsize(src, dst)
  * Handler for SIGCHLD in parent
  */
 static void
-sigchild(s)
-    int s;
+sigchild(int s)
 {
     pid_t pid;
     int serrno = errno;
@@ -1110,8 +890,7 @@ sigchild(s)
  * Generic handler for signals passed from parent -> child
  */
 static void
-handler(s)
-    int s;
+handler(int s)
 {
     recvsig = s;
 }
@@ -1120,8 +899,7 @@ handler(s)
  * Handler for SIGWINCH in parent
  */
 static void
-sigwinch(s)
-    int s;
+sigwinch(int s)
 {
     int serrno = errno;
 
diff --git a/src/sudo.c b/src/sudo.c
new file mode 100644 (file)
index 0000000..293c55b
--- /dev/null
@@ -0,0 +1,893 @@
+/*
+ * Copyright (c) 2009-2010 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * 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.
+ */
+
+#ifdef __TANDEM
+# include <floss.h>
+#endif
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/socket.h>
+#ifdef HAVE_SYS_SELECT_H
+# include <sys/select.h>
+#endif /* HAVE_SYS_SELECT_H */
+#ifdef HAVE_SETRLIMIT
+# include <sys/time.h>
+# include <sys/resource.h>
+#endif
+#include <stdio.h>
+#ifdef STDC_HEADERS
+# include <stdlib.h>
+# include <stddef.h>
+#else
+# ifdef HAVE_STDLIB_H
+#  include <stdlib.h>
+# endif
+#endif /* STDC_HEADERS */
+#ifdef HAVE_STRING_H
+# if defined(HAVE_MEMORY_H) && !defined(STDC_HEADERS)
+#  include <memory.h>
+# endif
+# include <string.h>
+#else
+# ifdef HAVE_STRINGS_H
+#  include <strings.h>
+# endif
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif /* HAVE_UNISTD_H */
+#include <pwd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <signal.h>
+#include <grp.h>
+#if TIME_WITH_SYS_TIME
+# include <time.h>
+#endif
+#ifdef HAVE_SETLOCALE
+# include <locale.h>
+#endif
+#ifdef HAVE_LOGIN_CAP_H
+# include <login_cap.h>
+#endif
+
+#include <sudo_usage.h>
+#include "sudo.h"
+#include "sudo_plugin.h"
+#include "sudo_plugin_int.h"
+
+#ifdef USING_NONUNIX_GROUPS
+# include "nonunix.h"
+#endif
+
+/*
+ * Local variables
+ */
+struct plugin_container policy_plugin;
+struct plugin_container_list io_plugins;
+
+/*
+ * Local functions
+ */
+static void fix_fds(void);
+static void disable_coredumps(void);
+static char **get_user_info(struct user_details *);
+static void command_info_to_details(char * const info[],
+    struct command_details *details);
+static int run_command(struct command_details *details, char *argv[],
+    char *envp[]);
+
+/* XXX - header file */
+extern const char *list_user, *runas_user, *runas_group;
+
+/* Used by getprogname() unless crt0 supports getting program name. */
+int Argc;
+char **Argv;
+
+#if defined(RLIMIT_CORE) && !defined(SUDO_DEVEL)
+static struct rlimit corelimit;
+#endif /* RLIMIT_CORE && !SUDO_DEVEL */
+sigaction_t saved_sa_int, saved_sa_quit, saved_sa_tstp;
+
+int
+main(int argc, char *argv[], char *envp[])
+{
+    sigaction_t sa;
+    int nargc, sudo_mode;
+    char **nargv, **settings, **env_add;
+    char **user_info, **command_info, **argv_out, **user_env_out;
+    struct plugin_container *plugin;
+    struct user_details user_details;
+    struct command_details command_details;
+    int ok;
+#if defined(SUDO_DEVEL) && defined(__OpenBSD__)
+    extern char *malloc_options;
+    malloc_options = "AFGJPR";
+#endif
+
+    Argc = argc;
+    Argv = argv;
+
+#ifdef HAVE_SETLOCALE
+    setlocale(LC_ALL, "");
+#endif
+
+    if (geteuid() != 0)
+       errorx(1, "must be setuid root");
+
+    /* XXX - Must be done before shadow file lookups... */
+#if defined(HAVE_GETPRPWNAM) && defined(HAVE_SET_AUTH_PARAMETERS)
+    (void) set_auth_parameters(Argc, Argv);
+# ifdef HAVE_INITPRIVS
+    initprivs();
+# endif
+#endif /* HAVE_GETPRPWNAM && HAVE_SET_AUTH_PARAMETERS */
+
+    /*
+     * Signal setup:
+     * Ignore keyboard-generated signals so the user cannot interrupt
+     *  us at some point and avoid the logging.
+     * XXX - leave this to the plugin?
+     */
+    zero_bytes(&sa, sizeof(sa));
+    sigemptyset(&sa.sa_mask);
+    sa.sa_flags = SA_RESTART;
+    sa.sa_handler = SIG_IGN;
+    (void) sigaction(SIGINT, &sa, &saved_sa_int);
+    (void) sigaction(SIGQUIT, &sa, &saved_sa_quit);
+    (void) sigaction(SIGTSTP, &sa, &saved_sa_tstp);
+
+    /* Turn off core dumps and make sure fds 0-2 are open. */
+    disable_coredumps();
+    fix_fds();
+
+    /* Parse command line arguments. */
+    sudo_mode = parse_args(Argc, Argv, &nargc, &nargv, &settings, &env_add);
+
+    /* Read sudo.conf and load plugins. */
+    sudo_load_plugins(_PATH_SUDO_CONF, &policy_plugin, &io_plugins);
+
+    /* Fill in user_info with user name, uid, cwd, etc. */
+    memset(&user_details, 0, sizeof(user_details));
+    user_info = get_user_info(&user_details);
+
+    /* Open each plugin (XXX - check for errors). */
+    policy_plugin.u.policy->open(SUDO_API_VERSION, sudo_conversation,
+       settings, user_info, envp);
+    tq_foreach_fwd(&io_plugins, plugin) {
+       /* XXX - remove from list if open returns 0 */
+       plugin->u.io->open(SUDO_API_VERSION, sudo_conversation, settings,
+           user_info, envp);
+    }
+
+    /* XXX - should not need to check for MODE_INVALIDATE ORed in */
+    warningx("sudo_mode %d", sudo_mode); /* XXX */
+    switch (sudo_mode & MODE_MASK) {
+       case MODE_VERSION:
+           policy_plugin.u.policy->show_version(!user_details.uid);
+           tq_foreach_fwd(&io_plugins, plugin) {
+               plugin->u.io->show_version(!user_details.uid);
+           }
+           break;
+       case MODE_VALIDATE:
+       case MODE_VALIDATE|MODE_INVALIDATE:
+           if (policy_plugin.u.policy->validate == NULL) {
+               warningx("policy plugin %s does not support the -v flag",
+                   policy_plugin.name);
+               ok = FALSE;
+           } else {
+               ok = policy_plugin.u.policy->validate();
+           }
+           exit(ok != TRUE);
+       case MODE_KILL:
+       case MODE_INVALIDATE:
+           if (policy_plugin.u.policy->validate == NULL) {
+               warningx("policy plugin %s does not support the -k/-K flags",
+                   policy_plugin.name);
+               exit(1);
+           }
+           policy_plugin.u.policy->invalidate(sudo_mode == MODE_KILL);
+           exit(0);
+           break;
+       case MODE_CHECK:
+       case MODE_CHECK|MODE_INVALIDATE:
+       case MODE_LIST:
+       case MODE_LIST|MODE_INVALIDATE:
+           if (policy_plugin.u.policy->list == NULL) {
+               warningx("policy plugin %s does not support listing privileges",
+                   policy_plugin.name);
+               ok = FALSE;
+           } else {
+               ok = policy_plugin.u.policy->list(nargc, nargv,
+                   ISSET(sudo_mode, MODE_LONG_LIST), list_user);
+           }
+           exit(ok != TRUE);
+       case MODE_RUN:
+           ok = policy_plugin.u.policy->check_policy(nargc, nargv, env_add,
+               &command_info, &argv_out, &user_env_out);
+           warningx("policy plugin returns %d", ok); /* XXX */
+           if (ok != TRUE)
+               exit(ok); /* plugin printed error message */
+           command_info_to_details(command_info, &command_details);
+           /* Restore coredumpsize resource limit before running. */
+#if defined(RLIMIT_CORE) && !defined(SUDO_DEVEL)
+           (void) setrlimit(RLIMIT_CORE, &corelimit);
+#endif /* RLIMIT_CORE && !SUDO_DEVEL */
+           /* run_command will call close for us */
+           run_command(&command_details, argv_out, user_env_out);
+           break;
+       case MODE_EDIT:
+           /* XXX - fill in */
+           break;
+       default:
+           errorx(1, "unexpected sudo mode 0x%x", sudo_mode);
+    }
+    exit(0);
+}
+
+/*
+ * Ensure that stdin, stdout and stderr are open; set to /dev/null if not.
+ * Some operating systems do this automatically in the kernel or libc.
+ */
+static void
+fix_fds(void)
+{
+    int miss[3], devnull = -1;
+
+    /*
+     * stdin, stdout and stderr must be open; set them to /dev/null
+     * if they are closed.
+     */
+    miss[STDIN_FILENO] = fcntl(STDIN_FILENO, F_GETFL, 0) == -1;
+    miss[STDOUT_FILENO] = fcntl(STDOUT_FILENO, F_GETFL, 0) == -1;
+    miss[STDERR_FILENO] = fcntl(STDERR_FILENO, F_GETFL, 0) == -1;
+    if (miss[STDIN_FILENO] || miss[STDOUT_FILENO] || miss[STDERR_FILENO]) {
+       if ((devnull = open(_PATH_DEVNULL, O_RDWR, 0644)) != -1) {
+           if (miss[STDIN_FILENO])
+               (void) dup2(devnull, STDIN_FILENO);
+           if (miss[STDOUT_FILENO])
+               (void) dup2(devnull, STDOUT_FILENO);
+           if (miss[STDERR_FILENO])
+               (void) dup2(devnull, STDERR_FILENO);
+           if (devnull > STDERR_FILENO)
+               close(devnull);
+       }
+    }
+}
+
+static char *
+get_user_groups(struct user_details *ud)
+{
+    char *gid_list = NULL;
+#ifdef HAVE_GETGROUPS
+    size_t glsize;
+    char *cp;
+    int i;
+
+    if ((ud->ngroups = getgroups(0, NULL)) <= 0)
+       return NULL;
+
+    ud->groups = emalloc2(ud->ngroups, sizeof(GETGROUPS_T));
+    if (getgroups(ud->ngroups, ud->groups) < 0)
+       error(1, "can't get group vector");
+    glsize = sizeof("groups=") - 1 + (ud->ngroups * (MAX_UID_T_LEN + 1));
+    gid_list = emalloc(glsize);
+    memcpy(gid_list, "groups=", sizeof("groups=") - 1);
+    cp = gid_list + sizeof("groups=") - 1;
+    for (i = 0; i < ud->ngroups; i++) {
+       snprintf(cp, glsize - (cp - gid_list), "%lu%s",
+           (unsigned long)ud->groups[i], i ? "," : "");
+    }
+#endif
+    return gid_list;
+}
+
+/*
+ * Return user information as an array of name=value pairs.
+ * and fill in struct user_details (which shares the same strings).
+ */
+static char **
+get_user_info(struct user_details *ud)
+{
+    char cwd[PATH_MAX];
+    char host[MAXHOSTNAMELEN];
+    char **user_info, *cp;
+    struct passwd *pw;
+    int i = 0;
+
+    /* XXX - bound check number of entries */
+    user_info = emalloc2(32, sizeof(char *));
+
+    ud->uid = getuid();
+    ud->euid = geteuid();
+    ud->gid = getgid();
+    ud->egid = getegid();
+
+    pw = getpwuid(ud->uid);
+    if (pw == NULL)
+       errorx(1, "unknown uid %lu: who are you?", (unsigned long)ud->uid);
+
+    user_info[i] = fmt_string("user", pw->pw_name);
+    ud->username = user_info[i] + sizeof("user=") - 1;
+
+    easprintf(&user_info[++i], "uid=%lu", (unsigned long)ud->uid);
+    easprintf(&user_info[++i], "euid=%lu", (unsigned long)ud->euid);
+    easprintf(&user_info[++i], "gid=%lu", (unsigned long)ud->gid);
+    easprintf(&user_info[++i], "egid=%lu", (unsigned long)ud->egid);
+
+    if ((cp = get_user_groups(ud)) != NULL)
+       user_info[++i] = cp;
+
+    if (getcwd(cwd, sizeof(cwd)) != NULL) {
+       user_info[++i] = fmt_string("cwd", cwd);
+       ud->cwd = user_info[i] + sizeof("cwd=") - 1;
+    }
+
+    if ((cp = ttyname(STDIN_FILENO)) || (cp = ttyname(STDOUT_FILENO)) ||
+       (cp = ttyname(STDERR_FILENO))) {
+       user_info[++i] = fmt_string("tty", cp);
+       ud->tty = user_info[i] + sizeof("tty=") - 1;
+    }
+
+    if (gethostname(host, sizeof(host)) == 0)
+       host[sizeof(host) - 1] = '\0';
+    else
+       strlcpy(host, "localhost", sizeof(host));
+    user_info[++i] = fmt_string("host", host);
+    ud->host = user_info[i] + sizeof("host=") - 1;
+
+    user_info[++i] = NULL;
+
+    return user_info;
+}
+
+/*
+ * Convert a command_info array into a command_details structure.
+ */
+static void
+command_info_to_details(char * const info[], struct command_details *details)
+{
+    int i;
+    long lval;
+    unsigned long ulval;
+    char *cp, *ep;
+
+    memset(details, 0, sizeof(*details));
+
+    for (i = 0; info[i] != NULL; i++) {
+       /* XXX - should ignore empty entries */
+       switch (info[i][0]) {
+           case 'c':
+               if (strncmp("chroot=", info[i], sizeof("chroot=") - 1) == 0) {
+                   details->chroot = info[i] + sizeof("chroot=") - 1;
+                   break;
+               }
+               if (strncmp("command=", info[i], sizeof("command=") - 1) == 0) {
+                   details->command = info[i] + sizeof("command=") - 1;
+                   break;
+               }
+               if (strncmp("cwd=", info[i], sizeof("cwd=") - 1) == 0) {
+                   details->cwd = info[i] + sizeof("cwd=") - 1;
+                   break;
+               }
+               break;
+           case 'l':
+               if (strncmp("login_class=", info[i], sizeof("login_class=") - 1) == 0) {
+                   details->login_class = info[i] + sizeof("login_class=") - 1;
+                   break;
+               }
+               break;
+           case 'n':
+               /* XXX - bounds check  -NZERO to NZERO (inclusive). */
+               if (strncmp("nice=", info[i], sizeof("nice=") - 1) == 0) {
+                   cp = info[i] + sizeof("nice=") - 1;
+                   errno = 0;
+                   lval = strtol(cp, &ep, 0);
+                   if (*cp != '\0' && *ep == '\0' &&
+                       !(errno == ERANGE &&
+                       (lval == LONG_MAX || lval == LONG_MIN)) &&
+                       lval < INT_MAX && lval > INT_MIN) {
+                       details->priority = (int)lval;
+                       SET(details->flags, CD_SET_PRIORITY);
+                   }
+                   break;
+               }
+               if (strncmp("noexec=", info[i], sizeof("noexec=") - 1) == 0) {
+                   if (atobool(info[i] + sizeof("noexec=") - 1))
+                       SET(details->flags, CD_NOEXEC);
+                   break;
+               }
+               break;
+           case 'p':
+               if (strncmp("preserve_groups=", info[i], sizeof("preserve_groups=") - 1) == 0) {
+                   if (atobool(info[i] + sizeof("preserve_groups=") - 1))
+                       SET(details->flags, CD_PRESERVE_GROUPS);
+                   break;
+               }
+               break;
+           case 'r':
+               if (strncmp("runas_egid=", info[i], sizeof("runas_egid=") - 1) == 0) {
+                   cp = info[i] + sizeof("runas_egid=") - 1;
+                   errno = 0;
+                   ulval = strtoul(cp, &ep, 0);
+                   if (*cp != '\0' && *ep == '\0' &&
+                       (errno != ERANGE || ulval != ULONG_MAX)) {
+                       details->egid = (gid_t)ulval;
+                       SET(details->flags, CD_SET_EGID);
+                   }
+                   break;
+               }
+               if (strncmp("runas_euid=", info[i], sizeof("runas_euid=") - 1) == 0) {
+                   cp = info[i] + sizeof("runas_euid=") - 1;
+                   errno = 0;
+                   ulval = strtoul(cp, &ep, 0);
+                   if (*cp != '\0' && *ep == '\0' &&
+                       (errno != ERANGE || ulval != ULONG_MAX)) {
+                       details->euid = (uid_t)ulval;
+                       SET(details->flags, CD_SET_EUID);
+                   }
+                   break;
+               }
+               if (strncmp("runas_gid=", info[i], sizeof("runas_gid=") - 1) == 0) {
+                   cp = info[i] + sizeof("runas_gid=") - 1;
+                   errno = 0;
+                   ulval = strtoul(cp, &ep, 0);
+                   if (*cp != '\0' && *ep == '\0' &&
+                       (errno != ERANGE || ulval != ULONG_MAX)) {
+                       details->gid = (gid_t)ulval;
+                       SET(details->flags, CD_SET_GID);
+                   }
+                   break;
+               }
+               if (strncmp("runas_groups=", info[i], sizeof("runas_groups=") - 1) == 0) {
+                   int j;
+
+                   /* count groups, alloc and fill in */
+                   cp = info[i] + sizeof("runas_groups=") - 1;
+                   for (;;) {
+                       details->ngroups++;
+                       if ((cp = strchr(cp, ',')) == NULL)
+                           break;
+                       cp++;
+                   }
+                   details->groups = emalloc2(details->ngroups, sizeof(GETGROUPS_T));
+                   cp = info[i] + sizeof("runas_groups=") - 1;
+                   for (j = 0; j < details->ngroups;) {
+                       errno = 0;
+                       ulval = strtoul(cp, &ep, 0);
+                       if (*cp != '\0' && (*ep == ',' || *ep == '\0') &&
+                           (errno != ERANGE || ulval != ULONG_MAX)) {
+                           details->groups[j++] = (gid_t)ulval;
+                       }
+                   }
+                   details->ngroups = j;
+                   break;
+               }
+               if (strncmp("runas_uid=", info[i], sizeof("runas_uid=") - 1) == 0) {
+                   cp = info[i] + sizeof("runas_uid=") - 1;
+                   errno = 0;
+                   ulval = strtoul(cp, &ep, 0);
+                   if (*cp != '\0' && *ep == '\0' &&
+                       (errno != ERANGE || ulval != ULONG_MAX)) {
+                       details->uid = (uid_t)ulval;
+                       SET(details->flags, CD_SET_UID);
+                   }
+                   break;
+               }
+               break;
+           case 's':
+               if (strncmp("selinux_role=", info[i], sizeof("selinux_role=") - 1) == 0) {
+                   details->selinux_role = info[i] + sizeof("selinux_role=") - 1;
+                   break;
+               }
+               if (strncmp("selinux_type=", info[i], sizeof("selinux_type=") - 1) == 0) {
+                   details->selinux_type = info[i] + sizeof("selinux_type=") - 1;
+                   break;
+               }
+               break;
+           case 't':
+               if (strncmp("timeout=", info[i], sizeof("timeout=") - 1) == 0) {
+                   cp = info[i] + sizeof("timeout=") - 1;
+                   errno = 0;
+                   lval = strtol(cp, &ep, 0);
+                   if (*cp != '\0' && *ep == '\0' &&
+                       !(errno == ERANGE &&
+                       (lval == LONG_MAX || lval == LONG_MIN)) &&
+                       lval <= INT_MAX && lval >= 0) {
+                       details->timeout = (int)lval;
+                       SET(details->flags, CD_SET_TIMEOUT);
+                   }
+                   break;
+               }
+               break;
+           case 'u':
+               if (strncmp("umask=", info[i], sizeof("umask=") - 1) == 0) {
+                   cp = info[i] + sizeof("umask=") - 1;
+                   errno = 0;
+                   ulval = strtoul(cp, &ep, 8);
+                   if (*cp != '\0' && *ep == '\0' &&
+                       (errno != ERANGE || ulval != ULONG_MAX)) {
+                       details->umask = (uid_t)ulval;
+                       SET(details->flags, CD_SET_UMASK);
+                   }
+               }
+               break;
+       }
+    }
+
+    if (!ISSET(details->flags, CD_SET_EUID))
+       details->euid = details->uid;
+}
+
+/*
+ * Disable core dumps to avoid dropping a core with user password in it.
+ * We will reset this limit before executing the command.
+ * Not all operating systems disable core dumps for setuid processes.
+ */
+static void
+disable_coredumps(void)
+{
+#if defined(__linux__) || (defined(RLIMIT_CORE) && !defined(SUDO_DEVEL))
+    struct rlimit rl;
+#endif
+
+#if defined(__linux__)
+    /*
+     * Unlimit the number of processes since Linux's setuid() will
+     * apply resource limits when changing uid and return EAGAIN if
+     * nproc would be violated by the uid switch.
+     */
+    rl.rlim_cur = rl.rlim_max = RLIM_INFINITY;
+    if (setrlimit(RLIMIT_NPROC, &rl)) {
+       if (getrlimit(RLIMIT_NPROC, &rl) == 0) {
+           rl.rlim_cur = rl.rlim_max;
+           (void)setrlimit(RLIMIT_NPROC, &rl);
+       }
+    }
+#endif /* __linux__ */
+#if defined(RLIMIT_CORE) && !defined(SUDO_DEVEL)
+    /*
+     * Turn off core dumps.
+     */
+    (void) getrlimit(RLIMIT_CORE, &corelimit);
+    memcpy(&rl, &corelimit, sizeof(struct rlimit));
+    rl.rlim_cur = 0;
+    (void) setrlimit(RLIMIT_CORE, &rl);
+#endif /* RLIMIT_CORE && !SUDO_DEVEL */
+}
+
+/*
+ * Cleanup hook for error()/errorx()
+ */
+void
+cleanup(gotsignal)
+    int gotsignal;
+{
+#if 0 /* XXX */
+    struct sudo_nss *nss;
+
+    if (!gotsignal) {
+       if (snl != NULL) {
+           tq_foreach_fwd(snl, nss)
+               nss->close(nss);
+       }
+       sudo_endpwent();
+       sudo_endgrent();
+    }
+#ifdef _PATH_SUDO_TRANSCRIPT
+    if (def_transcript)
+       term_restore(STDIN_FILENO, 0);
+#endif
+#endif
+}
+
+/*
+ * Setup the execution environment immediately prior to the call to execve()
+ */
+int
+exec_setup(struct command_details *details)
+{
+    struct passwd *pw;
+
+    pw = getpwuid(details->euid);
+    if (pw != NULL) {
+#ifdef HAVE_GETUSERATTR
+       aix_setlimits(pw->pw_name);
+#endif
+#ifdef HAVE_LOGIN_CAP_H
+       if (details->login_class) {
+           int flags;
+           login_cap_t *lc;
+
+           /*
+            * We only use setusercontext() to set the nice value and rlimits.
+            */
+           lc = login_getclass((char *)details->login_class);
+           if (!lc) {
+               warningx("unknown login class %s", details->login_class);
+               errno = ENOENT;
+               goto done;
+           }
+           flags = LOGIN_SETRESOURCES|LOGIN_SETPRIORITY;
+           if (setusercontext(lc, pw, pw->pw_uid, flags)) {
+               if (pw->pw_uid != ROOT_UID) {
+                   warning("unable to set user context");
+                   goto done;
+               } else
+                   warning("unable to set user context");
+           }
+       }
+#endif /* HAVE_LOGIN_CAP_H */
+    }
+
+    /*
+     * Set groups, including supplementary group vector.
+     */
+#ifdef HAVE_SETEUID
+    if (ISSET(details->flags, CD_SET_EGID) && setegid(details->egid)) {
+       warning("unable to set egid to runas gid");
+       goto done;
+    }
+#endif
+    if (ISSET(details->flags, CD_SET_GID) && setgid(details->gid)) {
+       warning("unable to set gid to runas gid");
+       goto done;
+    }
+
+    if (!ISSET(details->flags, CD_PRESERVE_GROUPS)) {
+       /* XXX - may need to initgroups anyway--plugin may not have list */
+#ifdef HAVE_GETGROUPS
+       if (details->ngroups >= 0) {
+           if (setgroups(details->ngroups, details->groups) < 0) {
+               warning("unable to set supplementary group IDs");
+               goto done;
+           }
+       }
+#else
+       if (pw && initgroups(pw->pw_name, pw->pw_gid) < 0) {
+           warning("unable to set supplementary group IDs");
+           goto done;
+       }
+#endif
+    }
+
+    if (ISSET(details->flags, CD_SET_PRIORITY)) {
+       if (setpriority(PRIO_PROCESS, 0, details->priority) != 0) {
+           warning("unable to set process priority");
+           goto done;
+       }
+    }
+    if (ISSET(details->flags, CD_SET_UMASK))
+       (void) umask(details->umask);
+    if (ISSET(details->flags, CD_SET_TIMEOUT))
+       alarm(details->timeout);
+    if (details->chroot) {
+       if (chroot(details->chroot) != 0 || chdir("/") != 0) {
+           warning("unable to change root to %s", details->chroot);
+           goto done;
+       }
+    }
+    if (details->cwd) {
+       /* cwd is relative to the new root, if any */
+       if (chdir(details->cwd) != 0) {
+           warning("unable to change directory to %s", details->cwd);
+           goto done;
+       }
+    }
+
+    /* Must set uids last */
+#ifdef HAVE_SETRESUID
+    if (setresuid(details->uid, details->euid, details->euid) != 0) {
+       warning("unable to change to runas uid");
+       goto done;
+    }
+#elif HAVE_SETREUID
+    if (setreuid(details->uid, details->euid) != 0) {
+       warning("unable to change to runas uid");
+       goto done;
+    }
+#else
+    if (seteuid(details->euid) != 0 || setuid(details->euid) != 0) {
+       warning("unable to change to runas uid");
+       goto done;
+    }
+#endif /* !HAVE_SETRESUID && !HAVE_SETREUID */
+
+    errno = 0;
+
+done:
+    return errno;
+}
+
+static sig_atomic_t sigchld;
+
+static void
+sigchild(int s)
+{
+    sigchld = 1;
+}
+
+/*
+ * Run the command and wait for it to complete.
+ */
+static int
+run_command(struct command_details *details, char *argv[], char *envp[])
+{
+    struct plugin_container *plugin;
+    struct command_status cstat;
+    int exitcode = 1;
+
+    cstat.type = CMD_INVALID;
+    cstat.val = 0;
+
+    /*
+     * XXX - missing closefrom(), may not be possible in new scheme
+     *       also no background support
+     *       or selinux...
+     */
+
+    /* If there are I/O plugins, allocate a pty and exec */
+    if (!tq_empty(&io_plugins)) {
+       warningx("script mode"); /* XXX */
+       script_setup(details->euid);
+       script_execve(details, argv, envp, &cstat);
+    } else {
+       pid_t child, pid;
+       int nready, sv[2];
+       ssize_t nread;
+       sigaction_t sa;
+       fd_set *fdsr;
+
+       zero_bytes(&sa, sizeof(sa));
+       sigemptyset(&sa.sa_mask);
+       sa.sa_flags = SA_RESTART;
+
+       /* Want select() to be interrupted when child dies. */
+       sa.sa_handler = sigchild;
+       sigaction(SIGCHLD, &sa, NULL);
+     
+       /* Ignore SIGPIPE from other end of socketpair. */
+       sa.sa_handler = SIG_IGN;
+       sigaction(SIGPIPE, &sa, NULL);
+
+       if (socketpair(PF_UNIX, SOCK_DGRAM, 0, sv) != 0)
+               error(1, "cannot create sockets");
+
+       child = fork();
+       if (child == -1)
+           error(1, "unable to fork");
+
+       if (child == 0) {
+           /* child */
+           close(sv[0]);
+           if (exec_setup(details) == 0) {
+               /* XXX - fallback via /bin/sh */
+               execve(details->command, argv, envp);
+           }
+           cstat.type = CMD_ERRNO;
+           cstat.val = errno;
+           write(sv[1], &cstat, sizeof(cstat));
+           _exit(1);
+       }
+       close(sv[1]);
+       warningx("waiting for child"); /* XXX */
+
+       /* wait for child to complete or for data on sv[0] */
+       fdsr = (fd_set *)emalloc2(howmany(sv[0] + 1, NFDBITS), sizeof(fd_mask));
+       zero_bytes(fdsr, howmany(sv[0] + 1, NFDBITS) * sizeof(fd_mask));
+       FD_SET(sv[0], fdsr);
+       for (;;) {
+           if (sigchld) {
+               sigchld = 0;
+               do {
+                   pid = waitpid(child, &cstat.val, WNOHANG);
+                   if (pid == child)
+                       cstat.type = CMD_WSTATUS;
+               } while (pid == -1 && errno == EINTR);
+               if (cstat.type == CMD_WSTATUS) {
+                   /* command terminated, we're done */
+                   break;
+               }
+           }
+           nready = select(sv[0] + 1, fdsr, NULL, NULL, NULL);
+           if (nready == -1) {
+               if (errno == EINTR)
+                   continue;
+               error(1, "select failed");
+           }
+           if (FD_ISSET(sv[0], fdsr)) {
+               /* read child status */
+               nread = recv(sv[0], &cstat, sizeof(cstat), 0);
+               if (nread == -1) {
+                   if (errno == EINTR)
+                       continue;
+               } else if (nread != sizeof(cstat)) {
+                   warningx("error reading command status");
+               }
+               break; /* XXX */
+           }
+       }
+    }
+
+    switch (cstat.type) {
+    case CMD_ERRNO:
+       /* exec_setup() or execve() returned an error. */
+       policy_plugin.u.policy->close(0, cstat.val);
+       tq_foreach_fwd(&io_plugins, plugin) {
+           plugin->u.io->close(0, cstat.val);
+       }
+       exitcode = 1;
+       break;
+    case CMD_WSTATUS:
+       /* Command ran, exited or was killed. */
+       policy_plugin.u.policy->close(cstat.val, 0);
+       tq_foreach_fwd(&io_plugins, plugin) {
+           plugin->u.io->close(0, cstat.val);
+       }
+       if (WIFEXITED(cstat.val))
+           exitcode = WEXITSTATUS(cstat.val);
+       else if (WIFSIGNALED(cstat.val))
+           exitcode = WTERMSIG(cstat.val) | 128;
+       break;
+    default:
+       warningx("unexpected child termination condition: %d", cstat.type);
+       break;
+    }
+    exit(exitcode);
+}
+
+#if 0 /* XXX - convert warning/error to something log this */
+/*
+ * Simple debugging/logging.
+ * XXX - use askpass if configured?
+ */
+void
+sudo_log(int level, const char *fmt, ...)
+{
+    va_list ap;
+
+    switch (level) {
+    case SUDO_LOG_INFO:
+       va_start(ap, fmt);
+       vfprintf(stdout, fmt, ap);
+       va_end(ap);
+       break;
+    case SUDO_LOG_DEBUG1:
+    case SUDO_LOG_DEBUG2:
+    case SUDO_LOG_DEBUG3:
+    case SUDO_LOG_DEBUG4:
+    case SUDO_LOG_DEBUG5:
+    case SUDO_LOG_DEBUG6:
+    case SUDO_LOG_DEBUG7:
+    case SUDO_LOG_DEBUG8:
+    case SUDO_LOG_DEBUG9:
+       if (level > debug_level)
+           return;
+       /* FALLTHROUGH */
+    case SUDO_LOG_WARN:
+    case SUDO_LOG_ERROR:
+       va_start(ap, fmt);
+       vfprintf(stderr, fmt, ap);
+       va_end(ap);
+    }
+}
+#endif
diff --git a/src/sudo.h b/src/sudo.h
new file mode 100644 (file)
index 0000000..cce93f0
--- /dev/null
@@ -0,0 +1,234 @@
+/*
+ * Copyright (c) 1993-1996, 1998-2005, 2007-2009
+ *     Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * 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.
+ *
+ * Sponsored in part by the Defense Advanced Research Projects
+ * Agency (DARPA) and Air Force Research Laboratory, Air Force
+ * Materiel Command, USAF, under agreement number F39502-99-1-0512.
+ *
+ * $Sudo: sudo.h,v 1.290 2009/12/12 16:12:26 millert Exp $
+ */
+
+#ifndef _SUDO_SUDO_H
+#define _SUDO_SUDO_H
+
+#include <pathnames.h>
+#include <limits.h>
+#include "compat.h"
+#include "alloc.h"
+#include "error.h"
+#include "list.h"
+#include "missing.h"
+
+#ifdef __TANDEM
+# define ROOT_UID       65535
+#else
+# define ROOT_UID       0
+#endif
+
+/*
+ * Pseudo-boolean values
+ */
+#undef TRUE
+#define TRUE                     1
+#undef FALSE
+#define FALSE                    0
+
+/*
+ * Various modes sudo can be in (based on arguments) in hex
+ */
+#define MODE_RUN               0x00000001
+#define MODE_EDIT              0x00000002
+#define MODE_VALIDATE          0x00000004
+#define MODE_INVALIDATE                0x00000008
+#define MODE_KILL              0x00000010
+#define MODE_VERSION           0x00000020
+#define MODE_HELP              0x00000040
+#define MODE_LIST              0x00000080
+#define MODE_CHECK             0x00000100
+#define MODE_MASK              0x0000ffff
+
+/* Mode flags */
+/* XXX - prune this */
+#define MODE_BACKGROUND                0x00010000
+#define MODE_SHELL             0x00020000
+#define MODE_LOGIN_SHELL       0x00040000
+#define MODE_IMPLIED_SHELL     0x00080000
+#define MODE_RESET_HOME                0x00100000
+#define MODE_PRESERVE_GROUPS   0x00200000
+#define MODE_PRESERVE_ENV      0x00400000
+#define MODE_NONINTERACTIVE    0x00800000
+#define MODE_LONG_LIST         0x01000000
+
+/*
+ * Used with set_perms()
+ */
+#define PERM_ROOT                0x00
+#define PERM_USER                0x01
+#define PERM_FULL_USER           0x02
+#define PERM_SUDOERS             0x03
+#define PERM_RUNAS               0x04
+#define PERM_FULL_RUNAS          0x05
+#define PERM_TIMESTAMP           0x06
+#define PERM_NOEXIT              0x10 /* flag */
+#define PERM_MASK                0xf0
+
+/*
+ * We used to use the system definition of PASS_MAX or _PASSWD_LEN,
+ * but that caused problems with various alternate authentication
+ * methods.  So, we just define our own and assume that it is >= the
+ * system max.
+ */
+#define SUDO_PASS_MAX  256
+
+/*
+ * Flags for lock_file()
+ */
+#define SUDO_LOCK      1               /* lock a file */
+#define SUDO_TLOCK     2               /* test & lock a file (non-blocking) */
+#define SUDO_UNLOCK    4               /* unlock a file */
+
+/*
+ * Flags for tgetpass()
+ */
+#define TGP_ECHO       0x01            /* leave echo on when reading passwd */
+#define TGP_STDIN      0x02            /* read from stdin, not /dev/tty */
+#define TGP_ASKPASS    0x04            /* read from askpass helper program */
+#define TGP_FEEDBACK   0x08            /* visual feedback during input */
+
+struct user_details {
+    uid_t uid;
+    uid_t euid;
+    uid_t gid;
+    uid_t egid;
+    const char *username;
+    const char *cwd;
+    const char *tty;
+    const char *host;
+    GETGROUPS_T *groups;
+    int ngroups;
+};
+
+#define CD_SET_UID             0x0001
+#define CD_SET_EUID            0x0002
+#define CD_SET_GID             0x0004
+#define CD_SET_EGID            0x0008
+#define CD_PRESERVE_GROUPS     0x0010
+#define CD_NOEXEC              0x0020
+#define CD_SET_PRIORITY                0x0040
+#define CD_SET_UMASK           0x0080
+#define CD_SET_TIMEOUT         0x0100
+
+struct command_details {
+    uid_t uid;
+    uid_t euid;
+    gid_t gid;
+    gid_t egid;
+    mode_t umask;
+    int flags;
+    int priority;
+    int timeout;
+    int ngroups;
+    GETGROUPS_T *groups;
+    const char *command;
+    const char *cwd;
+    const char *login_class;
+    const char *chroot;
+    const char *selinux_role;
+    const char *selinux_type;
+};
+
+/* Status passed between parent and child via socketpair */
+struct command_status {
+#define CMD_INVALID 0
+#define CMD_ERRNO 1
+#define CMD_WSTATUS 2
+#define CMD_SIGNO 3
+    int type;
+    int val;
+};
+
+/* For error() and errorx() (XXX - needed?) */
+void cleanup(int);
+
+/* tgetpass.c */
+char *tgetpass(const char *, int, int);
+int tty_present(void);
+
+/* zero_bytes.c */
+void zero_bytes(volatile void *, size_t);
+
+/* fileops.c */
+int lock_file(int, int);
+char *sudo_parseln(FILE *);
+
+/* script.c */
+int script_duplow(int);
+int script_execve(struct command_details *details, char *argv[], char *envp[],
+    struct command_status *cstat);
+void script_setup(uid_t);
+
+/* term.c */
+int term_cbreak(int);
+int term_copy(int, int, int);
+int term_noecho(int);
+int term_raw(int, int, int);
+int term_restore(int, int);
+
+/* fmt_string.h */
+char *fmt_string(const char *var, const char *value);
+
+/* atobool.c */
+int atobool(const char *str);
+
+/* parse_args.c */
+int parse_args(int argc, char **argv, int *nargc, char ***nargv,
+    char ***settingsp, char ***env_addp);
+
+/* pty.c */
+int get_pty(int *master, int *slave, char *name, size_t namesz, uid_t uid);
+
+/* sudo.c */
+int exec_setup(struct command_details *details);
+extern int debug_level;
+extern struct plugin_container_list io_plugins;
+
+#ifndef errno
+extern int errno;
+#endif
+
+#ifdef ntoyet
+/*
+ * Sudo logging/debugging, printf-style.
+ * XXX - not hooked up yet
+ * The debug level may be set on the command line via the -D flag.
+ * A higher debug level yields more verbose debugging.
+ */
+#define SUDO_LOG_DEBUG1  1
+#define SUDO_LOG_DEBUG2  2
+#define SUDO_LOG_DEBUG3  3
+#define SUDO_LOG_DEBUG4  4
+#define SUDO_LOG_DEBUG5  5
+#define SUDO_LOG_DEBUG6  6
+#define SUDO_LOG_DEBUG7  7
+#define SUDO_LOG_DEBUG8  8
+#define SUDO_LOG_DEBUG9  9
+#define SUDO_LOG_INFO   10
+#define SUDO_LOG_WARN   11
+#define SUDO_LOG_ERROR  12
+void sudo_log(int level, const char *format, ...) __printflike(2, 3);
+#endif
+
+#endif /* _SUDO_SUDO_H */
diff --git a/src/sudo_plugin_int.h b/src/sudo_plugin_int.h
new file mode 100644 (file)
index 0000000..3d1b802
--- /dev/null
@@ -0,0 +1,39 @@
+#ifndef _SUDO_PLUGIN_INT_H
+#define _SUDO_PLUGIN_INT_H
+
+/*
+ * Sudo plugin internals.
+ */
+
+struct plugin_info {
+    struct plugin_info *prev; /* required */
+    struct plugin_info *next; /* required */
+    const char *path;
+    const char *symbol_name;
+};
+TQ_DECLARE(plugin_info)
+
+struct plugin_container {
+    struct plugin_container *prev; /* required */
+    struct plugin_container *next; /* required */
+    const char *name;
+    void *handle;
+    union {
+       struct generic_plugin *generic;
+       struct policy_plugin *policy;
+       struct io_plugin *io;
+    } u;
+};
+TQ_DECLARE(plugin_container)
+
+extern struct plugin_container_list policy_plugins;
+extern struct plugin_container_list io_plugins;
+
+int sudo_conversation(int num_msgs, const struct sudo_conv_message msgs[],
+    struct sudo_conv_reply replies[]);
+
+void sudo_load_plugins(const char *conf_file,
+    struct plugin_container *policy_plugin,
+    struct plugin_container_list *io_plugins);
+
+#endif /* _SUDO_PLUGIN_INT_H */
similarity index 100%
rename from sudo_usage.h.in
rename to src/sudo_usage.h.in
index 3c51f28626c67454ee6aa14372bd0d4ebddc8202..0b264cd01d8d741315d02575379f49405be2cecc 100644 (file)
@@ -56,6 +56,9 @@
 
 #include "sudo.h"
 
+/* XXX */
+char *user_askpass = NULL;
+
 static volatile sig_atomic_t signo;
 
 static void handler __P((int));
@@ -111,7 +114,7 @@ restart:
     (void) sigaction(SIGTTIN, &sa, &savettin);
     (void) sigaction(SIGTTOU, &sa, &savettou);
 
-    if (def_pwfeedback)
+    if (ISSET(flags, TGP_FEEDBACK))
        neednl = term_cbreak(input);
     else
        neednl = term_noecho(input);
@@ -123,7 +126,7 @@ restart:
 
        if (timeout > 0)
            alarm(timeout);
-       pass = getln(input, buf, sizeof(buf), def_pwfeedback);
+       pass = getln(input, buf, sizeof(buf), ISSET(flags, TGP_FEEDBACK));
        alarm(0);
        save_errno = errno;
 
@@ -184,7 +187,8 @@ sudo_askpass(prompt)
     if (pid == 0) {
        /* child, point stdout to output side of the pipe and exec askpass */
        (void) dup2(pfd[1], STDOUT_FILENO);
-       set_perms(PERM_FULL_USER);
+       //XXX - set real and effective uid to user
+       //set_perms(PERM_FULL_USER);
        closefrom(STDERR_FILENO + 1);
        execl(user_askpass, user_askpass, prompt, (char *)NULL);
        warning("unable to run %s", user_askpass);