From: Todd C. Miller Date: Fri, 4 Jun 2010 13:41:08 +0000 (-0400) Subject: Merge I/O logging changes from trunk. Disabling I/O log support X-Git-Tag: SUDO_1_7_3~119 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=d02fac561868c46934405079899efa285e3ad42b;p=sudo Merge I/O logging changes from trunk. Disabling I/O log support at compile time does not currently work. Sudoedit is not yet hooked up to I/O logging. --HG-- branch : 1.7 --- diff --git a/INSTALL b/INSTALL index 350d4bda0..6cb9e5996 100644 --- a/INSTALL +++ b/INSTALL @@ -598,26 +598,26 @@ The following options are also configurable at runtime: if the executable is simply not in the user's path, sudo will tell the user that they are not allowed to run it, which can be confusing. - --disable-transcript - Disable sudo's transcript support. This can be used to allow sudo + --disable-iologdir + Disable sudo's I/O logging support. This can be used to allow sudo to be compiled on systems without pseudo-tty support. - --enable-transcript[=DIR] - By default, sudo stores transcript files in either - /var/log/sudo-transcript, /var/adm/sudo-transcript, or - /usr/log/sudo-transcript. If DIR is specified, transcripts - will be stored in the indicated directory instead. + --enable-iologdir[=DIR] + By default, sudo stores I/O log files in either /var/log/sudo-io, + /var/adm/sudo-sudo-io or /usr/log/sudo-io. If DIR is + specified, I/O logs will be stored in the indicated directory + instead. + + --enable-zlib[=DIR] + Enable the use of the zlib compress library when storing + I/O log files. If specified, DIR is the base directory + containing the zlib include and lib directories. By default + zlib is used if it is found on the system and I/O logging + support is not disabled. --disable-zlib Disable the use of the zlib compress library when storing - transcript files. - - --enable-zlib[=DIR] - Enable the use of the zlib compress library when storing - transcript files. If specified, DIR is the base directory - containing the zlib include and lib directories. By default - zlib is used if it is found on the system and transcript - support is not disabled. + I/O log files. --enable-warnings Enable compiler warnings when building sudo with gcc. diff --git a/Makefile.in b/Makefile.in index a7a4d111f..21d386700 100644 --- a/Makefile.in +++ b/Makefile.in @@ -1,5 +1,5 @@ # -# Copyright (c) 1996, 1998-2005, 2007-2009 +# Copyright (c) 1996, 1998-2005, 2007-2010 # Todd C. Miller # # Permission to use, copy, modify, and distribute this software for any @@ -33,7 +33,7 @@ top_builddir = . CC = @CC@ FLEX = @FLEX@ YACC = @YACC@ -NROFF = nroff -Tascii +NROFF = @NROFFPROG@ -Tascii LIBTOOL = @LIBTOOL@ AR=@AR@ RANLIB=@RANLIB@ @@ -103,11 +103,11 @@ SHELL = @SHELL@ PROGS = @PROGS@ SRCS = aix.c alias.c alloc.c audit.c boottime.c bsm_audit.c check.c \ - closefrom.c def_data.c defaults.c env.c error.c fileops.c find_path.c \ - fnmatch.c getcwd.c getprogname.c getspwuid.c gettime.c glob.c \ - goodpath.c gram.c gram.y interfaces.c isblank.c lbuf.c ldap.c list.c \ - logging.c match.c mkstemp.c memrchr.c nanosleep.c parse.c pwutil.c \ - pty.c script.c set_perms.c sigaction.c snprintf.c strcasecmp.c \ + closefrom.c def_data.c defaults.c env.c error.c exec.c fileops.c \ + find_path.c fnmatch.c getcwd.c getprogname.c getspwuid.c gettime.c \ + glob.c goodpath.c gram.c gram.y interfaces.c iolog.c isblank.c lbuf.c \ + ldap.c list.c logging.c match.c mkstemp.c memrchr.c nanosleep.c \ + parse.c pwutil.c pty.c set_perms.c sigaction.c snprintf.c strcasecmp.c \ strerror.c strlcat.c strlcpy.c strsignal.c sudo.c sudo_noexec.c \ sudo_edit.c sudo_nss.c term.c testsudoers.c tgetpass.c toke.c toke.l \ tsgetgrpw.c utimes.c vasgroups.c visudo.c zero_bytes.c redblack.c \ @@ -131,8 +131,8 @@ COMMON_OBJS = alias.o alloc.o defaults.o error.o getline.o gram.o \ list.o match.o pwutil.o timestr.o toke.o redblack.o \ term.o zero_bytes.o @NONUNIX_GROUPS_IMPL@ -SUDO_OBJS = $(AUTH_OBJS) @SUDO_OBJS@ audit.o boottime.o check.o \ - env.o getspwuid.o gettime.o goodpath.o fileops.o find_path.o \ +SUDO_OBJS = $(AUTH_OBJS) @SUDO_OBJS@ audit.o boottime.o check.o env.o \ + exec.o getspwuid.o gettime.o goodpath.o fileops.o find_path.o \ interfaces.o lbuf.o logging.o parse.o set_perms.o sudo.o \ sudo_edit.o sudo_nss.o tgetpass.o @@ -157,7 +157,7 @@ DISTFILES = $(SRCS) $(HDRS) ChangeLog HISTORY INSTALL INSTALL.configure \ sudoers.man.in sudoers.pod sudoers.ldap.cat sudoers.ldap.man.in \ sudoers.ldap.pod sudoers2ldif sudoreplay.cat sudoreplay.man.in \ sudoreplay.pod visudo.cat visudo.man.in visudo.pod auth/API \ - sudo.man.pl sudoers.man.pl + sudo.man.pl sudoers.man.pl BINFILES= ChangeLog HISTORY LICENSE README TROUBLESHOOTING \ UPGRADE install-sh mkinstalldirs sample.syslog.conf sample.sudoers \ @@ -263,6 +263,8 @@ env.o: $(srcdir)/env.c $(SUDODEP) $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/env.c error.o: $(srcdir)/error.c $(srcdir)/compat.h $(srcdir)/error.h config.h $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/error.c +exec.o: $(srcdir)/exec.c $(SUDODEP) + $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/exec.c fileops.o: $(srcdir)/fileops.c $(SUDODEP) $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/fileops.c find_path.o: $(srcdir)/find_path.c $(SUDODEP) @@ -289,6 +291,8 @@ gram.o: $(devdir)/gram.c $(SUDODEP) $(srcdir)/parse.h $(srcdir)/list.h $(devdir) $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(devdir)/gram.c interfaces.o: $(srcdir)/interfaces.c $(SUDODEP) $(srcdir)/interfaces.h $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/interfaces.c +iolog.o: $(srcdir)/iolog.c $(SUDODEP) + $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/iolog.c isblank.o: $(srcdir)/isblank.c $(srcdir)/compat.h config.h $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/isblank.c lbuf.o: $(srcdir)/lbuf.c $(SUDODEP) @@ -315,8 +319,6 @@ pty.o: $(srcdir)/pty.c $(SUDODEP) $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/pty.c redblack.o: $(srcdir)/redblack.c $(SUDODEP) $(srcdir)/redblack.h $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/redblack.c -script.o: $(srcdir)/script.c $(SUDODEP) - $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/script.c set_perms.o: $(srcdir)/set_perms.c $(SUDODEP) $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/set_perms.c sigaction.o: $(srcdir)/sigaction.c $(srcdir)/compat.h diff --git a/WHATSNEW b/WHATSNEW index f43049ebf..8c685b500 100644 --- a/WHATSNEW +++ b/WHATSNEW @@ -1,8 +1,9 @@ What's new in Sudo 1.7.3? - * Support for logging a transcript of the command being run. - For more information, see the documentation for the "transcript" - Defaults option in the sudoers manual and the sudoreplay manual. + * Support for logging I/O for the command being run. + For more information, see the documentation for the "log_input" + and "log_output" Defaults options in the sudoers manual. Also + see the sudoreplay manual for how to replay I/O log sessions. * The passwd_timeout and timestamp_timeout options may now be specified as floating point numbers for more granular timeout diff --git a/aclocal.m4 b/aclocal.m4 index 841ede86b..17b317be6 100644 --- a/aclocal.m4 +++ b/aclocal.m4 @@ -158,24 +158,22 @@ fi ])dnl dnl -dnl Where the transcript files go, use /var/log/sudo-transcript if -dnl /var/log exists, else /{var,usr}/adm/sudo-transcript -dnl -AC_DEFUN(SUDO_TRANSCRIPT, [ - if test "${enable_transcript-yes}" != "no"; then - AC_MSG_CHECKING(for transcript dir location) - if test "${enable_transcript-yes}" != "yes"; then - : - elif test -d "/var/log"; then - enable_transcript="/var/log/sudo-transcript" - elif test -d "/var/adm"; then - enable_transcript="/var/adm/sudo-transcript" - else - enable_transcript="/usr/adm/sudo-transcript" - fi - SUDO_DEFINE_UNQUOTED(_PATH_SUDO_TRANSCRIPT, "$enable_transcript") - AC_MSG_RESULT($enable_transcript) +dnl Where the I/O log files go, use /var/log/sudo-io if +dnl /var/log exists, else /{var,usr}/adm/sudo-io +dnl +AC_DEFUN(SUDO_IO_LOGDIR, [ + AC_MSG_CHECKING(for I/O log dir location) + if test "${with_iologdir-yes}" != "yes"; then + : + elif test -d "/var/log"; then + with_iologdir="/var/log/sudo-io" + elif test -d "/var/adm"; then + with_iologdir="/var/adm/sudo-io" + else + with_iologdir="/usr/adm/sudo-io" fi + SUDO_DEFINE_UNQUOTED(_PATH_SUDO_IO_LOGDIR, "$with_iologdir") + AC_MSG_RESULT($with_iologdir) ])dnl dnl diff --git a/configure b/configure index 420fd1b84..1499df90f 100755 --- a/configure +++ b/configure @@ -939,6 +939,7 @@ with_passprompt with_badpass_message with_fqdn with_timedir +with_iologdir with_sendmail with_sudoers_mode with_sudoers_uid @@ -980,7 +981,6 @@ enable_noargs_shell enable_shell_sets_home enable_path_info enable_env_debug -enable_transcript enable_warnings with_selinux enable_gss_krb5_ccache_name @@ -1634,9 +1634,6 @@ Optional Features: Set $HOME to target user in shell mode --disable-path-info Print 'command not allowed' not 'command not found' --enable-env-debug Whether to enable environment debugging. - --enable-transcript[=DIR] - Enable Sudo's transcript support, storing them in - DIR --enable-warnings Whether to enable compiler warnings --enable-gss-krb5-ccache-name Use GSS-API to set the Kerberos V cred cache name @@ -1697,6 +1694,7 @@ Optional Packages: --with-badpass-message message the user sees when the password is wrong --with-fqdn expect fully qualified hosts in sudoers --with-timedir path to the sudo timestamp dir + --with-iologdir=DIR directory to store sudo I/O log files in --with-sendmail set path to sendmail --without-sendmail do not send mail at all --with-sudoers-mode mode of sudoers file (defaults to 0440) @@ -3656,6 +3654,17 @@ fi +# Check whether --with-iologdir was given. +if test "${with_iologdir+set}" = set; then : + withval=$with_iologdir; case $with_iologdir in + yes) ;; + no) as_fn_error "\"--without-iologdir not supported.\"" "$LINENO" 5 + ;; +esac +fi + + + # Check whether --with-sendmail was given. if test "${with_sendmail+set}" = set; then : withval=$with_sendmail; case $with_sendmail in @@ -5344,12 +5353,6 @@ $as_echo "no" >&6; } fi -# Check whether --enable-transcript was given. -if test "${enable_transcript+set}" = set; then : - enableval=$enable_transcript; -fi - - # Check whether --enable-warnings was given. if test "${enable_warnings+set}" = set; then : enableval=$enable_warnings; case "$enableval" in @@ -6521,13 +6524,13 @@ if test "${lt_cv_nm_interface+set}" = set; then : else lt_cv_nm_interface="BSD nm" echo "int some_variable = 0;" > conftest.$ac_ext - (eval echo "\"\$as_me:6524: $ac_compile\"" >&5) + (eval echo "\"\$as_me:6527: $ac_compile\"" >&5) (eval "$ac_compile" 2>conftest.err) cat conftest.err >&5 - (eval echo "\"\$as_me:6527: $NM \\\"conftest.$ac_objext\\\"\"" >&5) + (eval echo "\"\$as_me:6530: $NM \\\"conftest.$ac_objext\\\"\"" >&5) (eval "$NM \"conftest.$ac_objext\"" 2>conftest.err > conftest.out) cat conftest.err >&5 - (eval echo "\"\$as_me:6530: output\"" >&5) + (eval echo "\"\$as_me:6533: output\"" >&5) cat conftest.out >&5 if $GREP 'External.*some_variable' conftest.out > /dev/null; then lt_cv_nm_interface="MS dumpbin" @@ -7732,7 +7735,7 @@ ia64-*-hpux*) ;; *-*-irix6*) # Find out which ABI we are using. - echo '#line 7735 "configure"' > conftest.$ac_ext + echo '#line 7738 "configure"' > conftest.$ac_ext if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 (eval $ac_compile) 2>&5 ac_status=$? @@ -9125,11 +9128,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:9128: $lt_compile\"" >&5) + (eval echo "\"\$as_me:9131: $lt_compile\"" >&5) (eval "$lt_compile" 2>conftest.err) ac_status=$? cat conftest.err >&5 - echo "$as_me:9132: \$? = $ac_status" >&5 + echo "$as_me:9135: \$? = $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. @@ -9464,11 +9467,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:9467: $lt_compile\"" >&5) + (eval echo "\"\$as_me:9470: $lt_compile\"" >&5) (eval "$lt_compile" 2>conftest.err) ac_status=$? cat conftest.err >&5 - echo "$as_me:9471: \$? = $ac_status" >&5 + echo "$as_me:9474: \$? = $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. @@ -9569,11 +9572,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:9572: $lt_compile\"" >&5) + (eval echo "\"\$as_me:9575: $lt_compile\"" >&5) (eval "$lt_compile" 2>out/conftest.err) ac_status=$? cat out/conftest.err >&5 - echo "$as_me:9576: \$? = $ac_status" >&5 + echo "$as_me:9579: \$? = $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 @@ -9624,11 +9627,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:9627: $lt_compile\"" >&5) + (eval echo "\"\$as_me:9630: $lt_compile\"" >&5) (eval "$lt_compile" 2>out/conftest.err) ac_status=$? cat out/conftest.err >&5 - echo "$as_me:9631: \$? = $ac_status" >&5 + echo "$as_me:9634: \$? = $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 @@ -11991,7 +11994,7 @@ else lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 lt_status=$lt_dlunknown cat > conftest.$ac_ext <<_LT_EOF -#line 11994 "configure" +#line 11997 "configure" #include "confdefs.h" #if HAVE_DLFCN_H @@ -12087,7 +12090,7 @@ else lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 lt_status=$lt_dlunknown cat > conftest.$ac_ext <<_LT_EOF -#line 12090 "configure" +#line 12093 "configure" #include "confdefs.h" #if HAVE_DLFCN_H @@ -17918,29 +17921,27 @@ EOF fi - if test "${enable_transcript-yes}" != "no"; then - { $as_echo "$as_me:${as_lineno-$LINENO}: checking for transcript dir location" >&5 -$as_echo_n "checking for transcript dir location... " >&6; } - if test "${enable_transcript-yes}" != "yes"; then - : - elif test -d "/var/log"; then - enable_transcript="/var/log/sudo-transcript" - elif test -d "/var/adm"; then - enable_transcript="/var/adm/sudo-transcript" - else - enable_transcript="/usr/adm/sudo-transcript" - fi - cat >>confdefs.h <&5 +$as_echo_n "checking for I/O log dir location... " >&6; } + if test "${with_iologdir-yes}" != "yes"; then + : + elif test -d "/var/log"; then + with_iologdir="/var/log/sudo-io" + elif test -d "/var/adm"; then + with_iologdir="/var/adm/sudo-io" + else + with_iologdir="/usr/adm/sudo-io" + fi + cat >>confdefs.h <&5 -$as_echo "$enable_transcript" >&6; } - fi + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_iologdir" >&5 +$as_echo "$with_iologdir" >&6; } -if test "${enable_transcript-yes}" != "no"; then - # currently require POSIX job control for transcript support +if test "${with_iologdir-yes}" != "no"; then + # Require POSIX job control for I/O log support for ac_func in tcsetpgrp do : ac_fn_c_check_func "$LINENO" "tcsetpgrp" "ac_cv_func_tcsetpgrp" @@ -17949,7 +17950,7 @@ if test "x$ac_cv_func_tcsetpgrp" = x""yes; then : #define HAVE_TCSETPGRP 1 _ACEOF - SUDO_OBJS="${SUDO_OBJS} pty.o script.o" + SUDO_OBJS="${SUDO_OBJS} exec.o iolog.o pty.o" PROGS="$PROGS sudoreplay" REPLAY="" @@ -18034,9 +18035,9 @@ fi else - { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Disabling transcript support due to lack of tcsetpgrp function" >&5 -$as_echo "$as_me: WARNING: Disabling transcript support due to lack of tcsetpgrp function" >&2;} - enable_transcript=no + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Disabling I/O log support due to lack of tcsetpgrp function" >&5 +$as_echo "$as_me: WARNING: Disabling I/O log support due to lack of tcsetpgrp function" >&2;} + with_iologdir=no fi done diff --git a/configure.in b/configure.in index 9f1e0c447..82151ea72 100644 --- a/configure.in +++ b/configure.in @@ -696,6 +696,13 @@ AC_ARG_WITH(timedir, [AS_HELP_STRING([--with-timedir], [path to the sudo timesta ;; esac]) +AC_ARG_WITH(iologdir, [AS_HELP_STRING([--with-iologdir=DIR], [directory to store sudo I/O log files in])], +[case $with_iologdir in + yes) ;; + no) AC_MSG_ERROR(["--without-iologdir not supported."]) + ;; +esac]) + AC_ARG_WITH(sendmail, [AS_HELP_STRING([--with-sendmail], [set path to sendmail]) AS_HELP_STRING([--without-sendmail], [do not send mail at all])], [case $with_sendmail in @@ -1224,10 +1231,6 @@ AC_ARG_ENABLE(env_debug, esac ], AC_MSG_RESULT(no)) -dnl we check enable_transcript later -AC_ARG_ENABLE(transcript, -[AS_HELP_STRING([--enable-transcript[[=DIR]]], [Enable Sudo's transcript support, storing them in DIR])]) - AC_ARG_ENABLE(warnings, [AS_HELP_STRING([--enable-warnings], [Whether to enable compiler warnings])], [ case "$enableval" in @@ -2626,19 +2629,19 @@ if test -n "$blibpath"; then fi dnl -dnl Check for log file, timestamp and transcript locations +dnl Check for log file, timestamp and iolog locations dnl SUDO_LOGFILE SUDO_TIMEDIR -SUDO_TRANSCRIPT +SUDO_IO_LOGDIR dnl -dnl If transcript is enabled, build sudoreplay and add pty.o script.o for sudo +dnl If I/O logging is enabled, build sudoreplay and add iolog.o pty.o for sudo dnl -if test "${enable_transcript-yes}" != "no"; then - # currently require POSIX job control for transcript support +if test "${with_iologdir-yes}" != "no"; then + # Require POSIX job control for I/O log support AC_CHECK_FUNCS(tcsetpgrp, [ - SUDO_OBJS="${SUDO_OBJS} pty.o script.o" + SUDO_OBJS="${SUDO_OBJS} iolog.o pty.o" PROGS="$PROGS sudoreplay" REPLAY="" @@ -2663,8 +2666,8 @@ if test "${enable_transcript-yes}" != "no"; then ]) fi ], [ - AC_MSG_WARN([Disabling transcript support due to lack of tcsetpgrp function]) - enable_transcript=no + AC_MSG_WARN([Disabling I/O log support due to lack of tcsetpgrp function]) + with_iologdir=no ]) fi diff --git a/def_data.c b/def_data.c index b16d128bb..fe4678986 100644 --- a/def_data.c +++ b/def_data.c @@ -315,12 +315,16 @@ struct sudo_defs_types sudo_defs_table[] = { "The umask specified in sudoers will override the user's, even if it is more permissive", NULL, }, { - "transcript", T_FLAG, - "Log a transcript of the command being run", + "log_input", T_FLAG, + "Log user's input for the command being run", NULL, }, { - "compress_transcript", T_FLAG, - "Compress session transcripts with zlib", + "log_output", T_FLAG, + "Log the output of the command being run", + NULL, + }, { + "compress_io", T_FLAG, + "Compress I/O logs using zlib", NULL, }, { NULL, 0, NULL diff --git a/def_data.h b/def_data.h index 9e9e80d37..96652af2c 100644 --- a/def_data.h +++ b/def_data.h @@ -144,10 +144,12 @@ #define I_FAST_GLOB 71 #define def_umask_override (sudo_defs_table[72].sd_un.flag) #define I_UMASK_OVERRIDE 72 -#define def_transcript (sudo_defs_table[73].sd_un.flag) -#define I_TRANSCRIPT 73 -#define def_compress_transcript (sudo_defs_table[74].sd_un.flag) -#define I_COMPRESS_TRANSCRIPT 74 +#define def_log_input (sudo_defs_table[73].sd_un.flag) +#define I_LOG_INPUT 73 +#define def_log_output (sudo_defs_table[74].sd_un.flag) +#define I_LOG_OUTPUT 74 +#define def_compress_io (sudo_defs_table[75].sd_un.flag) +#define I_COMPRESS_IO 75 enum def_tupple { never, diff --git a/def_data.in b/def_data.in index a5b8e9d41..56418397d 100644 --- a/def_data.in +++ b/def_data.in @@ -232,9 +232,12 @@ fast_glob umask_override T_FLAG "The umask specified in sudoers will override the user's, even if it is more permissive" -transcript +log_input T_FLAG - "Log a transcript of the command being run" -compress_transcript + "Log user's input for the command being run" +log_output T_FLAG - "Compress session transcripts with zlib" + "Log the output of the command being run" +compress_io + T_FLAG + "Compress I/O logs using zlib" diff --git a/defaults.c b/defaults.c index 081cf725f..ce68f05da 100644 --- a/defaults.c +++ b/defaults.c @@ -470,7 +470,7 @@ init_defaults() def_passwd_timeout = PASSWORD_TIMEOUT; def_passwd_tries = TRIES_FOR_PASSWORD; #ifdef HAVE_ZLIB - def_compress_transcript = TRUE; + def_compress_io = TRUE; #endif /* Now do the strings */ diff --git a/exec.c b/exec.c new file mode 100644 index 000000000..349c22488 --- /dev/null +++ b/exec.c @@ -0,0 +1,1227 @@ +/* + * Copyright (c) 2009-2010 Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include +#include +#include +#include +#include +#include +#ifdef HAVE_TERMIOS_H +# include +#else +# include +#endif /* HAVE_TERMIOS_H */ +#include +#ifdef HAVE_SYS_SELECT_H +# include +#endif /* HAVE_SYS_SELECT_H */ +#include +#ifdef STDC_HEADERS +# include +# include +#else +# ifdef HAVE_STDLIB_H +# include +# endif +#endif /* STDC_HEADERS */ +#ifdef HAVE_STRING_H +# if defined(HAVE_MEMORY_H) && !defined(STDC_HEADERS) +# include +# endif +# include +#else +# ifdef HAVE_STRINGS_H +# include +# endif +#endif /* HAVE_STRING_H */ +#ifdef HAVE_UNISTD_H +# include +#endif /* HAVE_UNISTD_H */ +#if TIME_WITH_SYS_TIME +# include +#endif +#include +#include +#include +#include +#include +#ifdef HAVE_SELINUX +# include +#endif + +#if !defined(NSIG) +# if defined(_NSIG) +# define NSIG _NSIG +# elif defined(__NSIG) +# define NSIG __NSIG +# else +# define NSIG 64 +# endif +#endif + +#include "sudo.h" + +#define SFD_STDIN 0 +#define SFD_STDOUT 1 +#define SFD_STDERR 2 +#define SFD_MASTER 3 +#define SFD_SLAVE 4 +#define SFD_USERTTY 5 + +#define TERM_COOKED 0 +#define TERM_RAW 1 + +/* Compatibility with older tty systems. */ +#if !defined(TIOCGSIZE) && defined(TIOCGWINSZ) +# define TIOCGSIZE TIOCGWINSZ +# define TIOCSSIZE TIOCSWINSZ +# define ttysize winsize +# define ts_cols ws_col +#endif + +struct io_buffer { + struct io_buffer *next; + int len; /* buffer length (how much produced) */ + int off; /* write position (how much already consumed) */ + int rfd; /* reader (producer) */ + int wfd; /* writer (consumer) */ + int (*action)(const char *buf, unsigned int len); + char buf[16 * 1024]; +}; + +static int io_fds[6] = { -1, -1, -1, -1, -1, -1}; +static int pipeline = FALSE; + +static sig_atomic_t recvsig[NSIG]; + +static pid_t ppgrp, child; +static int foreground; +static int ttymode = TERM_COOKED; +static int tty_initialized; + +static char slavename[PATH_MAX]; + +static int suspend_parent(int signo, struct io_buffer *iobufs); +static void flush_output(struct io_buffer *iobufs); +static int perform_io(struct io_buffer *iobufs, fd_set *fdsr, fd_set *fdsw); +static void handler(int s); +static int exec_monitor(const char *path, char *argv[], + char *envp[], int, int); +static void exec_pty(const char *path, char *argv[], + char *envp[], int); +static void sigwinch(int s); +static void sync_ttysize(int src, int dst); +static void deliver_signal(pid_t pid, int signo); +static int safe_close(int fd); + +static void +pty_setup() +{ + io_fds[SFD_USERTTY] = open(_PATH_TTY, O_RDWR|O_NOCTTY, 0); + if (io_fds[SFD_USERTTY] != -1) { + if (!get_pty(&io_fds[SFD_MASTER], &io_fds[SFD_SLAVE], + slavename, sizeof(slavename))) + error(1, "Can't get pty"); + } +} + +static void +check_foreground(void) +{ + if (io_fds[SFD_USERTTY] != -1) { + foreground = tcgetpgrp(io_fds[SFD_USERTTY]) == ppgrp; + if (foreground && !tty_initialized) { + if (term_copy(io_fds[SFD_USERTTY], io_fds[SFD_SLAVE])) { + tty_initialized = 1; + sync_ttysize(io_fds[SFD_USERTTY], io_fds[SFD_SLAVE]); + } + } + } +} + +/* + * Suspend sudo if the underlying command is suspended. + * Returns SIGUSR1 if the child should be resume in foreground else SIGUSR2. + */ +static int +suspend_parent(int signo, struct io_buffer *iobufs) +{ + sigaction_t sa, osa; + int n, oldmode = ttymode, rval = 0; + + switch (signo) { + case SIGTTOU: + case SIGTTIN: + /* + * If we are the foreground process, just resume the child. + * Otherwise, re-send the signal with the handler disabled. + */ + if (!foreground) + check_foreground(); + if (foreground) { + if (ttymode != TERM_RAW) { + do { + n = term_raw(io_fds[SFD_USERTTY], 0); + } while (!n && errno == EINTR); + ttymode = TERM_RAW; + } + rval = SIGUSR1; /* resume child in foreground */ + break; + } + ttymode = TERM_RAW; + /* FALLTHROUGH */ + case SIGSTOP: + case SIGTSTP: + /* Flush any remaining output before suspending. */ + flush_output(iobufs); + + /* Restore original tty mode before suspending. */ + if (oldmode != TERM_COOKED) { + do { + n = term_restore(io_fds[SFD_USERTTY], 0); + } while (!n && errno == EINTR); + } + + /* Suspend self and continue child when we resume. */ + sa.sa_handler = SIG_DFL; + sigaction(signo, &sa, &osa); + killpg(ppgrp, signo); + + /* Check foreground/background status on resume. */ + check_foreground(); + + /* + * Only modify term if we are foreground process and either + * the old tty mode was not cooked or child got SIGTT{IN,OU} + */ + if (ttymode != TERM_COOKED) { + if (foreground) { + /* Set raw mode. */ + do { + n = term_raw(io_fds[SFD_USERTTY], 0); + } while (!n && errno == EINTR); + } else { + /* Background process, no access to tty. */ + ttymode = TERM_COOKED; + } + } + + sigaction(signo, &osa, NULL); + rval = ttymode == TERM_RAW ? SIGUSR1 : SIGUSR2; + break; + } + + return(rval); +} + +/* + * Like execve(2) but falls back to running through /bin/sh + * ala execvp(3) if we get ENOEXEC. + */ +static int +my_execve(const char *path, char *argv[], char *envp[]) +{ + execve(path, argv, envp); + if (errno == ENOEXEC) { + argv--; /* at least one extra slot... */ + argv[0] = "sh"; + argv[1] = (char *)path; + execve(_PATH_BSHELL, argv, envp); + } + return -1; +} + +static void +terminate_child(pid_t pid, int use_pgrp) +{ + /* + * Kill child with increasing urgency. + * Note that SIGCHLD will interrupt the sleep() + */ + if (use_pgrp) { + killpg(pid, SIGHUP); + killpg(pid, SIGTERM); + sleep(2); + killpg(pid, SIGKILL); + } else { + kill(pid, SIGHUP); + kill(pid, SIGTERM); + sleep(2); + kill(pid, SIGKILL); + } +} + +static struct io_buffer * +io_buf_new(int rfd, int wfd, int (*action)(const char *, unsigned int), + struct io_buffer *head) +{ + struct io_buffer *iob; + + iob = emalloc(sizeof(*iob)); + zero_bytes(iob, sizeof(*iob)); + iob->rfd = rfd; + iob->wfd = wfd; + iob->action = action; + iob->next = head; + return iob; +} + +/* + * Read/write iobufs depending on fdsr and fdsw. + * Returns the number of errors. + */ +static int +perform_io(struct io_buffer *iobufs, fd_set *fdsr, fd_set *fdsw) +{ + struct io_buffer *iob; + int n, errors = 0; + + for (iob = iobufs; iob; iob = iob->next) { + if (iob->rfd != -1 && FD_ISSET(iob->rfd, fdsr)) { + do { + n = read(iob->rfd, iob->buf + iob->len, + sizeof(iob->buf) - iob->len); + } while (n == -1 && errno == EINTR); + if (n == -1) { + if (errno != EAGAIN) + break; + } else if (n == 0) { + /* got EOF */ + safe_close(iob->rfd); + iob->rfd = -1; + } else { + if (!iob->action(iob->buf + iob->len, n)) + terminate_child(child, TRUE); + iob->len += n; + } + } + if (iob->wfd != -1 && FD_ISSET(iob->wfd, fdsw)) { + do { + n = write(iob->wfd, iob->buf + iob->off, + iob->len - iob->off); + } while (n == -1 && errno == EINTR); + if (n == -1) { + if (errno == EPIPE) { + /* other end of pipe closed */ + if (iob->rfd != -1) { + safe_close(iob->rfd); + iob->rfd = -1; + } + safe_close(iob->wfd); + iob->wfd = -1; + continue; + } + if (errno != EAGAIN) + errors++; + } else { + iob->off += n; + } + } + } + return errors; +} + +/* + * This is a little bit tricky due to how POSIX job control works and + * we fact that we have two different controlling terminals to deal with. + * There are three processes: + * 1) parent, which forks a child and does all the I/O passing. + * Handles job control signals send by its child to bridge the + * two sessions (and ttys). + * 2) child, creates a new session so it can receive notification of + * tty stop signals (SIGTSTP, SIGTTIN, SIGTTOU). Waits for the + * command to stop or die and passes back tty stop signals to parent + * so job control works in the user's shell. + * 3) grandchild, executes the actual command with the pty slave as its + * controlling tty, belongs to child's session but has its own pgrp. + */ +int +sudo_execve(const char *path, char *argv[], char *envp[], + struct command_status *cstat) +{ + sigaction_t sa; + struct io_buffer *iob, *iobufs = NULL; + int n, nready; + int io_pipe[3][2], sv[2]; + fd_set *fdsr, *fdsw; + int rbac_enabled = 0; + int log_io, maxfd, status; + + cstat->type = CMD_INVALID; + + log_io = def_log_output || def_log_input; + if (log_io) { + pty_setup(); + io_log_open(); + } + +#ifdef HAVE_SELINUX + rbac_enabled = is_selinux_enabled() > 0 && user_role != NULL; + if (rbac_enabled) { + /* Must do SELinux setup before changing uid. */ + selinux_setup(user_role, user_type, + log_io ? slavename : user_ttypath, io_fds[SFD_SLAVE]); + } +#endif + + ppgrp = getpgrp(); /* parent's pgrp, so child can signal us */ + + /* + * 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_DGRAM, 0, sv) != 0) + error(1, "cannot create sockets"); + + zero_bytes(&sa, sizeof(sa)); + sigemptyset(&sa.sa_mask); + + /* Note: HP-UX select() will not be interrupted if SA_RESTART set */ + sa.sa_flags = 0; /* do not restart syscalls */ + sa.sa_handler = handler; + sigaction(SIGALRM, &sa, NULL); + sigaction(SIGCHLD, &sa, NULL); + sigaction(SIGHUP, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + sigaction(SIGPIPE, &sa, NULL); + sigaction(SIGQUIT, &sa, NULL); + sigaction(SIGTERM, &sa, NULL); + + if (log_io) { + if (io_fds[SFD_USERTTY] != -1) { + sa.sa_flags = SA_RESTART; + sa.sa_handler = sigwinch; + sigaction(SIGWINCH, &sa, NULL); + } + + /* + * Setup stdin/stdout/stderr for child, to be duped after forking. + */ + io_fds[SFD_STDIN] = io_fds[SFD_SLAVE]; + io_fds[SFD_STDOUT] = io_fds[SFD_SLAVE]; + io_fds[SFD_STDERR] = io_fds[SFD_SLAVE]; + + /* Copy /dev/tty -> pty master */ + if (io_fds[SFD_USERTTY] != -1) { + iobufs = io_buf_new(io_fds[SFD_USERTTY], io_fds[SFD_MASTER], + log_ttyin, iobufs); + + /* Copy pty master -> /dev/tty */ + iobufs = io_buf_new(io_fds[SFD_MASTER], io_fds[SFD_USERTTY], + log_ttyout, iobufs); + + /* Are we the foreground process? */ + foreground = tcgetpgrp(io_fds[SFD_USERTTY]) == ppgrp; + } + + /* + * If either stdin, stdout or stderr is not a tty we use a pipe + * to interpose ourselves instead of duping the pty fd. + */ + memset(io_pipe, 0, sizeof(io_pipe)); + if (!isatty(STDIN_FILENO)) { + pipeline = TRUE; + if (pipe(io_pipe[STDIN_FILENO]) != 0) + error(1, "unable to create pipe"); + iobufs = io_buf_new(STDIN_FILENO, io_pipe[STDIN_FILENO][1], + log_stdin, iobufs); + io_fds[SFD_STDIN] = io_pipe[STDIN_FILENO][0]; + } + if (!isatty(STDOUT_FILENO)) { + pipeline = TRUE; + if (pipe(io_pipe[STDOUT_FILENO]) != 0) + error(1, "unable to create pipe"); + iobufs = io_buf_new(io_pipe[STDOUT_FILENO][0], STDOUT_FILENO, + log_stdout, iobufs); + io_fds[SFD_STDOUT] = io_pipe[STDOUT_FILENO][1]; + } + if (!isatty(STDERR_FILENO)) { + if (pipe(io_pipe[STDERR_FILENO]) != 0) + error(1, "unable to create pipe"); + iobufs = io_buf_new(io_pipe[STDERR_FILENO][0], STDERR_FILENO, + log_stderr, iobufs); + io_fds[SFD_STDERR] = io_pipe[STDERR_FILENO][1]; + } + + /* Job control signals to relay from parent to child. */ + sa.sa_flags = 0; /* do not restart syscalls */ + sa.sa_handler = handler; + sigaction(SIGTSTP, &sa, NULL); +#if 0 /* XXX - add these? */ + sigaction(SIGTTIN, &sa, NULL); + sigaction(SIGTTOU, &sa, NULL); +#endif + + if (foreground) { + /* Copy terminal attrs from user tty -> pty slave. */ + if (term_copy(io_fds[SFD_USERTTY], io_fds[SFD_SLAVE])) { + tty_initialized = 1; + sync_ttysize(io_fds[SFD_USERTTY], io_fds[SFD_SLAVE]); + } + + /* Start out in raw mode if we are not part of a pipeline. */ + if (!pipeline) { + ttymode = TERM_RAW; + do { + n = term_raw(io_fds[SFD_USERTTY], 0); + } while (!n && errno == EINTR); + if (!n) + error(1, "Can't set terminal to raw mode"); + } + } + } + + /* + * Child will run the command in the pty, parent will pass data + * to and from pty. + */ + child = fork(); + switch (child) { + case -1: + error(1, "fork"); + break; + case 0: + /* child */ + close(sv[0]); + fcntl(sv[1], F_SETFD, FD_CLOEXEC); + if (exec_setup() == TRUE) { + /* headed for execve() */ + if (log_io) { + /* Close the other end of the stdin/stdout/stderr pipes. */ + if (io_pipe[STDIN_FILENO][1]) + close(io_pipe[STDIN_FILENO][1]); + if (io_pipe[STDOUT_FILENO][0]) + close(io_pipe[STDOUT_FILENO][0]); + if (io_pipe[STDERR_FILENO][0]) + close(io_pipe[STDERR_FILENO][0]); + exec_monitor(path, argv, envp, sv[1], rbac_enabled); + } else { + closefrom(def_closefrom); +#ifdef HAVE_SELINUX + if (rbac_enabled) + selinux_execve(path, argv, envp); + else +#endif + my_execve(path, argv, envp); + } + } + cstat->type = CMD_ERRNO; + cstat->val = errno; + send(sv[1], cstat, sizeof(*cstat), 0); + _exit(1); + } + close(sv[1]); + + /* Max fd we will be selecting on. */ + maxfd = sv[0]; + + if (log_io) { + /* Close the other end of the stdin/stdout/stderr pipes. */ + if (io_pipe[STDIN_FILENO][0]) + close(io_pipe[STDIN_FILENO][0]); + if (io_pipe[STDOUT_FILENO][1]) + close(io_pipe[STDOUT_FILENO][1]); + if (io_pipe[STDERR_FILENO][1]) + close(io_pipe[STDERR_FILENO][1]); + + for (iob = iobufs; iob; iob = iob->next) { + /* Determine maxfd */ + if (iob->rfd > maxfd) + maxfd = iob->rfd; + if (iob->wfd > maxfd) + maxfd = iob->wfd; + + /* Set non-blocking mode. */ + n = fcntl(iob->rfd, F_GETFL, 0); + if (n != -1 && !ISSET(n, O_NONBLOCK)) + (void) fcntl(iob->rfd, F_SETFL, n | O_NONBLOCK); + n = fcntl(iob->wfd, F_GETFL, 0); + if (n != -1 && !ISSET(n, O_NONBLOCK)) + (void) fcntl(iob->wfd, F_SETFL, n | O_NONBLOCK); + } + } + + /* + * In the event loop we pass input from user tty to master + * and pass output from master to stdout and IO plugin. + */ + fdsr = (fd_set *)emalloc2(howmany(maxfd + 1, NFDBITS), sizeof(fd_mask)); + fdsw = (fd_set *)emalloc2(howmany(maxfd + 1, NFDBITS), sizeof(fd_mask)); + for (;;) { + if (recvsig[SIGCHLD]) { + pid_t pid; + + /* + * If logging I/O, child is the intermediate process, + * otherwise it is the command itself. + */ + recvsig[SIGCHLD] = FALSE; + do { + pid = waitpid(child, &status, WNOHANG); + } while (pid == -1 && errno == EINTR); + if (pid == child) { + /* If not logging I/O and child has exited we are done. */ + if (!log_io) { + cstat->type = CMD_WSTATUS; + cstat->val = status; + return 0; + } + } + } + + zero_bytes(fdsw, howmany(maxfd + 1, NFDBITS) * sizeof(fd_mask)); + zero_bytes(fdsr, howmany(maxfd + 1, NFDBITS) * sizeof(fd_mask)); + + FD_SET(sv[0], fdsr); + for (iob = iobufs; iob; iob = iob->next) { + if (iob->rfd == -1 && iob->wfd == -1) + continue; + if (iob->off == iob->len) { + iob->off = iob->len = 0; + /* Forward the EOF from reader to writer. */ + if (iob->rfd == -1) { + safe_close(iob->wfd); + iob->wfd = -1; + } + } + /* Don't read/write /dev/tty if we are not in the foreground. */ + if (iob->rfd != -1 && + (ttymode == TERM_RAW || iob->rfd != io_fds[SFD_USERTTY])) { + if (iob->len != sizeof(iob->buf)) + FD_SET(iob->rfd, fdsr); + } + if (iob->wfd != -1 && + (foreground || iob->wfd != io_fds[SFD_USERTTY])) { + if (iob->len > iob->off) + FD_SET(iob->wfd, fdsw); + } + } + for (n = 0; n < NSIG; n++) { + if (recvsig[n] && n != SIGCHLD) { + if (log_io) { + FD_SET(sv[0], fdsw); + break; + } else { + /* nothing listening on sv[0], send directly */ + if (n == SIGALRM) { + terminate_child(child, FALSE); + } else { + kill(child, n); + } + } + } + } + + if (recvsig[SIGCHLD]) + continue; + nready = select(maxfd + 1, fdsr, fdsw, NULL, NULL); + if (nready == -1) { + if (errno == EINTR) + continue; + error(1, "select failed"); + } + if (FD_ISSET(sv[0], fdsr)) { + /* read child status */ + n = recv(sv[0], cstat, sizeof(*cstat), 0); + if (n == -1) { + if (errno == EINTR) + continue; + /* + * If not logging I/O we will receive ECONNRESET when + * the command is executed. It is safe to ignore this. + */ + if (log_io && errno != EAGAIN) { + cstat->type = CMD_ERRNO; + cstat->val = errno; + break; + } + } + if (cstat->type == CMD_WSTATUS) { + if (WIFSTOPPED(cstat->val)) { + /* Suspend parent and tell child how to resume on return. */ + n = suspend_parent(WSTOPSIG(cstat->val), iobufs); + recvsig[n] = TRUE; + 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 or broken pipe. */ + break; + } + } + + if (FD_ISSET(sv[0], fdsw)) { + for (n = 0; n < NSIG; n++) { + if (!recvsig[n]) + continue; + recvsig[n] = FALSE; + cstat->type = CMD_SIGNO; + cstat->val = n; + do { + n = send(sv[0], cstat, sizeof(*cstat), 0); + } while (n == -1 && errno == EINTR); + if (n != sizeof(*cstat)) { + recvsig[n] = TRUE; + break; + } + } + } + if (perform_io(iobufs, fdsr, fdsw) != 0) { + cstat->type = CMD_ERRNO; + cstat->val = errno; + break; + } + } + + if (log_io) { + /* Flush any remaining output (the plugin already got it) */ + if (io_fds[SFD_USERTTY] != -1) { + n = fcntl(io_fds[SFD_USERTTY], F_GETFL, 0); + if (n != -1 && ISSET(n, O_NONBLOCK)) { + CLR(n, O_NONBLOCK); + (void) fcntl(io_fds[SFD_USERTTY], F_SETFL, n); + } + } + flush_output(iobufs); + + if (io_fds[SFD_USERTTY] != -1) { + do { + n = term_restore(io_fds[SFD_USERTTY], 0); + } while (!n && errno == EINTR); + } + + if (cstat->type == CMD_WSTATUS && WIFSIGNALED(cstat->val)) { + int signo = WTERMSIG(cstat->val); + if (signo && signo != SIGINT && signo != SIGPIPE) { + char *reason = strsignal(signo); + n = io_fds[SFD_USERTTY] != -1 ? + io_fds[SFD_USERTTY] : STDOUT_FILENO; + write(n, reason, strlen(reason)); + if (WCOREDUMP(cstat->val)) + write(n, " (core dumped)", 14); + write(n, "\n", 1); + } + } + } + +#ifdef HAVE_SELINUX + if (rbac_enabled) { + /* This is probably not needed in log_io mode. */ + if (selinux_restore_tty() != 0) + warningx("unable to restore tty label"); + } +#endif + + efree(fdsr); + efree(fdsw); + while ((iob = iobufs) != NULL) { + iobufs = iobufs->next; + efree(iob); + } + + return cstat->type == CMD_ERRNO ? -1 : 0; +} + +static void +deliver_signal(pid_t pid, int signo) +{ + int status; + + /* Handle signal from parent. */ + switch (signo) { + case SIGKILL: + _exit(1); /* XXX */ + /* NOTREACHED */ + case SIGPIPE: + case SIGHUP: + case SIGTERM: + case SIGINT: + case SIGQUIT: + case SIGTSTP: + /* relay signal to child */ + killpg(pid, signo); + break; + case SIGALRM: + terminate_child(pid, TRUE); + break; + case SIGUSR1: + /* foreground process, grant it controlling tty. */ + do { + status = tcsetpgrp(io_fds[SFD_SLAVE], pid); + } while (status == -1 && errno == EINTR); + killpg(pid, SIGCONT); + break; + case SIGUSR2: + /* background process, I take controlling tty. */ + do { + status = tcsetpgrp(io_fds[SFD_SLAVE], getpid()); + } while (status == -1 && errno == EINTR); + killpg(pid, SIGCONT); + break; + default: + warningx("unexpected signal from child: %d", signo); + break; + } +} + +/* + * Send status to parent over socketpair. + * Return value is the same as send(2). + */ +static int +send_status(int fd, struct command_status *cstat) +{ + int n = -1; + + if (cstat->type != CMD_INVALID) { + do { + n = send(fd, cstat, sizeof(*cstat), 0); + } while (n == -1 && errno == EINTR); + cstat->type = CMD_INVALID; /* prevent re-sending */ + } + return n; +} + +/* + * Wait for child status after receiving SIGCHLD. + * If the child was stopped, the status is send back to the parent. + * Otherwise, cstat is filled in but not sent. + * Returns TRUE if child is still alive, else FALSE. + */ +static int +handle_sigchld(int backchannel, struct command_status *cstat) +{ + int status, alive = TRUE; + pid_t pid; + + /* read child status */ + do { + pid = waitpid(child, &status, WUNTRACED|WNOHANG); + } while (pid == -1 && errno == EINTR); + if (pid == child) { + if (cstat->type != CMD_ERRNO) { + cstat->type = CMD_WSTATUS; + cstat->val = status; + if (WIFSTOPPED(status)) { + if (send_status(backchannel, cstat) == -1) + return alive; /* XXX */ + } + } + if (!WIFSTOPPED(status)) + alive = FALSE; + } + return alive; +} + +/* + * Monitor process that creates a new session with the controlling tty, + * resets signal handlers and forks a child to call exec_pty(). + * Waits for status changes from the command and relays them to the + * parent and relays signals from the parent to the command. + * Returns an error if fork(2) fails, else calls _exit(2). + */ +int +exec_monitor(const char *path, char *argv[], char *envp[], + int backchannel, int rbac) +{ + struct command_status cstat; + struct timeval tv; + fd_set *fdsr; + sigaction_t sa; + int errpipe[2], maxfd, n, status; + int alive = TRUE; + + /* Close unused fds. */ + if (io_fds[SFD_MASTER] != -1) + close(io_fds[SFD_MASTER]); + if (io_fds[SFD_USERTTY] != -1) + close(io_fds[SFD_USERTTY]); + + /* Reset SIGWINCH and SIGALRM. */ + zero_bytes(&sa, sizeof(sa)); + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + sa.sa_handler = SIG_DFL; + sigaction(SIGWINCH, &sa, NULL); + sigaction(SIGALRM, &sa, NULL); + + /* Ignore any SIGTTIN or SIGTTOU we get. */ + sa.sa_handler = SIG_IGN; + sigaction(SIGTTIN, &sa, NULL); + sigaction(SIGTTOU, &sa, NULL); + + /* Note: HP-UX select() will not be interrupted if SA_RESTART set */ + sa.sa_flags = 0; + sa.sa_handler = handler; + sigaction(SIGCHLD, &sa, NULL); + + /* + * Start a new session with the parent as the session leader + * and the slave pty as the controlling terminal. + * This allows us to be notified when the child has been suspended. + */ +#ifdef HAVE_SETSID + if (setsid() == -1) { + warning("setsid"); + goto bad; + } +#else +# ifdef TIOCNOTTY + n = open(_PATH_TTY, O_RDWR|O_NOCTTY); + if (n >= 0) { + /* Disconnect from old controlling tty. */ + if (ioctl(n, TIOCNOTTY, NULL) == -1) + warning("cannot disconnect controlling tty"); + close(n); + } +# endif + setpgrp(0, 0); +#endif + if (io_fds[SFD_SLAVE] != -1) { +#ifdef TIOCSCTTY + if (ioctl(io_fds[SFD_SLAVE], TIOCSCTTY, NULL) != 0) + error(1, "unable to set controlling tty"); +#else + /* Set controlling tty by reopening slave. */ + if ((n = open(slavename, O_RDWR)) >= 0) + close(n); +#endif + } + + /* + * If stdin/stdout is not a tty, start command in the background + * since it might be part of a pipeline that reads from /dev/tty. + * In this case, we rely on the command receiving SIGTTOU or SIGTTIN + * when it needs access to the controlling tty. + */ + if (pipeline) + foreground = 0; + + /* Start command and wait for it to stop or exit */ + if (pipe(errpipe) == -1) + error(1, "unable to create pipe"); + child = fork(); + if (child == -1) { + warning("Can't fork"); + goto bad; + } + if (child == 0) { + /* We pass errno back to our parent via pipe on exec failure. */ + close(backchannel); + close(errpipe[0]); + fcntl(errpipe[1], F_SETFD, FD_CLOEXEC); + + /* setup tty and exec command */ + exec_pty(path, argv, envp, rbac); + cstat.type = CMD_ERRNO; + cstat.val = errno; + write(errpipe[1], &cstat, sizeof(cstat)); + _exit(1); + } + close(errpipe[1]); + + /* If any of stdin/stdout/stderr are pipes, close them in parent. */ + if (io_fds[SFD_STDIN] != io_fds[SFD_SLAVE]) + close(io_fds[SFD_STDIN]); + if (io_fds[SFD_STDOUT] != io_fds[SFD_SLAVE]) + close(io_fds[SFD_STDOUT]); + if (io_fds[SFD_STDERR] != io_fds[SFD_SLAVE]) + close(io_fds[SFD_STDERR]); + + /* + * Put child in its own process group. If we are starting the command + * in the foreground, assign its pgrp to the tty. + */ + setpgid(child, child); + if (foreground) { + do { + status = tcsetpgrp(io_fds[SFD_SLAVE], child); + } while (status == -1 && errno == EINTR); + } + + /* Wait for errno on pipe, signal on backchannel or for SIGCHLD */ + maxfd = MAX(errpipe[0], backchannel); + fdsr = (fd_set *)emalloc2(howmany(maxfd + 1, NFDBITS), sizeof(fd_mask)); + zero_bytes(fdsr, howmany(maxfd + 1, NFDBITS) * sizeof(fd_mask)); + zero_bytes(&cstat, sizeof(cstat)); + tv.tv_sec = 0; + tv.tv_usec = 0; + for (;;) { + /* Read child status. */ + if (recvsig[SIGCHLD]) { + recvsig[SIGCHLD] = FALSE; + alive = handle_sigchld(backchannel, &cstat); + } + + /* Check for signal on backchannel or errno on errpipe. */ + FD_SET(backchannel, fdsr); + if (errpipe[0] != -1) + FD_SET(errpipe[0], fdsr); + maxfd = MAX(errpipe[0], backchannel); + + if (recvsig[SIGCHLD]) + continue; + /* If command exited we just poll, there may be data on errpipe. */ + n = select(maxfd + 1, fdsr, NULL, NULL, alive ? NULL : &tv); + if (n <= 0) { + if (n == 0) + goto done; + if (errno == EINTR) + continue; + error(1, "select failed"); + } + + if (errpipe[0] != -1 && FD_ISSET(errpipe[0], fdsr)) { + /* read errno or EOF from command pipe */ + n = read(errpipe[0], &cstat, sizeof(cstat)); + if (n == -1) { + if (errno == EINTR) + continue; + warning("error reading from pipe"); + goto done; + } + /* Got errno or EOF, either way we are done with errpipe. */ + FD_CLR(errpipe[0], fdsr); + close(errpipe[0]); + errpipe[0] = -1; + } + if (FD_ISSET(backchannel, fdsr)) { + struct command_status cstmp; + + /* read command from backchannel, should be a signal */ + n = recv(backchannel, &cstmp, sizeof(cstmp), 0); + if (n == -1) { + if (errno == EINTR) + continue; + warning("error reading from socketpair"); + goto done; + } + if (cstmp.type != CMD_SIGNO) { + warningx("unexpected reply type on backchannel: %d", cstmp.type); + continue; + } + deliver_signal(child, cstmp.val); + } + } + +done: + if (alive) { + /* XXX An error occurred, should send an error back. */ + kill(child, SIGKILL); + } else { + /* Send parent status. */ + send_status(backchannel, &cstat); + } + _exit(1); + +bad: + return errno; +} + +/* + * Flush any output buffered in iobufs or readable from the fds. + * Does not read from /dev/tty. + */ +static void +flush_output(struct io_buffer *iobufs) +{ + struct io_buffer *iob; + struct timeval tv; + fd_set *fdsr, *fdsw; + int nready, nwriters, maxfd = -1; + + /* Determine maxfd */ + for (iob = iobufs; iob; iob = iob->next) { + if (iob->rfd > maxfd) + maxfd = iob->rfd; + if (iob->wfd > maxfd) + maxfd = iob->wfd; + } + if (maxfd == -1) + return; + + fdsr = (fd_set *)emalloc2(howmany(maxfd + 1, NFDBITS), sizeof(fd_mask)); + fdsw = (fd_set *)emalloc2(howmany(maxfd + 1, NFDBITS), sizeof(fd_mask)); + for (;;) { + zero_bytes(fdsw, howmany(maxfd + 1, NFDBITS) * sizeof(fd_mask)); + zero_bytes(fdsr, howmany(maxfd + 1, NFDBITS) * sizeof(fd_mask)); + + nwriters = 0; + for (iob = iobufs; iob; iob = iob->next) { + /* Don't read from /dev/tty while flushing. */ + if (io_fds[SFD_USERTTY] != -1 && iob->rfd == io_fds[SFD_USERTTY]) + continue; + if (iob->rfd == -1 && iob->wfd == -1) + continue; + if (iob->off == iob->len) { + iob->off = iob->len = 0; + /* Forward the EOF from reader to writer. */ + if (iob->rfd == -1) { + safe_close(iob->wfd); + iob->wfd = -1; + } + } + if (iob->rfd != -1) { + if (iob->len != sizeof(iob->buf)) + FD_SET(iob->rfd, fdsr); + } + if (iob->wfd != -1) { + if (iob->len > iob->off) { + nwriters++; + FD_SET(iob->wfd, fdsw); + } + } + } + + /* Don't sleep in select if there are no buffers that need writing. */ + tv.tv_sec = 0; + tv.tv_usec = 0; + nready = select(maxfd + 1, fdsr, fdsw, NULL, nwriters ? NULL : &tv); + if (nready <= 0) { + if (nready == 0) + break; /* all I/O flushed */ + if (errno == EINTR) + continue; + error(1, "select failed"); + } + if (perform_io(iobufs, fdsr, fdsw) != 0) + break; + } + efree(fdsr); + efree(fdsw); +} + +/* + * Sets up std{in,out,err} and executes the actual command. + * Returns only if execve() fails. + */ +static void +exec_pty(const char *path, char *argv[], char *envp[], + int rbac_enabled) +{ + sigaction_t sa; + pid_t self = getpid(); + + /* Reset signal handlers. */ + zero_bytes(&sa, sizeof(sa)); + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + sa.sa_handler = SIG_DFL; + sigaction(SIGHUP, &sa, NULL); + sigaction(SIGTERM, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + sigaction(SIGQUIT, &sa, NULL); + sigaction(SIGTSTP, &sa, NULL); + sigaction(SIGTTIN, &sa, NULL); + sigaction(SIGTTOU, &sa, NULL); + sigaction(SIGUSR1, &sa, NULL); + sigaction(SIGUSR2, &sa, NULL); + sigaction(SIGCHLD, &sa, NULL); + + /* Set child process group here too to avoid a race. */ + setpgid(0, self); + + /* Wire up standard fds, note that stdout/stderr may be pipes. */ + dup2(io_fds[SFD_STDIN], STDIN_FILENO); + dup2(io_fds[SFD_STDOUT], STDOUT_FILENO); + dup2(io_fds[SFD_STDERR], STDERR_FILENO); + + /* Wait for parent to grant us the tty if we are foreground. */ + if (foreground) { + while (tcgetpgrp(io_fds[SFD_SLAVE]) != self) + ; /* spin */ + } + + /* We have guaranteed that the slave fd is > 2 */ + if (io_fds[SFD_SLAVE] != -1) + close(io_fds[SFD_SLAVE]); + if (io_fds[SFD_STDIN] != io_fds[SFD_SLAVE]) + close(io_fds[SFD_STDIN]); + if (io_fds[SFD_STDOUT] != io_fds[SFD_SLAVE]) + close(io_fds[SFD_STDOUT]); + if (io_fds[SFD_STDERR] != io_fds[SFD_SLAVE]) + close(io_fds[SFD_STDERR]); + + closefrom(def_closefrom); +#ifdef HAVE_SELINUX + if (rbac_enabled) + selinux_execve(path, argv, envp); + else +#endif + my_execve(path, argv, envp); +} + +/* + * Propagates tty size change signals to pty being used by the command. + */ +static void +sync_ttysize(int src, int dst) +{ +#ifdef TIOCGSIZE + struct ttysize tsize; + pid_t pgrp; + + if (ioctl(src, TIOCGSIZE, &tsize) == 0) { + ioctl(dst, TIOCSSIZE, &tsize); +#ifdef TIOCGPGRP + if (ioctl(dst, TIOCGPGRP, &pgrp) == 0) + killpg(pgrp, SIGWINCH); +#endif + } +#endif +} + +/* + * Generic handler for signals passed from parent -> child. + * The recvsig[] array is checked in the main event loop. + */ +static void +handler(int s) +{ + recvsig[s] = TRUE; +} + +/* + * Handler for SIGWINCH in parent. + */ +static void +sigwinch(int s) +{ + int serrno = errno; + + sync_ttysize(io_fds[SFD_USERTTY], io_fds[SFD_SLAVE]); + errno = serrno; +} + +/* + * Only close the fd if it is not /dev/tty or std{in,out,err}. + * Return value is the same as send(2). + */ +static int +safe_close(int fd) +{ + /* Avoid closing /dev/tty or std{in,out,err}. */ + if (fd < 3 || fd == io_fds[SFD_USERTTY]) { + errno = EINVAL; + return -1; + } + return close(fd); +} diff --git a/gram.c b/gram.c index add47a40a..f4fca2ea8 100644 --- a/gram.c +++ b/gram.c @@ -1,11 +1,3 @@ -#ifndef lint -/*static char yysccsid[] = "from: @(#)yaccpar 1.9 (Berkeley) 02/21/93";*/ -static char yyrcsid[] -#if __GNUC__ >= 2 - __attribute__ ((unused)) -#endif /* __GNUC__ >= 2 */ - = "$OpenBSD: skeleton.c,v 1.29 2008/07/08 15:06:50 otto Exp $"; -#endif #include #include #define YYBYACC 1 @@ -127,7 +119,7 @@ yyerror(s) } parse_error = TRUE; } -#line 117 "gram.y" +#line 113 "gram.y" #ifndef YYSTYPE_DEFINED #define YYSTYPE_DEFINED typedef union { @@ -143,7 +135,7 @@ typedef union { int tok; } YYSTYPE; #endif /* YYSTYPE_DEFINED */ -#line 151 "y.tab.c" +#line 139 "y.tab.c" #define COMMAND 257 #define ALIAS 258 #define DEFVAR 259 @@ -162,17 +154,19 @@ typedef union { #define EXEC 272 #define SETENV 273 #define NOSETENV 274 -#define TRANSCRIPT 275 -#define NOTRANSCRIPT 276 -#define ALL 277 -#define COMMENT 278 -#define HOSTALIAS 279 -#define CMNDALIAS 280 -#define USERALIAS 281 -#define RUNASALIAS 282 -#define ERROR 283 -#define TYPE 284 -#define ROLE 285 +#define LOG_INPUT 275 +#define NOLOG_INPUT 276 +#define LOG_OUTPUT 277 +#define NOLOG_OUTPUT 278 +#define ALL 279 +#define COMMENT 280 +#define HOSTALIAS 281 +#define CMNDALIAS 282 +#define USERALIAS 283 +#define RUNASALIAS 284 +#define ERROR 285 +#define TYPE 286 +#define ROLE 287 #define YYERRCODE 256 #if defined(__cplusplus) || defined(__STDC__) const short yylhs[] = @@ -185,11 +179,11 @@ short yylhs[] = 3, 3, 3, 20, 20, 19, 10, 10, 8, 8, 8, 8, 8, 2, 2, 1, 6, 6, 23, 24, 22, 22, 22, 22, 22, 17, 17, 18, 18, 18, - 21, 21, 21, 21, 21, 21, 21, 21, 21, 5, - 5, 5, 28, 28, 31, 9, 9, 29, 29, 32, - 7, 7, 30, 30, 33, 27, 27, 34, 13, 13, - 11, 11, 12, 12, 12, 12, 12, 16, 16, 14, - 14, 15, 15, 15, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 5, 5, 5, 28, 28, 31, 9, 9, 29, + 29, 32, 7, 7, 30, 30, 33, 27, 27, 34, + 13, 13, 11, 11, 12, 12, 12, 12, 12, 16, + 16, 14, 14, 15, 15, 15, }; #if defined(__cplusplus) || defined(__STDC__) const short yylen[] = @@ -202,11 +196,11 @@ short yylen[] = 3, 3, 3, 1, 3, 3, 1, 2, 1, 1, 1, 1, 1, 1, 3, 4, 1, 2, 3, 3, 0, 1, 1, 2, 2, 0, 3, 1, 3, 2, - 0, 2, 2, 2, 2, 2, 2, 2, 2, 1, - 1, 1, 1, 3, 3, 1, 3, 1, 3, 3, - 1, 3, 1, 3, 3, 1, 3, 3, 1, 3, - 1, 2, 1, 1, 1, 1, 1, 1, 3, 1, - 2, 1, 1, 1, + 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 1, 1, 1, 1, 3, 3, 1, 3, 1, + 3, 3, 1, 3, 1, 3, 3, 1, 3, 3, + 1, 3, 1, 2, 1, 1, 1, 1, 1, 1, + 3, 1, 2, 1, 1, 1, }; #if defined(__cplusplus) || defined(__STDC__) const short yydefred[] = @@ -214,21 +208,21 @@ const short yydefred[] = short yydefred[] = #endif { 0, - 0, 83, 85, 86, 87, 0, 0, 0, 0, 0, - 84, 5, 0, 0, 0, 0, 0, 0, 79, 81, + 0, 85, 87, 88, 89, 0, 0, 0, 0, 0, + 86, 5, 0, 0, 0, 0, 0, 0, 81, 83, 0, 0, 3, 6, 0, 0, 17, 0, 29, 32, - 31, 33, 30, 0, 27, 0, 66, 0, 0, 62, - 61, 60, 0, 37, 71, 0, 0, 0, 63, 0, - 0, 68, 0, 0, 76, 0, 0, 73, 82, 0, + 31, 33, 30, 0, 27, 0, 68, 0, 0, 64, + 63, 62, 0, 37, 73, 0, 0, 0, 65, 0, + 0, 70, 0, 0, 78, 0, 0, 75, 84, 0, 0, 24, 0, 4, 0, 0, 0, 20, 0, 28, 0, 0, 0, 0, 38, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 80, 0, 0, 21, 22, - 23, 18, 67, 72, 0, 64, 0, 69, 0, 77, - 0, 74, 0, 34, 0, 0, 25, 0, 0, 0, - 0, 0, 0, 51, 0, 0, 92, 94, 93, 0, - 88, 90, 0, 0, 47, 35, 0, 0, 0, 44, - 45, 91, 0, 0, 40, 39, 52, 53, 54, 55, - 56, 57, 58, 59, 36, 89, + 0, 0, 0, 0, 0, 82, 0, 0, 21, 22, + 23, 18, 69, 74, 0, 66, 0, 71, 0, 79, + 0, 76, 0, 34, 0, 0, 25, 0, 0, 0, + 0, 0, 0, 51, 0, 0, 94, 96, 95, 0, + 90, 92, 0, 0, 47, 35, 0, 0, 0, 44, + 45, 93, 0, 0, 40, 39, 52, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 36, 91, }; #if defined(__cplusplus) || defined(__STDC__) const short yydgoto[] = @@ -246,238 +240,250 @@ const short yysindex[] = #else short yysindex[] = #endif - { -26, - -272, 0, 0, 0, 0, -16, 481, 485, 485, -31, - 0, 0, -237, -232, -228, -227, -238, 0, 0, 0, - 417, -26, 0, 0, -2, -217, 0, 7, 0, 0, - 0, 0, 0, -223, 0, -28, 0, -25, -25, 0, - 0, 0, -248, 0, 0, -11, -14, -6, 0, -3, - -5, 0, -1, 4, 0, 2, 6, 0, 0, 485, - -17, 0, 8, 0, -198, -196, -195, 0, -16, 0, - 481, 7, 7, 7, 0, -31, 7, 481, -237, -31, - -232, 485, -228, 485, -227, 0, 29, 481, 0, 0, - 0, 0, 0, 0, 26, 0, 30, 0, 31, 0, - 31, 0, 457, 0, 32, -270, 0, -30, -8, 36, - 29, 11, 12, 0, -206, -205, 0, 0, 0, -245, - 0, 0, 35, -30, 0, 0, -182, -180, 453, 0, - 0, 0, -30, 35, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0,}; + { 475, + -270, 0, 0, 0, 0, -29, 567, 594, 594, -2, + 0, 0, -240, -222, -216, -212, -241, 0, 0, 0, + -25, 475, 0, 0, -10, -207, 0, 9, 0, 0, + 0, 0, 0, -235, 0, -33, 0, -31, -31, 0, + 0, 0, -242, 0, 0, -30, -7, 3, 0, -6, + 4, 0, -5, 6, 0, -1, 8, 0, 0, 594, + -20, 0, 10, 0, -205, -196, -194, 0, -29, 0, + 567, 9, 9, 9, 0, -2, 9, 567, -240, -2, + -222, 594, -216, 594, -212, 0, 31, 567, 0, 0, + 0, 0, 0, 0, 26, 0, 28, 0, 29, 0, + 29, 0, 541, 0, 32, -247, 0, 86, -15, 33, + 31, 14, 16, 0, -208, -204, 0, 0, 0, -231, + 0, 0, 38, 86, 0, 0, -179, -178, 491, 0, + 0, 0, 86, 38, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0,}; #if defined(__cplusplus) || defined(__STDC__) const short yyrindex[] = #else short yyrindex[] = #endif - { 84, + { 87, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 89, 0, 0, 1, 0, 0, 163, 0, 0, + 0, 90, 0, 0, 1, 0, 0, 177, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 190, 0, 0, - 217, 0, 0, 244, 0, 0, 271, 0, 0, 0, - 0, 0, 298, 0, 0, 0, 0, 0, 0, 0, - 0, 325, 352, 379, 0, 0, 406, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 432, 0, 0, 0, - 0, 0, 0, 0, 28, 0, 55, 0, 82, 0, - 109, 0, 0, 0, 136, 480, 0, 0, 49, 0, - 432, 0, 0, 0, 507, 534, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 207, 0, 0, + 237, 0, 0, 271, 0, 0, 300, 0, 0, 0, + 0, 0, 329, 0, 0, 0, 0, 0, 0, 0, + 0, 358, 387, 417, 0, 0, 446, 0, 0, 0, + 0, 0, 0, 0, 0, 0, -26, 0, 0, 0, + 0, 0, 0, 0, 30, 0, 59, 0, 89, 0, + 118, 0, 0, 0, 148, 514, 0, 0, 45, 0, + -26, 0, 0, 0, 537, 565, 0, 0, 0, 0, 0, 0, 50, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 51, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0,}; + 0, 0, 0, 52, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0,}; #if defined(__cplusplus) || defined(__STDC__) const short yygindex[] = #else short yygindex[] = #endif { 0, - -18, 0, 25, 10, 52, -72, 16, 63, -7, 27, - 39, 83, 3, -32, -15, -22, 0, 0, 15, 0, - 0, 0, -12, -4, 0, 85, 0, 0, 0, 0, - 33, 37, 23, 34, + -17, 0, 27, 11, 54, -64, 15, 64, 2, 34, + 39, 84, -3, -27, -18, -21, 0, 0, 19, 0, + 0, 0, -12, -4, 0, 88, 0, 0, 0, 0, + 35, 40, 23, 37, }; -#define YYTABLESIZE 811 +#define YYTABLESIZE 873 #if defined(__cplusplus) || defined(__STDC__) const short yytable[] = #else short yytable[] = #endif - { 36, - 19, 43, 120, 94, 26, 24, 17, 26, 40, 41, - 38, 39, 117, 112, 113, 71, 26, 118, 60, 2, - 47, 26, 3, 4, 5, 50, 71, 65, 42, 53, - 56, 119, 76, 19, 29, 60, 30, 31, 11, 32, - 66, 68, 67, 87, 19, 72, 78, 73, 74, 124, - 69, 79, 81, 33, 70, 77, 145, 80, 65, 82, - 65, 83, 84, 85, 89, 88, 90, 91, 103, 71, - 95, 127, 128, 76, 60, 111, 125, 112, 133, 113, - 135, 78, 136, 1, 99, 65, 101, 70, 2, 48, - 50, 49, 126, 92, 75, 97, 70, 93, 86, 59, - 146, 134, 107, 131, 132, 109, 64, 102, 75, 0, - 130, 96, 70, 0, 78, 0, 100, 98, 0, 0, + { 26, + 19, 26, 26, 26, 38, 39, 46, 34, 36, 24, + 71, 94, 60, 76, 40, 41, 2, 47, 60, 3, + 4, 5, 29, 71, 30, 31, 117, 32, 60, 67, + 43, 118, 66, 19, 67, 50, 42, 11, 112, 113, + 87, 53, 124, 33, 19, 56, 72, 119, 73, 74, + 65, 68, 69, 78, 80, 82, 77, 89, 72, 84, + 79, 81, 67, 83, 147, 85, 90, 88, 91, 71, + 103, 76, 60, 125, 127, 111, 128, 112, 99, 95, + 101, 133, 113, 135, 136, 48, 1, 67, 80, 2, + 50, 72, 49, 126, 97, 92, 75, 70, 86, 109, + 59, 132, 134, 131, 93, 148, 107, 102, 0, 64, + 130, 0, 0, 96, 0, 0, 72, 77, 120, 100, + 98, 80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 26, 0, 0, 0, 78, - 0, 75, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 80, 26, 0, 0, + 77, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 12, 0, 0, 0, 75, 0, 26, 0, + 0, 0, 0, 0, 0, 77, 12, 0, 0, 0, + 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, - 0, 0, 0, 26, 0, 12, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 26, 9, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, - 0, 0, 9, 0, 0, 40, 41, 117, 0, 1, - 25, 2, 118, 25, 3, 4, 5, 6, 7, 8, - 9, 10, 25, 8, 0, 42, 119, 25, 0, 10, - 11, 12, 13, 14, 15, 16, 19, 0, 19, 0, - 0, 19, 19, 19, 19, 19, 19, 19, 19, 0, - 11, 0, 0, 0, 0, 0, 8, 19, 19, 19, - 19, 19, 19, 65, 0, 65, 0, 0, 65, 65, - 65, 65, 65, 65, 65, 65, 0, 7, 0, 0, - 0, 0, 0, 11, 65, 65, 65, 65, 65, 65, - 70, 0, 70, 0, 0, 70, 70, 70, 70, 70, - 70, 70, 70, 0, 15, 0, 0, 0, 0, 0, - 7, 70, 70, 70, 70, 70, 70, 78, 0, 78, - 0, 0, 78, 78, 78, 78, 78, 78, 78, 78, - 0, 13, 0, 0, 0, 0, 0, 15, 78, 78, - 78, 78, 78, 78, 75, 0, 75, 0, 0, 75, - 75, 75, 75, 75, 75, 75, 75, 0, 14, 0, - 0, 0, 0, 0, 13, 75, 75, 75, 75, 75, - 75, 26, 0, 26, 0, 0, 26, 26, 26, 26, - 26, 26, 26, 26, 0, 16, 0, 0, 0, 0, - 0, 14, 26, 26, 26, 26, 26, 26, 12, 0, - 12, 0, 0, 12, 12, 12, 12, 12, 12, 12, - 12, 0, 0, 0, 0, 0, 0, 0, 16, 12, - 12, 12, 12, 12, 12, 9, 0, 9, 0, 34, - 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, - 60, 0, 0, 0, 46, 0, 9, 9, 9, 9, - 9, 9, 10, 0, 10, 0, 0, 10, 10, 10, - 10, 10, 10, 10, 10, 43, 0, 0, 0, 17, - 0, 0, 0, 10, 10, 10, 10, 10, 10, 8, - 0, 8, 0, 0, 8, 8, 8, 8, 8, 8, - 8, 8, 41, 34, 108, 0, 0, 17, 0, 0, - 8, 8, 8, 8, 8, 8, 11, 0, 11, 0, - 0, 11, 11, 11, 11, 11, 11, 11, 11, 42, - 0, 0, 0, 0, 0, 0, 0, 11, 11, 11, - 11, 11, 11, 7, 0, 7, 0, 0, 7, 7, - 7, 7, 7, 7, 7, 7, 43, 0, 0, 0, - 0, 0, 0, 0, 7, 7, 7, 7, 7, 7, - 15, 0, 15, 0, 0, 15, 15, 15, 15, 15, - 15, 15, 15, 0, 0, 0, 0, 0, 0, 0, - 0, 15, 15, 15, 15, 15, 15, 13, 0, 13, - 0, 0, 13, 13, 13, 13, 13, 13, 13, 13, - 0, 0, 0, 0, 0, 0, 0, 0, 13, 13, - 13, 13, 13, 13, 14, 0, 14, 0, 0, 14, - 14, 14, 14, 14, 14, 14, 14, 0, 0, 0, + 0, 0, 0, 0, 0, 25, 0, 25, 25, 25, + 46, 46, 29, 0, 30, 31, 10, 32, 0, 9, + 0, 0, 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 33, 40, 41, 19, 0, 19, 46, + 46, 19, 19, 19, 19, 19, 19, 19, 19, 10, + 8, 0, 0, 0, 0, 0, 42, 0, 0, 19, + 19, 19, 19, 19, 19, 67, 0, 67, 0, 0, + 67, 67, 67, 67, 67, 67, 67, 67, 0, 11, + 0, 0, 0, 8, 0, 0, 0, 0, 67, 67, + 67, 67, 67, 67, 72, 0, 72, 0, 0, 72, + 72, 72, 72, 72, 72, 72, 72, 0, 7, 0, + 0, 0, 11, 0, 0, 0, 0, 72, 72, 72, + 72, 72, 72, 117, 80, 0, 80, 0, 118, 80, + 80, 80, 80, 80, 80, 80, 80, 15, 0, 0, + 0, 7, 0, 0, 119, 0, 0, 80, 80, 80, + 80, 80, 80, 77, 0, 77, 0, 0, 77, 77, + 77, 77, 77, 77, 77, 77, 13, 0, 0, 0, + 15, 0, 0, 0, 0, 0, 77, 77, 77, 77, + 77, 77, 0, 26, 0, 26, 0, 0, 26, 26, + 26, 26, 26, 26, 26, 26, 14, 0, 0, 13, + 0, 0, 0, 0, 0, 0, 26, 26, 26, 26, + 26, 26, 12, 0, 12, 0, 0, 12, 12, 12, + 12, 12, 12, 12, 12, 16, 0, 0, 0, 14, + 0, 0, 0, 0, 0, 12, 12, 12, 12, 12, + 12, 0, 9, 0, 9, 0, 0, 9, 9, 9, + 9, 9, 9, 9, 9, 0, 0, 0, 16, 0, + 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, + 9, 0, 10, 0, 10, 0, 0, 10, 10, 10, + 10, 10, 10, 10, 10, 0, 0, 17, 0, 0, + 0, 0, 0, 0, 0, 10, 10, 10, 10, 10, + 10, 0, 0, 43, 0, 0, 8, 0, 8, 0, + 0, 8, 8, 8, 8, 8, 8, 8, 8, 0, + 0, 0, 0, 0, 0, 0, 41, 0, 0, 8, + 8, 8, 8, 8, 8, 11, 0, 11, 0, 0, + 11, 11, 11, 11, 11, 11, 11, 11, 0, 42, + 0, 0, 0, 17, 0, 0, 0, 0, 11, 11, + 11, 11, 11, 11, 7, 0, 7, 0, 0, 7, + 7, 7, 7, 7, 7, 7, 7, 43, 108, 34, + 0, 0, 0, 0, 0, 0, 0, 7, 7, 7, + 7, 7, 7, 15, 0, 15, 0, 0, 15, 15, + 15, 15, 15, 15, 15, 15, 17, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, + 15, 15, 13, 0, 13, 0, 0, 13, 13, 13, + 13, 13, 13, 13, 13, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 13, 13, 13, 13, 13, + 13, 0, 14, 0, 14, 0, 0, 14, 14, 14, + 14, 14, 14, 14, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 14, 14, 14, 14, 14, 16, 0, 16, 0, 0, 16, 16, 16, 16, - 16, 16, 16, 16, 29, 0, 30, 31, 0, 32, - 0, 0, 16, 16, 16, 16, 16, 16, 46, 46, - 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, - 46, 46, 46, 46, 46, 46, 46, 46, 46, 40, - 41, 0, 0, 0, 2, 46, 46, 3, 4, 5, - 0, 137, 138, 139, 140, 141, 142, 143, 144, 42, - 0, 0, 0, 11, 0, 0, 41, 41, 29, 0, - 30, 31, 2, 32, 0, 3, 4, 5, 41, 41, - 41, 41, 41, 41, 41, 41, 41, 33, 0, 0, - 0, 11, 0, 42, 42, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 42, 42, 42, 42, 42, - 42, 42, 42, 42, 0, 0, 0, 0, 0, 0, - 43, 43, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 43, 43, 43, 43, 43, 43, 43, 43, - 43, + 16, 16, 16, 16, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 16, 16, 16, 16, 16, 16, + 1, 0, 2, 0, 0, 3, 4, 5, 6, 7, + 8, 9, 10, 0, 0, 0, 0, 40, 41, 0, + 0, 0, 0, 11, 12, 13, 14, 15, 16, 137, + 138, 139, 140, 141, 142, 143, 144, 145, 146, 42, + 41, 41, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 42, 42, 0, 0, 0, 2, 0, + 0, 3, 4, 5, 0, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 0, 0, 0, 11, + 0, 43, 43, 0, 29, 0, 30, 31, 0, 32, + 0, 0, 0, 43, 43, 43, 43, 43, 43, 43, + 43, 43, 43, 43, 0, 33, 0, 0, 0, 0, + 0, 2, 0, 0, 3, 4, 5, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 11, }; #if defined(__cplusplus) || defined(__STDC__) const short yycheck[] = #else short yycheck[] = #endif - { 7, - 0, 33, 33, 76, 33, 278, 33, 33, 257, 258, - 8, 9, 258, 284, 285, 44, 33, 263, 44, 258, - 258, 33, 261, 262, 263, 258, 44, 0, 277, 258, - 258, 277, 44, 33, 258, 44, 260, 261, 277, 263, - 43, 259, 45, 61, 44, 36, 61, 38, 39, 58, - 44, 58, 58, 277, 0, 46, 129, 61, 61, 61, - 33, 58, 61, 58, 263, 58, 263, 263, 40, 44, - 78, 61, 61, 44, 44, 44, 41, 284, 44, 285, - 263, 0, 263, 0, 82, 58, 84, 33, 0, 41, - 41, 41, 111, 69, 43, 80, 34, 71, 60, 17, - 133, 124, 88, 116, 120, 103, 22, 85, 0, -1, - 115, 79, 58, -1, 33, -1, 83, 81, -1, -1, + { 33, + 0, 33, 33, 33, 8, 9, 33, 33, 7, 280, + 44, 76, 44, 44, 257, 258, 258, 258, 44, 261, + 262, 263, 258, 44, 260, 261, 258, 263, 44, 0, + 33, 263, 43, 33, 45, 258, 279, 279, 286, 287, + 61, 258, 58, 279, 44, 258, 36, 279, 38, 39, + 61, 259, 44, 61, 61, 61, 46, 263, 0, 61, + 58, 58, 33, 58, 129, 58, 263, 58, 263, 44, + 40, 44, 44, 41, 61, 44, 61, 286, 82, 78, + 84, 44, 287, 263, 263, 41, 0, 58, 0, 0, + 41, 33, 41, 111, 80, 69, 43, 34, 60, 103, + 17, 120, 124, 116, 71, 133, 88, 85, -1, 22, + 115, -1, -1, 79, -1, -1, 58, 0, 33, 83, + 81, 33, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, 0, -1, -1, -1, 58, - -1, 33, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, 58, 0, -1, -1, + 33, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, 0, -1, -1, -1, 58, -1, 33, -1, + -1, -1, -1, -1, -1, 58, 0, -1, -1, -1, + 33, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, - -1, -1, -1, 58, -1, 33, -1, -1, -1, -1, + -1, -1, -1, -1, -1, 58, 0, -1, -1, 33, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, 0, -1, -1, -1, - -1, -1, 33, -1, -1, 257, 258, 258, -1, 256, - 259, 258, 263, 259, 261, 262, 263, 264, 265, 266, - 267, 268, 259, 0, -1, 277, 277, 259, -1, 33, - 277, 278, 279, 280, 281, 282, 256, -1, 258, -1, - -1, 261, 262, 263, 264, 265, 266, 267, 268, -1, - 0, -1, -1, -1, -1, -1, 33, 277, 278, 279, - 280, 281, 282, 256, -1, 258, -1, -1, 261, 262, - 263, 264, 265, 266, 267, 268, -1, 0, -1, -1, - -1, -1, -1, 33, 277, 278, 279, 280, 281, 282, - 256, -1, 258, -1, -1, 261, 262, 263, 264, 265, - 266, 267, 268, -1, 0, -1, -1, -1, -1, -1, - 33, 277, 278, 279, 280, 281, 282, 256, -1, 258, - -1, -1, 261, 262, 263, 264, 265, 266, 267, 268, - -1, 0, -1, -1, -1, -1, -1, 33, 277, 278, - 279, 280, 281, 282, 256, -1, 258, -1, -1, 261, + -1, -1, -1, -1, -1, 259, -1, 259, 259, 259, + 257, 258, 258, -1, 260, 261, 0, 263, -1, 33, + -1, -1, 269, 270, 271, 272, 273, 274, 275, 276, + 277, 278, 279, 279, 257, 258, 256, -1, 258, 286, + 287, 261, 262, 263, 264, 265, 266, 267, 268, 33, + 0, -1, -1, -1, -1, -1, 279, -1, -1, 279, + 280, 281, 282, 283, 284, 256, -1, 258, -1, -1, + 261, 262, 263, 264, 265, 266, 267, 268, -1, 0, + -1, -1, -1, 33, -1, -1, -1, -1, 279, 280, + 281, 282, 283, 284, 256, -1, 258, -1, -1, 261, 262, 263, 264, 265, 266, 267, 268, -1, 0, -1, - -1, -1, -1, -1, 33, 277, 278, 279, 280, 281, - 282, 256, -1, 258, -1, -1, 261, 262, 263, 264, - 265, 266, 267, 268, -1, 0, -1, -1, -1, -1, - -1, 33, 277, 278, 279, 280, 281, 282, 256, -1, - 258, -1, -1, 261, 262, 263, 264, 265, 266, 267, - 268, -1, -1, -1, -1, -1, -1, -1, 33, 277, - 278, 279, 280, 281, 282, 256, -1, 258, -1, 33, - 261, 262, 263, 264, 265, 266, 267, 268, -1, -1, - 44, -1, -1, -1, 33, -1, 277, 278, 279, 280, - 281, 282, 256, -1, 258, -1, -1, 261, 262, 263, - 264, 265, 266, 267, 268, 33, -1, -1, -1, 33, - -1, -1, -1, 277, 278, 279, 280, 281, 282, 256, - -1, 258, -1, -1, 261, 262, 263, 264, 265, 266, - 267, 268, 33, 33, 58, -1, -1, 33, -1, -1, - 277, 278, 279, 280, 281, 282, 256, -1, 258, -1, - -1, 261, 262, 263, 264, 265, 266, 267, 268, 33, - -1, -1, -1, -1, -1, -1, -1, 277, 278, 279, - 280, 281, 282, 256, -1, 258, -1, -1, 261, 262, + -1, -1, 33, -1, -1, -1, -1, 279, 280, 281, + 282, 283, 284, 258, 256, -1, 258, -1, 263, 261, + 262, 263, 264, 265, 266, 267, 268, 0, -1, -1, + -1, 33, -1, -1, 279, -1, -1, 279, 280, 281, + 282, 283, 284, 256, -1, 258, -1, -1, 261, 262, + 263, 264, 265, 266, 267, 268, 0, -1, -1, -1, + 33, -1, -1, -1, -1, -1, 279, 280, 281, 282, + 283, 284, -1, 256, -1, 258, -1, -1, 261, 262, + 263, 264, 265, 266, 267, 268, 0, -1, -1, 33, + -1, -1, -1, -1, -1, -1, 279, 280, 281, 282, + 283, 284, 256, -1, 258, -1, -1, 261, 262, 263, + 264, 265, 266, 267, 268, 0, -1, -1, -1, 33, + -1, -1, -1, -1, -1, 279, 280, 281, 282, 283, + 284, -1, 256, -1, 258, -1, -1, 261, 262, 263, + 264, 265, 266, 267, 268, -1, -1, -1, 33, -1, + -1, -1, -1, -1, -1, 279, 280, 281, 282, 283, + 284, -1, 256, -1, 258, -1, -1, 261, 262, 263, + 264, 265, 266, 267, 268, -1, -1, 33, -1, -1, + -1, -1, -1, -1, -1, 279, 280, 281, 282, 283, + 284, -1, -1, 33, -1, -1, 256, -1, 258, -1, + -1, 261, 262, 263, 264, 265, 266, 267, 268, -1, + -1, -1, -1, -1, -1, -1, 33, -1, -1, 279, + 280, 281, 282, 283, 284, 256, -1, 258, -1, -1, + 261, 262, 263, 264, 265, 266, 267, 268, -1, 33, + -1, -1, -1, 33, -1, -1, -1, -1, 279, 280, + 281, 282, 283, 284, 256, -1, 258, -1, -1, 261, + 262, 263, 264, 265, 266, 267, 268, 33, 58, 33, + -1, -1, -1, -1, -1, -1, -1, 279, 280, 281, + 282, 283, 284, 256, -1, 258, -1, -1, 261, 262, 263, 264, 265, 266, 267, 268, 33, -1, -1, -1, - -1, -1, -1, -1, 277, 278, 279, 280, 281, 282, + -1, -1, -1, -1, -1, -1, 279, 280, 281, 282, + 283, 284, 256, -1, 258, -1, -1, 261, 262, 263, + 264, 265, 266, 267, 268, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, 279, 280, 281, 282, 283, + 284, -1, 256, -1, 258, -1, -1, 261, 262, 263, + 264, 265, 266, 267, 268, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, 279, 280, 281, 282, 283, + 284, 256, -1, 258, -1, -1, 261, 262, 263, 264, + 265, 266, 267, 268, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, 279, 280, 281, 282, 283, 284, 256, -1, 258, -1, -1, 261, 262, 263, 264, 265, - 266, 267, 268, -1, -1, -1, -1, -1, -1, -1, - -1, 277, 278, 279, 280, 281, 282, 256, -1, 258, - -1, -1, 261, 262, 263, 264, 265, 266, 267, 268, - -1, -1, -1, -1, -1, -1, -1, -1, 277, 278, - 279, 280, 281, 282, 256, -1, 258, -1, -1, 261, - 262, 263, 264, 265, 266, 267, 268, -1, -1, -1, - -1, -1, -1, -1, -1, 277, 278, 279, 280, 281, - 282, 256, -1, 258, -1, -1, 261, 262, 263, 264, - 265, 266, 267, 268, 258, -1, 260, 261, -1, 263, - -1, -1, 277, 278, 279, 280, 281, 282, 257, 258, - -1, -1, -1, 277, -1, -1, -1, -1, -1, -1, - 269, 270, 271, 272, 273, 274, 275, 276, 277, 257, - 258, -1, -1, -1, 258, 284, 285, 261, 262, 263, - -1, 269, 270, 271, 272, 273, 274, 275, 276, 277, - -1, -1, -1, 277, -1, -1, 257, 258, 258, -1, - 260, 261, 258, 263, -1, 261, 262, 263, 269, 270, - 271, 272, 273, 274, 275, 276, 277, 277, -1, -1, - -1, 277, -1, 257, 258, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, 269, 270, 271, 272, 273, - 274, 275, 276, 277, -1, -1, -1, -1, -1, -1, + 266, 267, 268, -1, -1, -1, -1, 257, 258, -1, + -1, -1, -1, 279, 280, 281, 282, 283, 284, 269, + 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 257, 258, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 269, 270, 271, 272, 273, 274, 275, 276, - 277, + 277, 278, 279, 257, 258, -1, -1, -1, 258, -1, + -1, 261, 262, 263, -1, 269, 270, 271, 272, 273, + 274, 275, 276, 277, 278, 279, -1, -1, -1, 279, + -1, 257, 258, -1, 258, -1, 260, 261, -1, 263, + -1, -1, -1, 269, 270, 271, 272, 273, 274, 275, + 276, 277, 278, 279, -1, 279, -1, -1, -1, -1, + -1, 258, -1, -1, 261, 262, 263, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, 279, }; #define YYFINAL 18 #ifndef YYDEBUG #define YYDEBUG 0 #endif -#define YYMAXTOKEN 285 +#define YYMAXTOKEN 287 #if YYDEBUG #if defined(__cplusplus) || defined(__STDC__) const char * const yyname[] = @@ -494,9 +500,9 @@ char *yyname[] = 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, "COMMAND","ALIAS","DEFVAR","NTWKADDR","NETGROUP","USERGROUP","WORD","DEFAULTS", "DEFAULTS_HOST","DEFAULTS_USER","DEFAULTS_RUNAS","DEFAULTS_CMND","NOPASSWD", -"PASSWD","NOEXEC","EXEC","SETENV","NOSETENV","TRANSCRIPT","NOTRANSCRIPT","ALL", -"COMMENT","HOSTALIAS","CMNDALIAS","USERALIAS","RUNASALIAS","ERROR","TYPE", -"ROLE", +"PASSWD","NOEXEC","EXEC","SETENV","NOSETENV","LOG_INPUT","NOLOG_INPUT", +"LOG_OUTPUT","NOLOG_OUTPUT","ALL","COMMENT","HOSTALIAS","CMNDALIAS","USERALIAS", +"RUNASALIAS","ERROR","TYPE","ROLE", }; #if defined(__cplusplus) || defined(__STDC__) const char * const yyrule[] = @@ -561,8 +567,10 @@ char *yyrule[] = "cmndtag : cmndtag EXEC", "cmndtag : cmndtag SETENV", "cmndtag : cmndtag NOSETENV", -"cmndtag : cmndtag TRANSCRIPT", -"cmndtag : cmndtag NOTRANSCRIPT", +"cmndtag : cmndtag LOG_INPUT", +"cmndtag : cmndtag NOLOG_INPUT", +"cmndtag : cmndtag LOG_OUTPUT", +"cmndtag : cmndtag NOLOG_OUTPUT", "cmnd : ALL", "cmnd : ALIAS", "cmnd : COMMAND", @@ -625,7 +633,7 @@ short *yyss; short *yysslim; YYSTYPE *yyvs; int yystacksize; -#line 600 "gram.y" +#line 607 "gram.y" static struct defaults * new_default(var, val, op) char *var; @@ -816,7 +824,7 @@ init_parser(path, quiet) sudolineno = 1; verbose = !quiet; } -#line 772 "y.tab.c" +#line 776 "y.tab.c" /* allocate initial stack or double stack size, up to YYMAXDEPTH */ #if defined(__cplusplus) || defined(__STDC__) static int yygrowstack(void) @@ -1022,127 +1030,127 @@ yyreduce: switch (yyn) { case 1: -#line 190 "gram.y" +#line 188 "gram.y" { ; } break; case 5: -#line 198 "gram.y" +#line 196 "gram.y" { ; } break; case 6: -#line 201 "gram.y" +#line 199 "gram.y" { yyerrok; } break; case 7: -#line 204 "gram.y" +#line 202 "gram.y" { add_userspec(yyvsp[-1].member, yyvsp[0].privilege); } break; case 8: -#line 207 "gram.y" +#line 205 "gram.y" { ; } break; case 9: -#line 210 "gram.y" +#line 208 "gram.y" { ; } break; case 10: -#line 213 "gram.y" +#line 211 "gram.y" { ; } break; case 11: -#line 216 "gram.y" +#line 214 "gram.y" { ; } break; case 12: -#line 219 "gram.y" +#line 217 "gram.y" { add_defaults(DEFAULTS, NULL, yyvsp[0].defaults); } break; case 13: -#line 222 "gram.y" +#line 220 "gram.y" { add_defaults(DEFAULTS_USER, yyvsp[-1].member, yyvsp[0].defaults); } break; case 14: -#line 225 "gram.y" +#line 223 "gram.y" { add_defaults(DEFAULTS_RUNAS, yyvsp[-1].member, yyvsp[0].defaults); } break; case 15: -#line 228 "gram.y" +#line 226 "gram.y" { add_defaults(DEFAULTS_HOST, yyvsp[-1].member, yyvsp[0].defaults); } break; case 16: -#line 231 "gram.y" +#line 229 "gram.y" { add_defaults(DEFAULTS_CMND, yyvsp[-1].member, yyvsp[0].defaults); } break; case 18: -#line 237 "gram.y" +#line 235 "gram.y" { list_append(yyvsp[-2].defaults, yyvsp[0].defaults); yyval.defaults = yyvsp[-2].defaults; } break; case 19: -#line 243 "gram.y" +#line 241 "gram.y" { yyval.defaults = new_default(yyvsp[0].string, NULL, TRUE); } break; case 20: -#line 246 "gram.y" +#line 244 "gram.y" { yyval.defaults = new_default(yyvsp[0].string, NULL, FALSE); } break; case 21: -#line 249 "gram.y" +#line 247 "gram.y" { yyval.defaults = new_default(yyvsp[-2].string, yyvsp[0].string, TRUE); } break; case 22: -#line 252 "gram.y" +#line 250 "gram.y" { yyval.defaults = new_default(yyvsp[-2].string, yyvsp[0].string, '+'); } break; case 23: -#line 255 "gram.y" +#line 253 "gram.y" { yyval.defaults = new_default(yyvsp[-2].string, yyvsp[0].string, '-'); } break; case 25: -#line 261 "gram.y" +#line 259 "gram.y" { list_append(yyvsp[-2].privilege, yyvsp[0].privilege); yyval.privilege = yyvsp[-2].privilege; } break; case 26: -#line 267 "gram.y" +#line 265 "gram.y" { struct privilege *p = emalloc(sizeof(*p)); list2tq(&p->hostlist, yyvsp[-2].member); @@ -1153,51 +1161,51 @@ case 26: } break; case 27: -#line 277 "gram.y" +#line 275 "gram.y" { yyval.member = yyvsp[0].member; yyval.member->negated = FALSE; } break; case 28: -#line 281 "gram.y" +#line 279 "gram.y" { yyval.member = yyvsp[0].member; yyval.member->negated = TRUE; } break; case 29: -#line 287 "gram.y" +#line 285 "gram.y" { yyval.member = new_member(yyvsp[0].string, ALIAS); } break; case 30: -#line 290 "gram.y" +#line 288 "gram.y" { yyval.member = new_member(NULL, ALL); } break; case 31: -#line 293 "gram.y" +#line 291 "gram.y" { yyval.member = new_member(yyvsp[0].string, NETGROUP); } break; case 32: -#line 296 "gram.y" +#line 294 "gram.y" { yyval.member = new_member(yyvsp[0].string, NTWKADDR); } break; case 33: -#line 299 "gram.y" +#line 297 "gram.y" { yyval.member = new_member(yyvsp[0].string, WORD); } break; case 35: -#line 305 "gram.y" +#line 303 "gram.y" { list_append(yyvsp[-2].cmndspec, yyvsp[0].cmndspec); #ifdef HAVE_SELINUX @@ -1215,8 +1223,10 @@ case 35: if (yyvsp[0].cmndspec->tags.setenv == UNSPEC && yyvsp[0].cmndspec->prev->tags.setenv != IMPLIED) yyvsp[0].cmndspec->tags.setenv = yyvsp[0].cmndspec->prev->tags.setenv; - if (yyvsp[0].cmndspec->tags.transcript == UNSPEC) - yyvsp[0].cmndspec->tags.transcript = yyvsp[0].cmndspec->prev->tags.transcript; + if (yyvsp[0].cmndspec->tags.log_input == UNSPEC) + yyvsp[0].cmndspec->tags.log_input = yyvsp[0].cmndspec->prev->tags.log_input; + if (yyvsp[0].cmndspec->tags.log_output == UNSPEC) + yyvsp[0].cmndspec->tags.log_output = yyvsp[0].cmndspec->prev->tags.log_output; if ((tq_empty(&yyvsp[0].cmndspec->runasuserlist) && tq_empty(&yyvsp[0].cmndspec->runasgrouplist)) && (!tq_empty(&yyvsp[0].cmndspec->prev->runasuserlist) || @@ -1354,71 +1364,84 @@ break; case 51: #line 428 "gram.y" { - yyval.tag.nopasswd = yyval.tag.noexec = yyval.tag.setenv = yyval.tag.transcript = UNSPEC; + yyval.tag.nopasswd = yyval.tag.noexec = yyval.tag.setenv = + yyval.tag.log_input = yyval.tag.log_output = UNSPEC; } break; case 52: -#line 431 "gram.y" +#line 432 "gram.y" { yyval.tag.nopasswd = TRUE; } break; case 53: -#line 434 "gram.y" +#line 435 "gram.y" { yyval.tag.nopasswd = FALSE; } break; case 54: -#line 437 "gram.y" +#line 438 "gram.y" { yyval.tag.noexec = TRUE; } break; case 55: -#line 440 "gram.y" +#line 441 "gram.y" { yyval.tag.noexec = FALSE; } break; case 56: -#line 443 "gram.y" +#line 444 "gram.y" { yyval.tag.setenv = TRUE; } break; case 57: -#line 446 "gram.y" +#line 447 "gram.y" { yyval.tag.setenv = FALSE; } break; case 58: -#line 449 "gram.y" +#line 450 "gram.y" { - yyval.tag.transcript = TRUE; + yyval.tag.log_input = TRUE; } break; case 59: -#line 452 "gram.y" +#line 453 "gram.y" { - yyval.tag.transcript = FALSE; + yyval.tag.log_input = FALSE; } break; case 60: -#line 457 "gram.y" +#line 456 "gram.y" { - yyval.member = new_member(NULL, ALL); + yyval.tag.log_output = TRUE; } break; case 61: -#line 460 "gram.y" +#line 459 "gram.y" { - yyval.member = new_member(yyvsp[0].string, ALIAS); + yyval.tag.log_output = FALSE; } break; case 62: -#line 463 "gram.y" +#line 464 "gram.y" +{ + yyval.member = new_member(NULL, ALL); + } +break; +case 63: +#line 467 "gram.y" +{ + yyval.member = new_member(yyvsp[0].string, ALIAS); + } +break; +case 64: +#line 470 "gram.y" { struct sudo_command *c = emalloc(sizeof(*c)); c->cmnd = yyvsp[0].command.cmnd; @@ -1426,8 +1449,8 @@ case 62: yyval.member = new_member((char *)c, COMMAND); } break; -case 65: -#line 475 "gram.y" +case 67: +#line 482 "gram.y" { char *s; if ((s = alias_add(yyvsp[-2].string, HOSTALIAS, yyvsp[0].member)) != NULL) { @@ -1436,15 +1459,15 @@ case 65: } } break; -case 67: -#line 485 "gram.y" +case 69: +#line 492 "gram.y" { list_append(yyvsp[-2].member, yyvsp[0].member); yyval.member = yyvsp[-2].member; } break; -case 70: -#line 495 "gram.y" +case 72: +#line 502 "gram.y" { char *s; if ((s = alias_add(yyvsp[-2].string, CMNDALIAS, yyvsp[0].member)) != NULL) { @@ -1453,15 +1476,15 @@ case 70: } } break; -case 72: -#line 505 "gram.y" +case 74: +#line 512 "gram.y" { list_append(yyvsp[-2].member, yyvsp[0].member); yyval.member = yyvsp[-2].member; } break; -case 75: -#line 515 "gram.y" +case 77: +#line 522 "gram.y" { char *s; if ((s = alias_add(yyvsp[-2].string, RUNASALIAS, yyvsp[0].member)) != NULL) { @@ -1470,8 +1493,8 @@ case 75: } } break; -case 78: -#line 528 "gram.y" +case 80: +#line 535 "gram.y" { char *s; if ((s = alias_add(yyvsp[-2].string, USERALIAS, yyvsp[0].member)) != NULL) { @@ -1480,97 +1503,97 @@ case 78: } } break; -case 80: -#line 538 "gram.y" +case 82: +#line 545 "gram.y" { list_append(yyvsp[-2].member, yyvsp[0].member); yyval.member = yyvsp[-2].member; } break; -case 81: -#line 544 "gram.y" +case 83: +#line 551 "gram.y" { yyval.member = yyvsp[0].member; yyval.member->negated = FALSE; } break; -case 82: -#line 548 "gram.y" +case 84: +#line 555 "gram.y" { yyval.member = yyvsp[0].member; yyval.member->negated = TRUE; } break; -case 83: -#line 554 "gram.y" +case 85: +#line 561 "gram.y" { yyval.member = new_member(yyvsp[0].string, ALIAS); } break; -case 84: -#line 557 "gram.y" +case 86: +#line 564 "gram.y" { yyval.member = new_member(NULL, ALL); } break; -case 85: -#line 560 "gram.y" +case 87: +#line 567 "gram.y" { yyval.member = new_member(yyvsp[0].string, NETGROUP); } break; -case 86: -#line 563 "gram.y" +case 88: +#line 570 "gram.y" { yyval.member = new_member(yyvsp[0].string, USERGROUP); } break; -case 87: -#line 566 "gram.y" +case 89: +#line 573 "gram.y" { yyval.member = new_member(yyvsp[0].string, WORD); } break; -case 89: -#line 572 "gram.y" +case 91: +#line 579 "gram.y" { list_append(yyvsp[-2].member, yyvsp[0].member); yyval.member = yyvsp[-2].member; } break; -case 90: -#line 578 "gram.y" +case 92: +#line 585 "gram.y" { yyval.member = yyvsp[0].member; yyval.member->negated = FALSE; } break; -case 91: -#line 582 "gram.y" +case 93: +#line 589 "gram.y" { yyval.member = yyvsp[0].member; yyval.member->negated = TRUE; } break; -case 92: -#line 588 "gram.y" +case 94: +#line 595 "gram.y" { yyval.member = new_member(yyvsp[0].string, ALIAS); } break; -case 93: -#line 591 "gram.y" +case 95: +#line 598 "gram.y" { yyval.member = new_member(NULL, ALL); } break; -case 94: -#line 594 "gram.y" +case 96: +#line 601 "gram.y" { yyval.member = new_member(yyvsp[0].string, WORD); } break; -#line 1526 "y.tab.c" +#line 1545 "y.tab.c" } yyssp -= yym; yystate = *yyssp; diff --git a/gram.h b/gram.h index 1d1c6d590..2bec42054 100644 --- a/gram.h +++ b/gram.h @@ -16,17 +16,19 @@ #define EXEC 272 #define SETENV 273 #define NOSETENV 274 -#define TRANSCRIPT 275 -#define NOTRANSCRIPT 276 -#define ALL 277 -#define COMMENT 278 -#define HOSTALIAS 279 -#define CMNDALIAS 280 -#define USERALIAS 281 -#define RUNASALIAS 282 -#define ERROR 283 -#define TYPE 284 -#define ROLE 285 +#define LOG_INPUT 275 +#define NOLOG_INPUT 276 +#define LOG_OUTPUT 277 +#define NOLOG_OUTPUT 278 +#define ALL 279 +#define COMMENT 280 +#define HOSTALIAS 281 +#define CMNDALIAS 282 +#define USERALIAS 283 +#define RUNASALIAS 284 +#define ERROR 285 +#define TYPE 286 +#define ROLE 287 #ifndef YYSTYPE_DEFINED #define YYSTYPE_DEFINED typedef union { diff --git a/gram.y b/gram.y index 561862a3e..1c669aab5 100644 --- a/gram.y +++ b/gram.y @@ -142,8 +142,10 @@ yyerror(s) %token EXEC /* don't preload dummy execve() */ %token SETENV /* user may set environment for cmnd */ %token NOSETENV /* user may not set environment */ -%token TRANSCRIPT /* log a transcript of the cmnd */ -%token NOTRANSCRIPT /* don't log a transcript of the cmnd */ +%token LOG_INPUT /* log user's cmnd input */ +%token NOLOG_INPUT /* don't log user's cmnd input */ +%token LOG_OUTPUT /* log cmnd output */ +%token NOLOG_OUTPUT /* don't log cmnd output */ %token ALL /* ALL keyword */ %token COMMENT /* comment and/or carriage return */ %token HOSTALIAS /* Host_Alias keyword */ @@ -315,8 +317,10 @@ cmndspeclist : cmndspec if ($3->tags.setenv == UNSPEC && $3->prev->tags.setenv != IMPLIED) $3->tags.setenv = $3->prev->tags.setenv; - if ($3->tags.transcript == UNSPEC) - $3->tags.transcript = $3->prev->tags.transcript; + if ($3->tags.log_input == UNSPEC) + $3->tags.log_input = $3->prev->tags.log_input; + if ($3->tags.log_output == UNSPEC) + $3->tags.log_output = $3->prev->tags.log_output; if ((tq_empty(&$3->runasuserlist) && tq_empty(&$3->runasgrouplist)) && (!tq_empty(&$3->prev->runasuserlist) || @@ -422,7 +426,8 @@ runaslist : userlist { ; cmndtag : /* empty */ { - $$.nopasswd = $$.noexec = $$.setenv = $$.transcript = UNSPEC; + $$.nopasswd = $$.noexec = $$.setenv = + $$.log_input = $$.log_output = UNSPEC; } | cmndtag NOPASSWD { $$.nopasswd = TRUE; @@ -442,11 +447,17 @@ cmndtag : /* empty */ { | cmndtag NOSETENV { $$.setenv = FALSE; } - | cmndtag TRANSCRIPT { - $$.transcript = TRUE; + | cmndtag LOG_INPUT { + $$.log_input = TRUE; } - | cmndtag NOTRANSCRIPT { - $$.transcript = FALSE; + | cmndtag NOLOG_INPUT { + $$.log_input = FALSE; + } + | cmndtag LOG_OUTPUT { + $$.log_output = TRUE; + } + | cmndtag NOLOG_OUTPUT { + $$.log_output = FALSE; } ; diff --git a/iolog.c b/iolog.c new file mode 100644 index 000000000..4ac05b005 --- /dev/null +++ b/iolog.c @@ -0,0 +1,358 @@ +/* + * Copyright (c) 2009-2010 Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include +#include +#include +#include +#include +#ifdef STDC_HEADERS +# include +# include +#else +# ifdef HAVE_STDLIB_H +# include +# endif +#endif /* STDC_HEADERS */ +#ifdef HAVE_STRING_H +# include +#else +# ifdef HAVE_STRINGS_H +# include +# endif +#endif /* HAVE_STRING_H */ +#ifdef HAVE_UNISTD_H +# include +#endif /* HAVE_UNISTD_H */ +#if TIME_WITH_SYS_TIME +# include +#endif +#include +#include +#include +#include +#include +#ifdef HAVE_ZLIB +# include +#endif + +#include "sudo.h" + +union io_fd { + FILE *f; +#ifdef HAVE_ZLIB + gzFile g; +#endif + void *v; +}; + +struct script_buf { + int len; /* buffer length (how much read in) */ + int off; /* write position (how much already consumed) */ + char buf[16 * 1024]; +}; + +#define IOFD_STDIN 0 +#define IOFD_STDOUT 1 +#define IOFD_STDERR 2 +#define IOFD_TTYIN 3 +#define IOFD_TTYOUT 4 +#define IOFD_TIMING 5 +#define IOFD_MAX 6 + +static sigset_t ttyblock; +static struct timeval last_time; +static union io_fd io_fds[IOFD_MAX]; + +void +io_nextid(void) +{ + 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_IO_LOGDIR if it doesn't already exist. + */ + if (stat(_PATH_SUDO_IO_LOGDIR, &sb) != 0) { + if (mkdir(_PATH_SUDO_IO_LOGDIR, S_IRWXU) != 0) + log_error(USE_ERRNO, "Can't mkdir %s", _PATH_SUDO_IO_LOGDIR); + } else if (!S_ISDIR(sb.st_mode)) { + log_error(0, "%s exists but is not a directory (0%o)", + _PATH_SUDO_IO_LOGDIR, (unsigned int) sb.st_mode); + } + + /* + * Open sequence file + */ + len = snprintf(pathbuf, sizeof(pathbuf), "%s/seq", _PATH_SUDO_IO_LOGDIR); + 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(char *pathbuf, size_t pathsize) +{ + 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, pathsize, "%s/%c%c/%c%c/%c%c", _PATH_SUDO_IO_LOGDIR, + 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 >= pathsize) { + errno = ENAMETOOLONG; + log_error(USE_ERRNO, "%s/%s", _PATH_SUDO_IO_LOGDIR, 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] = '/'; + } + + return(len); +} + +static void * +open_io_fd(char *pathbuf, int len, const char *suffix, int docompress) +{ + void *vfd = NULL; + int fd; + + pathbuf[len] = '\0'; + strlcat(pathbuf, suffix, PATH_MAX); + fd = open(pathbuf, O_CREAT|O_EXCL|O_WRONLY, S_IRUSR|S_IWUSR); + if (fd != -1) { + fcntl(fd, F_SETFD, FD_CLOEXEC); +#ifdef HAVE_ZLIB + if (docompress) + vfd = gzdopen(fd, "w"); + else +#endif + vfd = fdopen(fd, "w"); + } + return vfd; +} + +int +io_log_open() +{ + char pathbuf[PATH_MAX]; + FILE *io_logfile; + int len; + + if (!def_log_input && !def_log_output) + return FALSE; + + /* + * 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, sizeof(pathbuf)); + if (len == -1) + return -1; + + if (mkdir(pathbuf, S_IRUSR|S_IWUSR|S_IXUSR) != 0) + log_error(USE_ERRNO, "Can't mkdir %s", pathbuf); + + /* + * We create 7 files: a log file, a timing file and 5 for input/output. + */ + io_logfile = open_io_fd(pathbuf, len, "/log", FALSE); + if (io_logfile == NULL) + log_error(USE_ERRNO, "Can't create %s", pathbuf); + + io_fds[IOFD_TIMING].v = open_io_fd(pathbuf, len, "/timing", def_compress_io); + if (io_fds[IOFD_TIMING].v == NULL) + log_error(USE_ERRNO, "Can't create %s", pathbuf); + + io_fds[IOFD_TTYIN].v = open_io_fd(pathbuf, len, "/ttyin", def_compress_io); + if (io_fds[IOFD_TTYIN].v == NULL) + log_error(USE_ERRNO, "Can't create %s", pathbuf); + + io_fds[IOFD_TTYOUT].v = open_io_fd(pathbuf, len, "/ttyout", def_compress_io); + if (io_fds[IOFD_TTYOUT].v == NULL) + log_error(USE_ERRNO, "Can't create %s", pathbuf); + + io_fds[IOFD_STDIN].v = open_io_fd(pathbuf, len, "/stdin", def_compress_io); + if (io_fds[IOFD_STDIN].v == NULL) + log_error(USE_ERRNO, "Can't create %s", pathbuf); + + io_fds[IOFD_STDOUT].v = open_io_fd(pathbuf, len, "/stdout", def_compress_io); + if (io_fds[IOFD_STDOUT].v == NULL) + log_error(USE_ERRNO, "Can't create %s", pathbuf); + + io_fds[IOFD_STDERR].v = open_io_fd(pathbuf, len, "/stderr", def_compress_io); + if (io_fds[IOFD_STDERR].v == NULL) + log_error(USE_ERRNO, "Can't create %s", pathbuf); + + /* So we can block tty-generated signals */ + sigemptyset(&ttyblock); + sigaddset(&ttyblock, SIGINT); + sigaddset(&ttyblock, SIGQUIT); + sigaddset(&ttyblock, SIGTSTP); + sigaddset(&ttyblock, SIGTTIN); + sigaddset(&ttyblock, SIGTTOU); + + gettimeofday(&last_time, NULL); + + /* XXX - log more stuff? window size? environment? */ + fprintf(io_logfile, "%ld:%s:%s:%s:%s\n", last_time.tv_sec, user_name, + runas_pw->pw_name, runas_gr ? runas_gr->gr_name : "", user_tty); + fprintf(io_logfile, "%s\n", user_cwd); + fprintf(io_logfile, "%s%s%s\n", user_cmnd, user_args ? " " : "", + user_args ? user_args : ""); + fclose(io_logfile); + + return TRUE; +} + +void +io_log_close() +{ + int i; + + for (i = 0; i < IOFD_MAX; i++) { + if (io_fds[i].v == NULL) + continue; +#ifdef HAVE_ZLIB + if (def_compress_io) + gzclose(io_fds[i].g); + else +#endif + fclose(io_fds[i].f); + } +} + +static int +log_io(const char *buf, unsigned int len, int idx) +{ + struct timeval now, tv; + sigset_t omask; + + gettimeofday(&now, NULL); + + sigprocmask(SIG_BLOCK, &ttyblock, &omask); + +#ifdef HAVE_ZLIB + if (def_compress_io) + gzwrite(io_fds[idx].g, buf, len); + else +#endif + fwrite(buf, 1, len, io_fds[idx].f); + timersub(&now, &last_time, &tv); +#ifdef HAVE_ZLIB + if (def_compress_io) + gzprintf(io_fds[IOFD_TIMING].g, "%d %f %d\n", idx, + tv.tv_sec + ((double)tv.tv_usec / 1000000), len); + else +#endif + fprintf(io_fds[IOFD_TIMING].f, "%d %f %d\n", idx, + tv.tv_sec + ((double)tv.tv_usec / 1000000), len); + last_time.tv_sec = now.tv_sec; + last_time.tv_usec = now.tv_usec; + + sigprocmask(SIG_SETMASK, &omask, NULL); + + return TRUE; +} + +int +log_ttyin(const char *buf, unsigned int len) +{ + return log_io(buf, len, IOFD_TTYIN); +} + +int +log_ttyout(const char *buf, unsigned int len) +{ + return log_io(buf, len, IOFD_TTYOUT); +} + +int +log_stdin(const char *buf, unsigned int len) +{ + return log_io(buf, len, IOFD_STDIN); +} + +int +log_stdout(const char *buf, unsigned int len) +{ + return log_io(buf, len, IOFD_STDOUT); +} + +int +log_stderr(const char *buf, unsigned int len) +{ + return log_io(buf, len, IOFD_STDERR); +} diff --git a/parse.c b/parse.c index da026997d..63dedd479 100644 --- a/parse.c +++ b/parse.c @@ -250,8 +250,10 @@ sudo_file_lookup(nss, validated, pwflag) def_noexec = tags->noexec; if (tags->setenv != UNSPEC) def_setenv = tags->setenv; - if (tags->transcript != UNSPEC) - def_transcript = tags->transcript; + if (tags->log_input != UNSPEC) + def_log_input = tags->log_input; + if (tags->log_output != UNSPEC) + def_log_output = tags->log_output; } } else if (match == DENY) { SET(validated, VALIDATE_NOT_OK); @@ -293,10 +295,15 @@ sudo_file_append_cmnd(cs, tags, lbuf) "PASSWD: ", NULL); tags->nopasswd = cs->tags.nopasswd; } - if (TAG_CHANGED(transcript)) { - lbuf_append(lbuf, cs->tags.transcript ? "SCRIPT: " : - "NOSCRIPT: ", NULL); - tags->transcript = cs->tags.transcript; + if (TAG_CHANGED(log_input)) { + lbuf_append(lbuf, cs->tags.log_input ? "LOG_INPUT: " : + "NOLOG_INPUT: ", NULL); + tags->log_input = cs->tags.log_input; + } + if (TAG_CHANGED(log_output)) { + lbuf_append(lbuf, cs->tags.log_output ? "LOG_OUTPUT: " : + "NOLOG_OUTPUT: ", NULL); + tags->log_output = cs->tags.log_output; } m = cs->cmnd; print_member(lbuf, m->name, m->type, m->negated, @@ -321,7 +328,8 @@ sudo_file_display_priv_short(pw, us, lbuf) tags.noexec = UNSPEC; tags.setenv = UNSPEC; tags.nopasswd = UNSPEC; - tags.transcript = UNSPEC; + tags.log_input = UNSPEC; + tags.log_output = UNSPEC; lbuf_append(lbuf, " ", NULL); tq_foreach_fwd(&priv->cmndlist, cs) { if (cs != tq_first(&priv->cmndlist)) @@ -375,7 +383,8 @@ sudo_file_display_priv_long(pw, us, lbuf) tags.noexec = UNSPEC; tags.setenv = UNSPEC; tags.nopasswd = UNSPEC; - tags.transcript = UNSPEC; + tags.log_input = UNSPEC; + tags.log_output = UNSPEC; lbuf_append(lbuf, "\nSudoers entry:\n", NULL); tq_foreach_fwd(&priv->cmndlist, cs) { lbuf_append(lbuf, " RunAsUsers: ", NULL); diff --git a/parse.h b/parse.h index af8179cf5..9d881f463 100644 --- a/parse.h +++ b/parse.h @@ -40,10 +40,11 @@ struct sudo_command { * Possible valus: TRUE, FALSE, UNSPEC. */ struct cmndtag { - __signed char nopasswd; - __signed char noexec; - __signed char setenv; - __signed char transcript; + __signed int nopasswd: 3; + __signed int noexec: 3; + __signed int setenv: 3; + __signed int log_input: 3; + __signed int log_output: 3; }; /* diff --git a/pathnames.h.in b/pathnames.h.in index ccacfb49f..656aca482 100644 --- a/pathnames.h.in +++ b/pathnames.h.in @@ -67,12 +67,12 @@ #endif /* _PATH_SUDO_TIMEDIR */ /* - * Where to put the session files. Defaults to /var/log/sudo-session, - * /var/adm/sudo-session or /usr/adm/sudo-session depending on what exists. + * Where to put the I/O log files. Defaults to /var/log/sudo-io, + * /var/adm/sudo-io or /usr/adm/sudo-io depending on what exists. */ -#ifndef _PATH_SUDO_TRANSCRIPT -#undef _PATH_SUDO_TRANSCRIPT -#endif /* _PATH_SUDO_TRANSCRIPT */ +#ifndef _PATH_SUDO_IO_LOGDIR +#undef _PATH_SUDO_IO_LOGDIR +#endif /* _PATH_SUDO_IO_LOGDIR */ /* * Where to put the sudo log file when logging to a file. Defaults to diff --git a/pty.c b/pty.c index 7d4f15a9d..65f1afd12 100644 --- a/pty.c +++ b/pty.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009 Todd C. Miller + * Copyright (c) 2009-2010 Todd C. Miller * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above diff --git a/script.c b/script.c deleted file mode 100644 index 67087d1f6..000000000 --- a/script.c +++ /dev/null @@ -1,1130 +0,0 @@ -/* - * Copyright (c) 2009 Todd C. Miller - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include - -#include -#include -#include -#include -#include -#include -#ifdef HAVE_TERMIOS_H -# include -#else -# ifdef HAVE_TERMIO_H -# include -# else -# include -# endif /* HAVE_TERMIO_H */ -#endif /* HAVE_TERMIOS_H */ -#include -#ifdef HAVE_SYS_SELECT_H -#include -#endif /* HAVE_SYS_SELECT_H */ -#include -#ifdef STDC_HEADERS -# include -# include -#else -# ifdef HAVE_STDLIB_H -# include -# endif -#endif /* STDC_HEADERS */ -#ifdef HAVE_STRING_H -# if defined(HAVE_MEMORY_H) && !defined(STDC_HEADERS) -# include -# endif -# include -#else -# ifdef HAVE_STRINGS_H -# include -# endif -#endif /* HAVE_STRING_H */ -#ifdef HAVE_UNISTD_H -# include -#endif /* HAVE_UNISTD_H */ -#if TIME_WITH_SYS_TIME -# include -#endif -#include -#include -#include -#include -#include -#ifdef HAVE_SELINUX -# include -#endif -#ifdef HAVE_ZLIB -# include -#endif - -#include "sudo.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 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 ttyout; - -static sig_atomic_t alive = 1; -static sig_atomic_t recvsig = 0; -static sig_atomic_t ttymode = TERM_COOKED; -static sig_atomic_t tty_initialized = 0; - -static sigset_t ttyblock; - -static pid_t ppgrp, child; -static int child_status; -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 >= (unsigned long)2176782336) - 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] = '/'; - } - - return(len); -} - -void -script_setup() -{ - 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 */ - - 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); -} - -int -script_duplow(fd) - int fd; -{ - 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++; - } - return(fd); -} - -/* Update output and timing files. */ -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; -{ - struct timeval tv; - 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; - - sigprocmask(SIG_SETMASK, &omask, NULL); -} - -static void -check_foreground() -{ - foreground = tcgetpgrp(script_fds[SFD_USERTTY]) == ppgrp; - if (foreground && !tty_initialized) { - if (term_copy(script_fds[SFD_USERTTY], script_fds[SFD_SLAVE], ttyout)) { - tty_initialized = 1; - sync_winsize(script_fds[SFD_USERTTY], script_fds[SFD_SLAVE]); - } - } -} - -/* - * Suspend sudo if the underlying command is suspended. - * 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; -{ - sigaction_t sa, osa; - int n, oldmode = ttymode, rval = 0; - - switch (signo) { - case SIGTTOU: - case SIGTTIN: - /* - * If we are the foreground process, just resume the child. - * Otherwise, re-send the signal with the handler disabled. - */ - if (!foreground) - check_foreground(); - if (foreground) { - if (ttymode != TERM_RAW) { - do { - n = term_raw(script_fds[SFD_USERTTY], !ttyout, 0); - } while (!n && errno == EINTR); - ttymode = TERM_RAW; - } - rval = SIGUSR1; /* resume child in foreground */ - break; - } - ttymode = TERM_RAW; - /* FALLTHROUGH */ - case SIGTSTP: - /* Flush any remaining output to master tty. */ - flush_output(output, then, now, ofile, tfile); - - /* Restore original tty mode before suspending. */ - if (oldmode != TERM_COOKED) { - do { - n = term_restore(script_fds[SFD_USERTTY], 0); - } while (!n && errno == EINTR); - } - - /* Suspend self and continue child when we resume. */ - sa.sa_handler = SIG_DFL; - sigaction(signo, &sa, &osa); -#ifdef SCRIPT_DEBUG - warningx("kill parent %d", signo); -#endif - killpg(ppgrp, signo); - - /* Check foreground/background status on resume. */ - check_foreground(); - - /* - * Only modify term if we are foreground process and either - * the old tty mode was not cooked or child got SIGTT{IN,OU} - */ -#ifdef SCRIPT_DEBUG - warningx("parent is in %sground, ttymode %d -> %d", - foreground ? "fore" : "back", oldmode, ttymode); -#endif - - if (ttymode != TERM_COOKED) { - if (foreground) { - /* Set raw/cbreak mode. */ - do { - n = term_raw(script_fds[SFD_USERTTY], !ttyout, - ttymode == TERM_CBREAK); - } while (!n && errno == EINTR); - } else { - /* background process, no access to tty. */ - ttymode = TERM_COOKED; - } - } - - sigaction(signo, &osa, NULL); - rval = ttymode == TERM_RAW ? SIGUSR1 : SIGUSR2; - break; - } - - return(rval); -} - -/* - * This is a little bit tricky due to how POSIX job control works and - * we fact that we have two different controlling terminals to deal with. - * There are three processes: - * 1) parent, which forks a child and does all the I/O passing. - * Handles job control signals send by its child to bridge the - * two sessions (and ttys). - * 2) child, creates a new session so it can receive notification of - * tty stop signals (SIGTSTP, SIGTTIN, SIGTTOU). Waits for the - * command to stop or die and passes back tty stop signals to parent - * so job control works in the user's shell. - * 3) grandchild, executes the actual command with the pty slave as its - * controlling tty, belongs to child's session but has its own pgrp. - */ -int -script_execv(path, argv) - char *path; - char *argv[]; -{ - sigaction_t sa; - struct script_buf input, output; - struct timeval now, then; - int n, nready, exitcode = 1; - int relaysig, sv[2]; - fd_set *fdsr, *fdsw; - FILE *idfile; - union script_fd ofile, tfile; - int rbac_enabled = 0; - -#ifdef HAVE_SELINUX - rbac_enabled = is_selinux_enabled() > 0 && user_role != NULL; - if (rbac_enabled) { - selinux_prefork(user_role, user_type, script_fds[SFD_SLAVE]); - /* Re-open slave fd after it has been relabeled */ - 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); - } -#endif - - /* Are we the foreground process? */ - ppgrp = getpgrp(); /* so child can pass signals back to us */ - foreground = tcgetpgrp(script_fds[SFD_USERTTY]) == ppgrp; - - /* So we can block tty-generated signals */ - sigemptyset(&ttyblock); - sigaddset(&ttyblock, SIGINT); - sigaddset(&ttyblock, SIGQUIT); - sigaddset(&ttyblock, SIGTSTP); - sigaddset(&ttyblock, SIGTTIN); - sigaddset(&ttyblock, SIGTTOU); - - /* Setup signal handlers window size changes and child stop/exit */ - zero_bytes(&sa, sizeof(sa)); - sigemptyset(&sa.sa_mask); - sa.sa_flags = SA_RESTART; - sa.sa_handler = sigwinch; - sigaction(SIGWINCH, &sa, NULL); - - /* XXX - now get command status via sv (still need to detect child death) */ - sa.sa_handler = sigchild; - sigaction(SIGCHLD, &sa, NULL); - - /* Ignore SIGPIPE from other end of socketpair. */ - sa.sa_handler = SIG_IGN; - sigaction(SIGPIPE, &sa, NULL); - - /* Signals to relay from parent to child. */ - sa.sa_flags = 0; /* do not restart syscalls for these */ - sa.sa_handler = handler; - sigaction(SIGHUP, &sa, NULL); - sigaction(SIGTERM, &sa, NULL); - sigaction(SIGINT, &sa, NULL); - sigaction(SIGQUIT, &sa, NULL); - sigaction(SIGTSTP, &sa, NULL); -#if 0 /* XXX - keep? */ - sigaction(SIGTTIN, &sa, NULL); - sigaction(SIGTTOU, &sa, NULL); -#endif - - /* If stdout is not a tty we handle post-processing differently. */ - ttyout = isatty(STDOUT_FILENO); - - /* - * 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 (foreground) { - /* Copy terminal attrs from user tty -> pty slave. */ - if (term_copy(script_fds[SFD_USERTTY], script_fds[SFD_SLAVE], ttyout)) { - tty_initialized = 1; - sync_winsize(script_fds[SFD_USERTTY], script_fds[SFD_SLAVE]); - } - - /* Start out in raw mode is stdout is a tty. */ - ttymode = ttyout ? TERM_RAW : TERM_CBREAK; - do { - n = term_raw(script_fds[SFD_USERTTY], !ttyout, - ttymode == TERM_CBREAK); - } while (!n && errno == EINTR); - if (!n) - log_error(USE_ERRNO, "Can't set terminal to raw mode"); - } - - /* - * Child will run the command in the pty, parent will pass data - * to and from pty. - */ - child = fork(); - switch (child) { - case -1: - log_error(USE_ERRNO, "fork"); - break; - case 0: - close(sv[0]); - script_child(path, argv, sv[1], rbac_enabled); - /* NOTREACHED */ - break; - } - 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; - (void) fcntl(script_fds[SFD_MASTER], F_SETFL, n); - } - n = fcntl(script_fds[SFD_USERTTY], F_GETFL, 0); - if (n != -1) { - n |= O_NONBLOCK; - (void) fcntl(script_fds[SFD_USERTTY], F_SETFL, n); - } - n = fcntl(STDOUT_FILENO, F_GETFL, 0); - if (n != -1) { - n |= O_NONBLOCK; - (void) fcntl(STDOUT_FILENO, F_SETFL, n); - } - - /* - * 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). - */ - fdsr = (fd_set *)emalloc2(howmany(sv[0] + 1, NFDBITS), sizeof(fd_mask)); - fdsw = (fd_set *)emalloc2(howmany(sv[0] + 1, NFDBITS), sizeof(fd_mask)); - zero_bytes(&input, sizeof(input)); - zero_bytes(&output, sizeof(output)); - while (alive) { - /* XXX - racey */ - if (!relaysig && recvsig != SIGCHLD) { - relaysig = recvsig; - recvsig = 0; - } - - if (input.off == input.len) - input.off = input.len = 0; - 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)); - - if (ttymode == TERM_RAW && input.len != sizeof(input.buf)) - FD_SET(script_fds[SFD_USERTTY], fdsr); - if (output.len != sizeof(output.buf)) - FD_SET(script_fds[SFD_MASTER], fdsr); - if (output.len > output.off) - FD_SET(STDOUT_FILENO, fdsw); - if (input.len > input.off) - FD_SET(script_fds[SFD_MASTER], fdsw); - FD_SET(sv[0], fdsr); - if (relaysig) - FD_SET(sv[0], fdsw); - - nready = select(sv[0] + 1, fdsr, fdsw, NULL, NULL); - if (nready == -1) { - if (errno == EINTR) - continue; - log_error(USE_ERRNO, "select failed"); - } - if (FD_ISSET(sv[0], fdsr)) { - /* read child status */ - n = read(sv[0], &child_status, sizeof(child_status)); - if (n == -1) { - if (errno == EINTR) - continue; - if (errno != EAGAIN) - break; - } else if (n != sizeof(child_status)) { - break; /* EOF? */ - } - if (WIFSTOPPED(child_status)) { - /* Suspend parent and tell child how to resume on return. */ -#ifdef SCRIPT_DEBUG - 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; - 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)); - relaysig = 0; - if (n == -1) { - if (errno == EINTR) - continue; - if (errno != EAGAIN) - break; - } else if (n != sizeof(relaysig)) { - break; /* should not happen */ - } - } - if (FD_ISSET(script_fds[SFD_USERTTY], fdsr)) { - n = read(script_fds[SFD_USERTTY], input.buf + input.len, - sizeof(input.buf) - input.len); - if (n == -1) { - if (errno == EINTR) - continue; - if (errno != EAGAIN) - break; - } else { - if (n == 0) - break; /* got EOF */ - input.len += n; - } - } - if (FD_ISSET(script_fds[SFD_MASTER], fdsw)) { - n = write(script_fds[SFD_MASTER], input.buf + input.off, - input.len - input.off); - if (n == -1) { - if (errno == EINTR) - continue; - if (errno != EAGAIN) - break; - } else { - input.off += n; - } - } - 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) { - if (errno == EINTR) - continue; - if (errno != EAGAIN) - break; - } else { - if (n == 0) - break; /* got EOF */ - - /* Update output and timing files. */ - log_output(output.buf + output.len, n, &then, &now, ofile, tfile); - output.len += n; - } - } - if (FD_ISSET(STDOUT_FILENO, fdsw)) { - n = write(STDOUT_FILENO, output.buf + output.off, - output.len - output.off); - if (n == -1) { - if (errno == EINTR) - continue; - if (errno != EAGAIN) - break; - } else { - output.off += n; - } - } - } - - /* Flush any remaining output to stdout (already updated output file). */ - n = fcntl(STDOUT_FILENO, F_GETFL, 0); - if (n != -1) { - 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); - } - -#ifdef HAVE_STRSIGNAL - if (WIFSIGNALED(child_status)) { - int signo = WTERMSIG(child_status); - if (signo && signo != SIGINT && signo != SIGPIPE) { - char *reason = strsignal(signo); - write(STDOUT_FILENO, reason, strlen(reason)); - if (WCOREDUMP(child_status)) - write(STDOUT_FILENO, " (core dumped)", 14); - write(STDOUT_FILENO, "\n", 1); - } - } -#endif - - do { - n = term_restore(script_fds[SFD_USERTTY], 0); - } while (!n && errno == EINTR); - - exit(exitcode); -} - -void -script_child(path, argv, backchannel, rbac_enabled) - char *path; - char *argv[]; - int backchannel; - int rbac_enabled; -{ - sigaction_t sa; - pid_t pid, self = getpid(); - int nread, signo, status; -#ifndef TIOCSCTTY - int n; -#endif - - 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. */ - zero_bytes(&sa, sizeof(sa)); - sigemptyset(&sa.sa_mask); - sa.sa_flags = SA_RESTART; - sa.sa_handler = SIG_DFL; - sigaction(SIGCONT, &sa, NULL); - sigaction(SIGWINCH, &sa, NULL); - - /* Ignore any SIGTT{IN,OU} or SIGPIPE we get. */ - sa.sa_handler = SIG_IGN; - sigaction(SIGPIPE, &sa, NULL); - 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. */ - sa.sa_handler = handler; - sigaction(SIGCHLD, &sa, NULL); - - /* - * Start a new session with the parent as the session leader - * and the slave pty as the controlling terminal. - * This allows us to be notified when the child has been suspended. - */ -#ifdef HAVE_SETSID - if (setsid() == -1) { - warning("setsid"); - _exit(1); - } -#else -# ifdef TIOCNOTTY - n = open(_PATH_TTY, O_RDWR|O_NOCTTY); - if (n >= 0) { - /* Disconnect from old controlling tty. */ - if (ioctl(n, TIOCNOTTY, NULL) == -1) - warning("cannot disconnect controlling tty"); - close(n); - } -# endif - setpgrp(0, 0); -#endif -#ifdef TIOCSCTTY - if (ioctl(script_fds[SFD_SLAVE], TIOCSCTTY, NULL) != 0) - log_error(USE_ERRNO, "unable to set controlling tty"); -#else - /* Set controlling tty by reopening slave. */ - if ((n = open(slavename, O_RDWR)) >= 0) - close(n); -#endif - - if (foreground && !ttyout) - foreground = 0; - - /* Start command and wait for it to stop or exit */ - child = fork(); - if (child == -1) { - warning("Can't fork"); - _exit(1); - } - if (child == 0) { - /* Reset signal handlers. */ - sa.sa_flags = SA_RESTART; - sa.sa_handler = SIG_DFL; - sigaction(SIGHUP, &sa, NULL); - sigaction(SIGTERM, &sa, NULL); - sigaction(SIGINT, &sa, NULL); - sigaction(SIGQUIT, &sa, NULL); - sigaction(SIGTSTP, &sa, NULL); - sigaction(SIGTTIN, &sa, NULL); - sigaction(SIGTTOU, &sa, NULL); - sigaction(SIGUSR1, &sa, NULL); - sigaction(SIGUSR2, &sa, NULL); - - /* setup tty and exec command */ - script_run(path, argv, rbac_enabled); - warning("unable to execute %s", path); - _exit(127); - } - - /* - * Put child in its own process group. If we are starting the command - * in the foreground, assign its pgrp to the tty. - */ - setpgid(child, child); - if (foreground) { - do { - status = tcsetpgrp(script_fds[SFD_SLAVE], child); - } while (status == -1 && errno == EINTR); - } - - /* Wait for signal on backchannel or for SIGCHLD */ - for (;;) { - /* Read child status, assumes recvsig can only be SIGCHLD */ - while (recvsig) { - recvsig = 0; - /* read child status and relay to parent */ - do { - pid = waitpid(child, &status, WUNTRACED|WNOHANG); - } while (pid == -1 && errno == EINTR); - if (pid == child) { -#ifdef SCRIPT_DEBUG - if (WIFSTOPPED(status)) - warningx("command stopped, signal %d", WSTOPSIG(status)); - else if (WIFSIGNALED(status)) - warningx("command killed, signal %d", WTERMSIG(status)); - else - warningx("command exited?"); -#endif - if (write(backchannel, &status, sizeof(status)) != sizeof(status)) - break; /* XXX - error, kill child and exit */ -#ifdef SCRIPT_DEBUG - warningx("sent signo to parent"); -#endif - if (!WIFSTOPPED(status)) { - _exit(1); /* child dead */ - } - } - } - nread = read(backchannel, &signo, sizeof(signo)); - if (nread == -1) { - if (errno != EINTR) - break; /* XXX - error, kill child and exit */ - continue; - } - if (nread != sizeof(signo)) { - /* EOF? */ - break; - } - - /* Handle signal from parent. */ -#ifdef SCRIPT_DEBUG - warningx("signal %d from parent", signo); -#endif - switch (signo) { - case SIGKILL: - _exit(1); - /* NOTREACHED */ - case SIGHUP: - case SIGTERM: - case SIGINT: - case SIGQUIT: - case SIGTSTP: - /* relay signal to child */ - killpg(child, signo); - break; - case SIGUSR1: - /* foreground process, grant it controlling tty. */ - do { - status = tcsetpgrp(script_fds[SFD_SLAVE], child); - } while (status == -1 && errno == EINTR); - killpg(child, SIGCONT); - break; - case SIGUSR2: - /* background process, I take controlling tty. */ - do { - status = tcsetpgrp(script_fds[SFD_SLAVE], self); - } while (status == -1 && errno == EINTR); - killpg(child, SIGCONT); - break; - default: - warningx("unexpected signal from child: %d", signo); - break; - } - } - - _exit(1); -} - -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; -{ - int n; - - while (output->len > output->off) { - n = write(STDOUT_FILENO, output->buf + output->off, - output->len - output->off); - if (n <= 0) - break; - output->off += n; - } - - /* Make sure there is no output remaining on the master pty. */ - for (;;) { - n = read(script_fds[SFD_MASTER], output->buf, sizeof(output->buf)); - if (n <= 0) - break; - log_output(output->buf, n, &then, &now, ofile, tfile); - output->off = 0; - output->len = n; - do { - n = write(STDOUT_FILENO, output->buf + output->off, - output->len - output->off); - if (n <= 0) - break; - output->off += n; - } while (output->len > output->off); - } -} - -static void -script_run(path, argv, rbac_enabled) - char *path; - char *argv[]; - int rbac_enabled; -{ - pid_t self = getpid(); - - /* Set child process group here too to avoid a race. */ - setpgid(0, self); - - /* - * We have guaranteed that the slave fd > 3 - */ - if (isatty(STDIN_FILENO)) - dup2(script_fds[SFD_SLAVE], STDIN_FILENO); - dup2(script_fds[SFD_SLAVE], STDOUT_FILENO); - dup2(script_fds[SFD_SLAVE], STDERR_FILENO); - close(script_fds[SFD_SLAVE]); - - /* Wait for parent to grant us the tty if we are foreground. */ - if (foreground) { - while (tcgetpgrp(STDOUT_FILENO) != self) - ; /* spin */ - } - -#ifdef HAVE_SELINUX - if (rbac_enabled) - selinux_execv(path, argv); - else -#endif - execv(path, argv); -} - -static void -sync_winsize(src, dst) - int src; - int dst; -{ -#ifdef TIOCGWINSZ - struct winsize win; - pid_t pgrp; - - if (ioctl(src, TIOCGWINSZ, &win) == 0) { - ioctl(dst, TIOCSWINSZ, &win); -#ifdef TIOCGPGRP - if (ioctl(dst, TIOCGPGRP, &pgrp) == 0) - killpg(pgrp, SIGWINCH); -#endif - } -#endif -} - -/* - * Handler for SIGCHLD in parent - */ -static void -sigchild(s) - int s; -{ - pid_t pid; - int serrno = errno; - - do { - pid = waitpid(child, &child_status, WNOHANG | WUNTRACED); - } while (pid == -1 && errno == EINTR); - if (pid == child) { - if (WIFSTOPPED(child_status)) - recvsig = WSTOPSIG(child_status); - else - alive = 0; - } - - errno = serrno; -} - -/* - * Generic handler for signals passed from parent -> child - */ -static void -handler(s) - int s; -{ - recvsig = s; -} - -/* - * Handler for SIGWINCH in parent - */ -static void -sigwinch(s) - int s; -{ - int serrno = errno; - - sync_winsize(script_fds[SFD_USERTTY], script_fds[SFD_SLAVE]); - errno = serrno; -} diff --git a/selinux.c b/selinux.c index 1e71acefc..22416f46e 100644 --- a/selinux.c +++ b/selinux.c @@ -1,4 +1,5 @@ /* + * Copyright (c) 2009-2010 Todd C. Miller * Copyright (c) 2008 Dan Walsh * * Borrowed heavily from newrole source code @@ -48,51 +49,52 @@ #include "sudo.h" #include "pathnames.h" -static security_context_t old_context; -static security_context_t new_context; -static security_context_t tty_context; -static security_context_t new_tty_context; -static int enforcing; +static struct selinux_state { + security_context_t old_context; + security_context_t new_context; + security_context_t tty_context; + security_context_t new_tty_context; + const char *ttyn; + int ttyfd; + int enforcing; +} se_state; /* * This function attempts to revert the relabeling done to the tty. * fd - referencing the opened ttyn * ttyn - name of tty to restore - * tty_context - original context of the tty - * new_tty_context - context tty was relabeled to * * Returns zero on success, non-zero otherwise */ -static int -restore_tty_label(int fd, const char *ttyn, security_context_t tty_context, - security_context_t new_tty_context) +/* XXX - should also be called as part of cleanup() */ +int +selinux_restore_tty(void) { - int rc = 0; + int retval = 0; security_context_t chk_tty_context = NULL; - if (!ttyn) - goto skip_relabel; - - if (!new_tty_context) - goto skip_relabel; + if (se_state.ttyfd == -1 || se_state.new_tty_context == NULL) + goto skip_relabel; /* Verify that the tty still has the context set by sudo. */ - if ((rc = fgetfilecon(fd, &chk_tty_context)) < 0) { - warning("unable to fgetfilecon %s", ttyn); - goto skip_relabel; + if ((retval = fgetfilecon(se_state.ttyfd, &chk_tty_context)) < 0) { + warning("unable to fgetfilecon %s", se_state.ttyn); + goto skip_relabel; } - if ((rc = strcmp(chk_tty_context, new_tty_context))) { - warningx("%s changed labels.", ttyn); - goto skip_relabel; + if ((retval = strcmp(chk_tty_context, se_state.new_tty_context))) { + warningx("%s changed labels.", se_state.ttyn); + goto skip_relabel; } - if ((rc = fsetfilecon(fd, tty_context)) < 0) - warning("unable to restore context for %s", ttyn); + if ((retval = fsetfilecon(se_state.ttyfd, se_state.tty_context)) < 0) + warning("unable to restore context for %s", se_state.ttyn); skip_relabel: + if (se_state.ttyfd != -1) + close(se_state.ttyfd); freecon(chk_tty_context); - return(rc); + return retval; } /* @@ -104,41 +106,90 @@ skip_relabel: * in permissive mode. */ static int -relabel_tty(int ttyfd, security_context_t new_context, - security_context_t *tty_context, security_context_t *new_tty_context, - int enforcing) +relabel_tty(const char *ttyn, int ptyfd) { security_context_t tty_con = NULL; security_context_t new_tty_con = NULL; - if (fgetfilecon(ttyfd, &tty_con) < 0) { + se_state.ttyfd = ptyfd; + + /* It is perfectly legal to have no tty. */ + if (ptyfd == -1 && ttyn == NULL) + return 0; + + /* If sudo is not allocating a pty for the command, open current tty. */ + if (ptyfd == -1) { + se_state.ttyfd = open(ttyn, O_RDWR|O_NONBLOCK); + if (se_state.ttyfd == -1) { + warning("unable to open %s, not relabeling tty", ttyn); + if (se_state.enforcing) + goto bad; + } + (void)fcntl(se_state.ttyfd, F_SETFL, + fcntl(se_state.ttyfd, F_GETFL, 0) & ~O_NONBLOCK); + } + + if (fgetfilecon(se_state.ttyfd, &tty_con) < 0) { warning("unable to get current tty context, not relabeling tty"); - if (enforcing) - goto error; + if (se_state.enforcing) + goto bad; } - if (tty_con && (security_compute_relabel(new_context, tty_con, + if (tty_con && (security_compute_relabel(se_state.new_context, tty_con, SECCLASS_CHR_FILE, &new_tty_con) < 0)) { warning("unable to get new tty context, not relabeling tty"); - if (enforcing) - goto error; + if (se_state.enforcing) + goto bad; } if (new_tty_con != NULL) { - if (fsetfilecon(ttyfd, new_tty_con) < 0) { + if (fsetfilecon(se_state.ttyfd, new_tty_con) < 0) { warning("unable to set new tty context"); - if (enforcing) - goto error; + if (se_state.enforcing) + goto bad; } } - *tty_context = tty_con; - *new_tty_context = new_tty_con; - return(0); - -error: + if (ptyfd != -1) { + /* Reopen pty that was relabeled, std{in,out,err} are reset later. */ + se_state.ttyfd = open(ttyn, O_RDWR|O_NOCTTY, 0); + if (se_state.ttyfd == -1) { + warning("cannot open %s", ttyn); + if (se_state.enforcing) + goto bad; + } + dup2(se_state.ttyfd, ptyfd); + } else { + /* Re-open tty to get new label and reset std{in,out,err} */ + close(se_state.ttyfd); + se_state.ttyfd = open(ttyn, O_RDWR|O_NONBLOCK); + if (se_state.ttyfd == -1) + warning("unable to open %s", ttyn); + else + (void)fcntl(se_state.ttyfd, F_SETFL, + fcntl(se_state.ttyfd, F_GETFL, 0) & ~O_NONBLOCK); + if (isatty(STDIN_FILENO)) + dup2(se_state.ttyfd, STDIN_FILENO); + if (isatty(STDOUT_FILENO)) + dup2(se_state.ttyfd, STDOUT_FILENO); + if (isatty(STDERR_FILENO)) + dup2(se_state.ttyfd, STDERR_FILENO); + } + /* Retain se_state.ttyfd so we can restore label when command finishes. */ + (void)fcntl(se_state.ttyfd, F_SETFD, FD_CLOEXEC); + + se_state.ttyn = ttyn; + se_state.tty_context = tty_con; + se_state.new_tty_context = new_tty_con; + return 0; + +bad: + if (se_state.ttyfd != -1 && se_state.ttyfd != ptyfd) { + close(se_state.ttyfd); + se_state.ttyfd = -1; + } freecon(tty_con); - return(-1); + return -1; } /* @@ -146,7 +197,7 @@ error: * specified role and type. */ security_context_t -get_exec_context(security_context_t old_context, char *role, char *type) +get_exec_context(security_context_t old_context, const char *role, const char *type) { security_context_t new_context = NULL; context_t context = NULL; @@ -155,12 +206,12 @@ get_exec_context(security_context_t old_context, char *role, char *type) /* We must have a role, the type is optional (we can use the default). */ if (!role) { warningx("you must specify a role."); - return(NULL); + return NULL; } if (!type) { if (get_default_type(role, &typebuf)) { warningx("unable to get default type"); - return(NULL); + return NULL; } type = typebuf; } @@ -177,11 +228,11 @@ get_exec_context(security_context_t old_context, char *role, char *type) */ if (context_role_set(context, role)) { warningx("failed to set new role %s", role); - goto error; + goto bad; } if (context_type_set(context, type)) { warningx("failed to set new type %s", type); - goto error; + goto bad; } /* @@ -190,7 +241,7 @@ get_exec_context(security_context_t old_context, char *role, char *type) new_context = estrdup(context_str(context)); if (security_check_context(new_context) < 0) { warningx("%s is not a valid context", new_context); - goto error; + goto bad; } #ifdef DEBUG @@ -198,64 +249,71 @@ get_exec_context(security_context_t old_context, char *role, char *type) #endif context_free(context); - return(new_context); + return new_context; -error: +bad: free(typebuf); context_free(context); freecon(new_context); - return(NULL); + return NULL; } /* - * Set the tty context in preparation for fork/exec. + * Set the exec and tty contexts in preparation for fork/exec. + * Must run as root, before the uid change. + * If ptyfd is not -1, it indicates we are running + * in a pty and do not need to reset std{in,out,err}. */ void -selinux_prefork(char *role, char *type, int ttyfd) +selinux_setup(const char *role, const char *type, const char *ttyn, + int ptyfd) { /* Store the caller's SID in old_context. */ - if (getprevcon(&old_context)) + if (getprevcon(&se_state.old_context)) error(EXIT_FAILURE, "failed to get old_context"); - enforcing = security_getenforce(); - if (enforcing < 0) + se_state.enforcing = security_getenforce(); + if (se_state.enforcing < 0) error(EXIT_FAILURE, "unable to determine enforcing mode."); #ifdef DEBUG - warningx("your old context was %s", old_context); + warningx("your old context was %s", se_state.old_context); #endif - new_context = get_exec_context(old_context, role, type); - if (!new_context) + se_state.new_context = get_exec_context(se_state.old_context, role, type); + if (!se_state.new_context) error(EXIT_FAILURE, "unable to get exec context"); - ttyfd = relabel_tty(ttyfd, new_context, &tty_context, - &new_tty_context, enforcing); - if (ttyfd < 0) - error(EXIT_FAILURE, "unable to setup tty context for %s", new_context); + if (relabel_tty(ttyn, ptyfd) < 0) + error(EXIT_FAILURE, "unable to setup tty context for %s", se_state.new_context); #ifdef DEBUG - warningx("your old tty context is %s", tty_context); - warningx("your new tty context is %s", new_tty_context); + if (se_state.ttyfd != -1) { + warningx("your old tty context is %s", se_state.tty_context); + warningx("your new tty context is %s", se_state.new_tty_context); + } #endif + } void -selinux_execv(char *path, char **argv) +selinux_execve(const char *path, char *argv[], char *envp[]) { - if (setexeccon(new_context)) { - warning("unable to set exec context to %s", new_context); - if (enforcing) + int serrno; + + if (setexeccon(se_state.new_context)) { + warning("unable to set exec context to %s", se_state.new_context); + if (se_state.enforcing) return; } - if (setkeycreatecon(new_context)) { - warning("unable to set key creation context to %s", new_context); - if (enforcing) + if (setkeycreatecon(se_state.new_context)) { + warning("unable to set key creation context to %s", se_state.new_context); + if (se_state.enforcing) return; } #ifdef WITH_AUDIT - if (send_audit_message(1, old_context, new_context, user_ttypath)) + if (send_audit_message(1, se_state.old_context, se_state.new_context, se_state.ttyn)) return; #endif @@ -264,86 +322,5 @@ selinux_execv(char *path, char **argv) argv[0] = *argv[1] == '-' ? "-sesh" : "sesh"; argv[1] = path; - execv(_PATH_SUDO_SESH, argv); - warning("%s", path); -} - -/* - * If the program is being run with a different security context we - * need to go through an intermediary process for the transition to - * be allowed by the policy. We use the "sesh" shell for this, which - * will simply execute the command pass to it on the command line. - */ -void -selinux_exec(char *role, char *type, char **argv) -{ - pid_t childPid; - int ttyfd; - - /* Must have a tty. */ - if (user_ttypath == NULL || *user_ttypath == '\0') - error(EXIT_FAILURE, "unable to determine tty"); - - /* Re-open TTY descriptor */ - ttyfd = open(user_ttypath, O_RDWR | O_NONBLOCK); - if (ttyfd == -1) - error(EXIT_FAILURE, "unable to open %s", user_ttypath); - (void)fcntl(ttyfd, F_SETFL, fcntl(ttyfd, F_GETFL, 0) & ~O_NONBLOCK); - - /* - * Get the old and new security and tty contexts, sets the new - * tty context on ttyfd. - */ - selinux_prefork(role, type, ttyfd); - - childPid = fork(); - if (childPid < 0) { - /* fork failed, no child to worry about */ - warning("unable to fork"); - if (restore_tty_label(ttyfd, user_ttypath, tty_context, new_tty_context)) - warningx("unable to restore tty label"); - exit(EXIT_FAILURE); - } else if (childPid) { - pid_t pid; - int status; - - /* Parent, wait for child to finish. */ - do { - pid = waitpid(childPid, &status, 0); - } while (pid == -1 && errno == EINTR); - - if (pid == -1) - error(EXIT_FAILURE, "waitpid"); - - if (restore_tty_label(ttyfd, user_ttypath, tty_context, new_tty_context)) - errorx(EXIT_FAILURE, "unable to restore tty label"); - - /* Preserve child exit status. */ - if (WIFEXITED(status)) - exit(WEXITSTATUS(status)); - exit(EXIT_FAILURE); - } - /* Child */ - /* Close the tty and reopen descriptors 0 through 2 */ - if (close(ttyfd) || close(STDIN_FILENO) || close(STDOUT_FILENO) || - close(STDERR_FILENO)) { - warning("could not close descriptors"); - goto error; - } - ttyfd = open(user_ttypath, O_RDONLY | O_NONBLOCK); - if (ttyfd != STDIN_FILENO) - goto error; - fcntl(ttyfd, F_SETFL, fcntl(ttyfd, F_GETFL, 0) & ~O_NONBLOCK); - ttyfd = open(user_ttypath, O_RDWR | O_NONBLOCK); - if (ttyfd != STDOUT_FILENO) - goto error; - fcntl(ttyfd, F_SETFL, fcntl(ttyfd, F_GETFL, 0) & ~O_NONBLOCK); - ttyfd = dup(STDOUT_FILENO); - if (ttyfd != STDERR_FILENO) - goto error; - - selinux_execv(safe_cmnd, argv); - -error: - _exit(EXIT_FAILURE); + execve(_PATH_SUDO_SESH, argv, envp); } diff --git a/sudo.c b/sudo.c index 09cc7ec60..36f9a46a2 100644 --- a/sudo.c +++ b/sudo.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1993-1996, 1998-2009 Todd C. Miller + * Copyright (c) 1993-1996, 1998-2010 Todd C. Miller * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -31,6 +31,7 @@ #include #include +#include #include #include #ifdef HAVE_SETRLIMIT @@ -159,6 +160,7 @@ sigaction_t saved_sa_int, saved_sa_quit, saved_sa_tstp; static char *runas_user; static char *runas_group; static struct sudo_nss_list *snl; +static int sudo_mode; /* For getopt(3) */ extern char *optarg; @@ -171,7 +173,7 @@ main(argc, argv, envp) char **envp; { int sources = 0, validated; - int fd, cmnd_status, sudo_mode, pwflag, rc = 0; + int fd, cmnd_status, pwflag, rc = 0; sigaction_t sa; struct sudo_nss *nss; #if defined(SUDO_DEVEL) && defined(__OpenBSD__) @@ -445,6 +447,9 @@ main(argc, argv, envp) } if (ISSET(validated, VALIDATE_OK)) { + struct command_status cstat; + int exitcode = 1; + /* Finally tell the user if the command did not exist. */ if (cmnd_status == NOT_FOUND_DOT) { audit_failure(NewArgv, "command in current directory"); @@ -463,12 +468,11 @@ main(argc, argv, envp) validate_env_vars(sudo_user.env_vars); } -#ifdef _PATH_SUDO_TRANSCRIPT +#ifdef _PATH_SUDO_IO_LOGDIR /* Get next session ID so we can log it. */ - if (def_transcript && ISSET(sudo_mode, (MODE_RUN | MODE_EDIT))) - script_nextid(); + if (ISSET(sudo_mode, (MODE_RUN | MODE_EDIT)) && (def_log_input || def_log_output)) + io_nextid(); #endif - log_allowed(validated); if (ISSET(sudo_mode, MODE_CHECK)) rc = display_cmnd(snl, list_pw ? list_pw : sudo_user.pw); @@ -483,39 +487,10 @@ main(argc, argv, envp) if (ISSET(sudo_mode, (MODE_VALIDATE|MODE_CHECK|MODE_LIST))) exit(rc); - /* - * Set umask based on sudoers. - * If user's umask is more restrictive, OR in those bits too - * unless umask_override is set. - */ - if (def_umask != 0777) { - if (def_umask_override) { - umask(def_umask); - } else { - mode_t mask = umask(def_umask); - mask |= def_umask; - if (mask != def_umask) - umask(mask); - } - } - - /* Restore coredumpsize resource limit. */ -#if defined(RLIMIT_CORE) && !defined(SUDO_DEVEL) - (void) setrlimit(RLIMIT_CORE, &corelimit); -#endif /* RLIMIT_CORE && !SUDO_DEVEL */ - /* Must audit before uid change. */ audit_success(NewArgv); -#ifdef _PATH_SUDO_TRANSCRIPT - /* Open tty as needed */ - if (def_transcript) - script_setup(); -#endif - /* Become specified user or root if executing a command. */ - if (ISSET(sudo_mode, MODE_RUN)) - set_perms(PERM_FULL_RUNAS); if (ISSET(sudo_mode, MODE_LOGIN_SHELL)) { char *p; @@ -526,10 +501,6 @@ main(argc, argv, envp) *p = '-'; NewArgv[0] = p; - /* Change to target user's homedir. */ - if (chdir(runas_pw->pw_dir) == -1) - warning("unable to change directory to %s", runas_pw->pw_dir); - #if defined(__linux__) || defined(_AIX) /* Insert system-wide environment variables. */ read_env_file(_PATH_ENVIRONMENT, TRUE); @@ -555,43 +526,31 @@ main(argc, argv, envp) sudo_endpwent(); sudo_endgrent(); - /* Move pty master/slave to low numbered fd and close the rest. */ -#ifdef _PATH_SUDO_TRANSCRIPT - fd = def_transcript ? script_duplow(def_closefrom) : def_closefrom; - closefrom(fd); -#else - closefrom(def_closefrom); -#endif - #ifdef PROFILING exit(0); #endif - if (ISSET(sudo_mode, MODE_BACKGROUND) && fork() > 0) { - syslog(LOG_AUTH|LOG_ERR, "fork"); /* XXX */ - exit(0); + sudo_execve(safe_cmnd, NewArgv, environ, &cstat); + switch (cstat.type) { + case CMD_ERRNO: + /* exec_setup() or execve() returned an error. */ + warningx("unable to execute %s: %s", safe_cmnd, strerror(cstat.val)); + exitcode = 127; + break; + case CMD_WSTATUS: + /* Command ran, exited or was killed. */ + 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; } -#ifdef _PATH_SUDO_TRANSCRIPT - if (def_transcript) - script_execv(safe_cmnd, NewArgv); - else +#ifdef _PATH_SUDO_IO_LOGDIR + io_log_close(); #endif -#ifdef HAVE_SELINUX - if (is_selinux_enabled() > 0 && user_role != NULL) - selinux_exec(user_role, user_type, NewArgv); - else -#endif - execv(safe_cmnd, NewArgv); - /* - * If we got here then execve() failed... - */ - if (errno == ENOEXEC) { - NewArgv--; /* at least one extra slot... */ - NewArgv[0] = "sh"; - NewArgv[1] = safe_cmnd; - execv(_PATH_BSHELL, NewArgv); - } - warning("unable to execute %s", safe_cmnd); - exit(127); + exit(exitcode); } else if (ISSET(validated, FLAG_NO_USER | FLAG_NO_HOST)) { audit_failure(NewArgv, "No user or host"); log_denial(validated, 1); @@ -878,6 +837,66 @@ set_cmnd(sudo_mode) return(rval); } +/* + * Setup the execution environment immediately prior to the call to execve() + * Returns TRUE on success and FALSE on failure. + */ +int +exec_setup() +{ + int rval = FALSE; + + /* + * Set umask based on sudoers. + * If user's umask is more restrictive, OR in those bits too + * unless umask_override is set. + */ + if (def_umask != 0777) { + if (def_umask_override) { + umask(def_umask); + } else { + mode_t mask = umask(def_umask); + mask |= def_umask; + if (mask != def_umask) + umask(mask); + } + } + + /* Restore coredumpsize resource limit. */ +#if defined(RLIMIT_CORE) && !defined(SUDO_DEVEL) + (void) setrlimit(RLIMIT_CORE, &corelimit); +#endif /* RLIMIT_CORE && !SUDO_DEVEL */ + + if (ISSET(sudo_mode, MODE_RUN)) + set_perms(PERM_FULL_RUNAS); + + if (ISSET(sudo_mode, MODE_LOGIN_SHELL)) { + /* Change to target user's homedir. */ + if (chdir(runas_pw->pw_dir) == -1) + warning("unable to change directory to %s", runas_pw->pw_dir); + goto done; + } + + if (ISSET(sudo_mode, MODE_BACKGROUND)) { + switch (fork()) { + case -1: + warning("fork"); + goto done; + case 0: + /* child continues */ + break; + default: + /* parent exists */ + exit(0); + } + } + + rval = TRUE; + +done: + return(rval); +} + /* * Command line argument parsing. * Sets NewArgc and NewArgv which corresponds to the argc/argv we'll use @@ -1481,11 +1500,11 @@ cleanup(gotsignal) } sudo_endpwent(); sudo_endgrent(); - } -#ifdef _PATH_SUDO_TRANSCRIPT - if (def_transcript) - term_restore(STDIN_FILENO, 0); +#ifdef _PATH_SUDO_IO_LOGDIR + io_log_close(); #endif + } + term_restore(STDIN_FILENO, 0); } static void diff --git a/sudo.h b/sudo.h index 867105513..035a4f857 100644 --- a/sudo.h +++ b/sudo.h @@ -74,6 +74,16 @@ struct sudo_user { #endif }; +/* 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; +}; + /* * Return values for sudoers_lookup(), also used as arguments for log_auth() * Note: cannot use '0' as a value here. @@ -274,20 +284,33 @@ void selinux_prefork __P((char *, char *, int)); #ifdef HAVE_GETUSERATTR void aix_setlimits __P((char *)); #endif -int script_duplow __P((int)); -int script_execv __P((char *, char **)); -void script_nextid __P((void)); -void script_setup __P((void)); int term_cbreak __P((int)); -int term_copy __P((int, int, int)); +int term_copy __P((int, int)); int term_noecho __P((int)); -int term_raw __P((int, int, int)); +int term_raw __P((int, int)); int term_restore __P((int, int)); char *get_timestr __P((time_t, int)); int get_boottime __P((struct timeval *)); int user_in_group __P((struct passwd *, const char *)); +int exec_setup __P((void)); YY_DECL; +/* exec.c */ +int sudo_execve __P((const char *, char *[], char *[], struct command_status *cstat)); + +/* iolog.c */ +int io_log_open __P((void)); +int log_stderr __P((const char *buf, unsigned int len)); +int log_stdin __P((const char *buf, unsigned int len)); +int log_stdout __P((const char *buf, unsigned int len)); +int log_ttyin __P((const char *buf, unsigned int len)); +int log_ttyout __P((const char *buf, unsigned int len)); +void io_log_close __P((void)); +void io_nextid __P((void)); + +/* pty.c */ +int get_pty __P((int *master, int *slave, char *name, size_t namesz)); + /* Only provide extern declarations outside of sudo.c. */ #ifndef _SUDO_MAIN extern struct sudo_user sudo_user; @@ -300,5 +323,6 @@ extern uid_t timestamp_uid; #ifndef errno extern int errno; #endif +extern char **environ; /* XXX */ #endif /* _SUDO_SUDO_H */ diff --git a/sudoers.cat b/sudoers.cat index 40b8f30e6..4252e6e7c 100644 --- a/sudoers.cat +++ b/sudoers.cat @@ -61,7 +61,7 @@ DDEESSCCRRIIPPTTIIOONN -1.7.3b2 April 7, 2010 1 +1.7.3b3 June 3, 2010 1 @@ -127,7 +127,7 @@ SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) -1.7.3b2 April 7, 2010 2 +1.7.3b3 June 3, 2010 2 @@ -193,7 +193,7 @@ SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) -1.7.3b2 April 7, 2010 3 +1.7.3b3 June 3, 2010 3 @@ -259,7 +259,7 @@ SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) -1.7.3b2 April 7, 2010 4 +1.7.3b3 June 3, 2010 4 @@ -280,7 +280,8 @@ SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) Runas_Spec ::= '(' Runas_List? (':' Runas_List)? ')' Tag_Spec ::= ('NOPASSWD:' | 'PASSWD:' | 'NOEXEC:' | 'EXEC:' | - 'SETENV:' | 'NOSETENV:' | 'TRANSCRIPT:' | 'NOTRANSCRIPT:') + 'SETENV:' | 'NOSETENV:' | 'LOG_INPUT:' | 'NOLOG_INPUT:' | + 'LOG_OUTPUT:' | 'NOLOG_OUTPUT:') A uusseerr ssppeecciiffiiccaattiioonn determines which commands a user may run (and as what user) on specified hosts. By default, commands are run as rroooott, @@ -324,8 +325,7 @@ SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) - -1.7.3b2 April 7, 2010 5 +1.7.3b3 June 3, 2010 5 @@ -350,10 +350,10 @@ SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) TTaagg__SSppeecc A command may have zero or more tags associated with it. There are eight possible tag values, NOPASSWD, PASSWD, NOEXEC, EXEC, SETENV, - NOSETENV, TRANSCRIPT and NOTRANSCRIPT. Once a tag is set on a Cmnd, - subsequent Cmnds in the Cmnd_Spec_List, inherit the tag unless it is - overridden by the opposite tag (i.e.: PASSWD overrides NOPASSWD and - NOEXEC overrides EXEC). + NOSETENV, LOG_INPUT, NOLOG_INPUT, LOG_OUTPUT and NOLOG_OUTPUT. Once a + tag is set on a Cmnd, subsequent Cmnds in the Cmnd_Spec_List, inherit + the tag unless it is overridden by the opposite tag (i.e.: PASSWD + overrides NOPASSWD and NOEXEC overrides EXEC). _N_O_P_A_S_S_W_D _a_n_d _P_A_S_S_W_D @@ -391,7 +391,7 @@ SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) -1.7.3b2 April 7, 2010 6 +1.7.3b3 June 3, 2010 6 @@ -418,10 +418,16 @@ SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) If the command matched is AALLLL, the SETENV tag is implied for that command; this default may be overridden by use of the UNSETENV tag. - _T_R_A_N_S_C_R_I_P_T _a_n_d _N_O_T_R_A_N_S_C_R_I_P_T + _L_O_G___I_N_P_U_T _a_n_d _N_O_L_O_G___I_N_P_U_T - These tags override the value of the _t_r_a_n_s_c_r_i_p_t option on a per-command - basis. For more information, see the description of _t_r_a_n_s_c_r_i_p_t in the + These tags override the value of the _l_o_g___i_n_p_u_t option on a per-command + basis. For more information, see the description of _l_o_g___i_n_p_u_t in the + "SUDOERS OPTIONS" section below. + + _L_O_G___O_U_T_P_U_T _a_n_d _N_O_L_O_G___O_U_T_P_U_T + + These tags override the value of the _l_o_g___o_u_t_p_u_t option on a per-command + basis. For more information, see the description of _l_o_g___o_u_t_p_u_t in the "SUDOERS OPTIONS" section below. WWiillddccaarrddss @@ -449,22 +455,22 @@ SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) Would match any file name beginning with a letter. - Note that a forward slash ('/') will nnoott be matched by wildcards used - in the path name. When matching the command line arguments, however, a - slash ddooeess get matched by wildcards. This is to make a path like: - /usr/bin/* +1.7.3b3 June 3, 2010 7 -1.7.3b2 April 7, 2010 7 +SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) -SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) + Note that a forward slash ('/') will nnoott be matched by wildcards used + in the path name. When matching the command line arguments, however, a + slash ddooeess get matched by wildcards. This is to make a path like: + /usr/bin/* match _/_u_s_r_/_b_i_n_/_w_h_o but not _/_u_s_r_/_b_i_n_/_X_1_1_/_x_t_e_r_m. @@ -514,16 +520,10 @@ SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) sorted lexical order. That is, _/_e_t_c_/_s_u_d_o_e_r_s_._d_/_0_1___f_i_r_s_t will be parsed before _/_e_t_c_/_s_u_d_o_e_r_s_._d_/_1_0___s_e_c_o_n_d. Be aware that because the sorting is lexical, not numeric, _/_e_t_c_/_s_u_d_o_e_r_s_._d_/_1___w_h_o_o_p_s would be loaded aafftteerr - _/_e_t_c_/_s_u_d_o_e_r_s_._d_/_1_0___s_e_c_o_n_d. Using a consistent number of leading zeroes - in the file names can be used to avoid such problems. - - Note that unlike files included via #include, vviissuuddoo will not edit the - files in a #includedir directory unless one of them contains a syntax - error. It is still possible to run vviissuuddoo with the -f flag to edit the -1.7.3b2 April 7, 2010 8 +1.7.3b3 June 3, 2010 8 @@ -532,6 +532,12 @@ SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) + _/_e_t_c_/_s_u_d_o_e_r_s_._d_/_1_0___s_e_c_o_n_d. Using a consistent number of leading zeroes + in the file names can be used to avoid such problems. + + Note that unlike files included via #include, vviissuuddoo will not edit the + files in a #includedir directory unless one of them contains a syntax + error. It is still possible to run vviissuuddoo with the -f flag to edit the files directly. OOtthheerr ssppeecciiaall cchhaarraacctteerrss aanndd rreesseerrvveedd wwoorrddss @@ -580,16 +586,10 @@ SSUUDDOOEERRSS OOPPTTIIOONNSS authenticate If set, users must authenticate themselves via a password (or other means of authentication) before they - may run commands. This default may be overridden via - the PASSWD and NOPASSWD tags. This flag is _o_n by - default. - - closefrom_override - If set, the user may use ssuuddoo's --CC option which -1.7.3b2 April 7, 2010 9 +1.7.3b3 June 3, 2010 9 @@ -598,14 +598,20 @@ SSUUDDOOEERRSS OOPPTTIIOONNSS SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) + may run commands. This default may be overridden via + the PASSWD and NOPASSWD tags. This flag is _o_n by + default. + + closefrom_override + If set, the user may use ssuuddoo's --CC option which overrides the default starting point at which ssuuddoo begins closing open file descriptors. This flag is _o_f_f by default. - compress_transcript - If set, and the _t_r_a_n_s_c_r_i_p_t flag is also set, ssuuddoo will - compress the transcript logs using zzlliibb. This flag is - _o_n by default when ssuuddoo is compiled with zzlliibb support. + compress_io If set, and ssuuddoo is configured to log a command's input + or output, the I/O logs will be compressed using zzlliibb. + This flag is _o_n by default when ssuuddoo is compiled with + zzlliibb support. env_editor If set, vviissuuddoo will use the value of the EDITOR or VISUAL environment variables before falling back on the @@ -615,7 +621,7 @@ SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) alternative is to place a colon-separated list of editors in the editor variable. vviissuuddoo will then only use the EDITOR or VISUAL if they match a value - specified in editor. This flag is _o_f_f by default. + specified in editor. This flag is _o_n by default. env_reset If set, ssuuddoo will reset the environment to only contain the LOGNAME, SHELL, USER, USERNAME and the SUDO_* @@ -646,16 +652,10 @@ SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) flag is _o_f_f by default. fqdn Set this flag if you want to put fully qualified host - names in the _s_u_d_o_e_r_s file. I.e., instead of myhost you - would use myhost.mydomain.edu. You may still use the - short form if you wish (and even mix the two). Beware - that turning on _f_q_d_n requires ssuuddoo to make DNS lookups - which may make ssuuddoo unusable if DNS stops working (for - example if the machine is not plugged into the -1.7.3b2 April 7, 2010 10 +1.7.3b3 June 3, 2010 10 @@ -664,6 +664,12 @@ SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) + names in the _s_u_d_o_e_r_s file. I.e., instead of myhost you + would use myhost.mydomain.edu. You may still use the + short form if you wish (and even mix the two). Beware + that turning on _f_q_d_n requires ssuuddoo to make DNS lookups + which may make ssuuddoo unusable if DNS stops working (for + example if the machine is not plugged into the network). Also note that you must use the host's official name as DNS knows it. That is, you may not use a host alias (CNAME entry) due to performance @@ -675,7 +681,7 @@ SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) ignore_dot If set, ssuuddoo will ignore '.' or '' (current dir) in the PATH environment variable; the PATH itself is not - modified. This flag is _o_f_f by default. + modified. This flag is _o_n by default. ignore_local_sudoers If set via LDAP, parsing of _/_e_t_c_/_s_u_d_o_e_r_s will be @@ -691,7 +697,7 @@ SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) _o_f_f by default. insults If set, ssuuddoo will insult users when they enter an - incorrect password. This flag is _o_f_f by default. + incorrect password. This flag is _o_n by default. log_host If set, the host name will be logged in the (non- syslog) ssuuddoo log file. This flag is _o_f_f by default. @@ -713,15 +719,9 @@ SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) does not enter the correct password. This flag is _o_f_f by default. - mail_no_host If set, mail will be sent to the _m_a_i_l_t_o user if the - invoking user exists in the _s_u_d_o_e_r_s file, but is not - allowed to run commands on the current host. This flag - is _o_f_f by default. - - -1.7.3b2 April 7, 2010 11 +1.7.3b3 June 3, 2010 11 @@ -730,6 +730,11 @@ SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) + mail_no_host If set, mail will be sent to the _m_a_i_l_t_o user if the + invoking user exists in the _s_u_d_o_e_r_s file, but is not + allowed to run commands on the current host. This flag + is _o_f_f by default. + mail_no_perms If set, mail will be sent to the _m_a_i_l_t_o user if the invoking user is allowed to use ssuuddoo but the command they are trying is not listed in their _s_u_d_o_e_r_s file @@ -758,10 +763,10 @@ SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) passprompt_override The password prompt specified by _p_a_s_s_p_r_o_m_p_t will - normally only be used if the password prompt provided by - systems such as PAM matches the string "Password:". If - _p_a_s_s_p_r_o_m_p_t___o_v_e_r_r_i_d_e is set, _p_a_s_s_p_r_o_m_p_t will always be - used. This flag is _o_f_f by default. + normally only be used if the password prompt provided + by systems such as PAM matches the string "Password:". + If _p_a_s_s_p_r_o_m_p_t___o_v_e_r_r_i_d_e is set, _p_a_s_s_p_r_o_m_p_t will always + be used. This flag is _o_f_f by default. preserve_groups By default, ssuuddoo will initialize the group vector to the list of groups the target user is in. When @@ -780,14 +785,9 @@ SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) able to determine the length of the password being entered. This flag is _o_f_f by default. - requiretty If set, ssuuddoo will only run when the user is logged in - to a real tty. When this flag is set, ssuuddoo can only be - run from a login session and not via other means such - as _c_r_o_n(1m) or cgi-bin scripts. This flag is _o_f_f by - -1.7.3b2 April 7, 2010 12 +1.7.3b3 June 3, 2010 12 @@ -796,6 +796,10 @@ SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) + requiretty If set, ssuuddoo will only run when the user is logged in + to a real tty. When this flag is set, ssuuddoo can only be + run from a login session and not via other means such + as _c_r_o_n(1m) or cgi-bin scripts. This flag is _o_f_f by default. root_sudo If set, root is allowed to run ssuuddoo too. Disabling @@ -846,14 +850,10 @@ SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) shell as root (the shell is determined by the SHELL environment variable if it is set, falling back on the shell listed in the invoking user's /etc/passwd entry - if not). This flag is _o_f_f by default. - stay_setuid Normally, when ssuuddoo executes a command the real and - effective UIDs are set to the target user (root by - -1.7.3b2 April 7, 2010 13 +1.7.3b3 June 3, 2010 13 @@ -862,6 +862,10 @@ SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) + if not). This flag is _o_f_f by default. + + stay_setuid Normally, when ssuuddoo executes a command the real and + effective UIDs are set to the target user (root by default). This option changes that behavior such that the real UID is left as the invoking user's UID. In other words, this makes ssuuddoo act as a setuid wrapper. @@ -879,25 +883,31 @@ SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) not listed in the passwd database as an argument to the --uu option. This flag is _o_f_f by default. - transcript If set, ssuuddoo will log a transcript of the command being - run, similar to the _s_c_r_i_p_t(1) command. In this mode - ssuuddoo will allocate a new _p_s_e_u_d_o _t_t_y and log all input - and output for the command (except when echo is turned - off as when a password is entered). Transcripts are - logged to the _/_v_a_r_/_l_o_g_/_s_u_d_o_-_s_e_s_s_i_o_n directory with a - unique transcript ID that is included in the normal - ssuuddoo log line, prefixed with _T_S_I_D_=. - - Transcripts may be viewed with the _s_u_d_o_r_e_p_l_a_y(1m) + log_input If set, ssuuddoo will run the command in a _p_s_e_u_d_o _t_t_y and + log all user input. If the standard input is not + connected to the user's tty, due to I/O redirection or + because the command is part of a pipeline, that input + is also captured and stored in a separate log file. + + Input is logged to the _/_v_a_r_/_l_o_g_/_s_u_d_o_-_i_o directory using + a unique session ID that is included in the normal ssuuddoo + log line, prefixed with _T_S_I_D_=. + + log_output If set, ssuuddoo will run the command in a _p_s_e_u_d_o _t_t_y and + log all output that is sent to the screen, similar to + the _s_c_r_i_p_t(1) command. If the standard output or + standard error is not connected to the user's tty, due + to I/O redirection or because the command is part of a + pipeline, that output is also captured and stored in + separate log files. + + Output is logged to the _/_v_a_r_/_l_o_g_/_s_u_d_o_-_i_o directory + using a unique session ID that is included in the + normal ssuuddoo log line, prefixed with _T_S_I_D_=. + + Output logs may be viewed with the _s_u_d_o_r_e_p_l_a_y(1m) utility, which can also be used to list or search the - available transcripts. - - A side effect of this mode is that it will not be - possible to suspend the command being run (because it - is running in a different tty with its own job - control). If a shell is being run, commands executed - by that shell will have normal job control but the - shell itself may not be suspended. + available logs. tty_tickets If set, users must authenticate on a per-tty basis. Normally, ssuuddoo uses a directory in the ticket dir with @@ -906,20 +916,10 @@ SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) user is logged in on in that directory. This flag is _o_f_f by default. - umask_override If set, ssuuddoo will set the umask as specified by _s_u_d_o_e_r_s - without modification. This makes it possible to - specify a more permissive umask in _s_u_d_o_e_r_s than the - user's own umask and matches historical behavior. If - _u_m_a_s_k___o_v_e_r_r_i_d_e is not set, ssuuddoo will set the umask to - be the union of the user's umask and what is specified - in _s_u_d_o_e_r_s. This flag is _o_f_f by default. - - use_loginclass If set, ssuuddoo will apply the defaults specified for the - target user's login class if one exists. Only -1.7.3b2 April 7, 2010 14 +1.7.3b3 June 3, 2010 14 @@ -928,6 +928,16 @@ SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) + umask_override If set, ssuuddoo will set the umask as specified by _s_u_d_o_e_r_s + without modification. This makes it possible to + specify a more permissive umask in _s_u_d_o_e_r_s than the + user's own umask and matches historical behavior. If + _u_m_a_s_k___o_v_e_r_r_i_d_e is not set, ssuuddoo will set the umask to + be the union of the user's umask and what is specified + in _s_u_d_o_e_r_s. This flag is _o_f_f by default. + + use_loginclass If set, ssuuddoo will apply the defaults specified for the + target user's login class if one exists. Only available if ssuuddoo is configured with the --with-logincap option. This flag is _o_f_f by default. @@ -972,20 +982,10 @@ SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) fractional component if minute granularity is insufficient, for example 2.5. The default is 5. Set this to 0 to always prompt for a password. If set to a - value less than 0 the user's timestamp will never - expire. This can be used to allow users to create or - delete their own timestamps via sudo -v and sudo -k - respectively. - umask Umask to use when running the command. Negate this - option or set it to 0777 to preserve the user's umask. - The actual umask that is used will be the union of the - user's umask and 0022. This guarantees that ssuuddoo never - lowers the umask when running a command. Note on - -1.7.3b2 April 7, 2010 15 +1.7.3b3 June 3, 2010 15 @@ -994,6 +994,16 @@ SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) + value less than 0 the user's timestamp will never + expire. This can be used to allow users to create or + delete their own timestamps via sudo -v and sudo -k + respectively. + + umask Umask to use when running the command. Negate this + option or set it to 0777 to preserve the user's umask. + The actual umask that is used will be the union of the + user's umask and 0022. This guarantees that ssuuddoo never + lowers the umask when running a command. Note on systems that use PAM, the default PAM configuration may specify its own umask which will override the value set in _s_u_d_o_e_r_s. @@ -1038,27 +1048,27 @@ SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) for (respects the _r_o_o_t_p_w, _t_a_r_g_e_t_p_w and _r_u_n_a_s_p_w flags in _s_u_d_o_e_r_s) - %U expanded to the login name of the user the command - will be run as (defaults to root) - %u expanded to the invoking user's login name - %% two consecutive % characters are collapsed into a - single % character - The default value is Password:. +1.7.3b3 June 3, 2010 16 -1.7.3b2 April 7, 2010 16 +SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) + %U expanded to the login name of the user the command + will be run as (defaults to root) + %u expanded to the invoking user's login name -SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) + %% two consecutive % characters are collapsed into a + single % character + The default value is Password:. runas_default The default user to run commands as if the --uu option is not specified on the command line. This defaults to @@ -1105,26 +1115,26 @@ SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) Users in this group are exempt from password and PATH requirements. This is not set by default. - lecture This option controls when a short lecture will be printed - along with the password prompt. It has the following - possible values: - always Always lecture the user. - never Never lecture the user. +1.7.3b3 June 3, 2010 17 - once Only lecture the user the first time they run ssuuddoo. -1.7.3b2 April 7, 2010 17 +SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) + lecture This option controls when a short lecture will be printed + along with the password prompt. It has the following + possible values: + always Always lecture the user. -SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) + never Never lecture the user. + once Only lecture the user the first time they run ssuuddoo. If no value is specified, a value of _o_n_c_e is implied. Negating the option results in a value of _n_e_v_e_r being used. @@ -1171,19 +1181,9 @@ SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) quotes (") to protect against ssuuddoo interpreting the @ sign. Defaults to the name of the user running ssuuddoo. - mailto Address to send warning and error mail to. The address - should be enclosed in double quotes (") to protect against - ssuuddoo interpreting the @ sign. Defaults to root. - - secure_path Path used for every command run from ssuuddoo. If you don't - trust the people running ssuuddoo to have a sane PATH - environment variable you may want to use this. Another use - is if you want to have the "root path" be separate from the - "user path." Users in the group specified by the - -1.7.3b2 April 7, 2010 18 +1.7.3b3 June 3, 2010 18 @@ -1192,11 +1192,20 @@ SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) + mailto Address to send warning and error mail to. The address + should be enclosed in double quotes (") to protect against + ssuuddoo interpreting the @ sign. Defaults to root. + + secure_path Path used for every command run from ssuuddoo. If you don't + trust the people running ssuuddoo to have a sane PATH + environment variable you may want to use this. Another use + is if you want to have the "root path" be separate from the + "user path." Users in the group specified by the _e_x_e_m_p_t___g_r_o_u_p option are not affected by _s_e_c_u_r_e___p_a_t_h. This option is not set by default. syslog Syslog facility if syslog is being used for logging (negate - to disable syslog logging). Defaults to local2. + to disable syslog logging). Defaults to authpriv. verifypw This option controls when a password will be required when a user runs ssuuddoo with the --vv option. It has the following @@ -1237,19 +1246,10 @@ SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) environment variables to check is displayed when ssuuddoo is run by root with the _-_V option. - env_delete Environment variables to be removed from the user's - environment when the _e_n_v___r_e_s_e_t option is not in effect. - The argument may be a double-quoted, space-separated - list or a single value without double-quotes. The list - can be replaced, added to, deleted from, or disabled by - using the =, +=, -=, and ! operators respectively. The - default list of environment variables to remove is - displayed when ssuuddoo is run by root with the _-_V option. - Note that many operating systems will remove -1.7.3b2 April 7, 2010 19 +1.7.3b3 June 3, 2010 19 @@ -1258,6 +1258,15 @@ SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) + env_delete Environment variables to be removed from the user's + environment when the _e_n_v___r_e_s_e_t option is not in effect. + The argument may be a double-quoted, space-separated + list or a single value without double-quotes. The list + can be replaced, added to, deleted from, or disabled by + using the =, +=, -=, and ! operators respectively. The + default list of environment variables to remove is + displayed when ssuuddoo is run by root with the _-_V option. + Note that many operating systems will remove potentially dangerous variables from the environment of any setuid process (such as ssuuddoo). @@ -1286,7 +1295,7 @@ FFIILLEESS _/_e_t_c_/_n_e_t_g_r_o_u_p List of network groups - _/_v_a_r_/_l_o_g_/_s_u_d_o_-_s_e_s_s_i_o_n Transcript logs + _/_v_a_r_/_l_o_g_/_s_u_d_o_-_i_o I/O log files EEXXAAMMPPLLEESS Below are example _s_u_d_o_e_r_s entries. Admittedly, some of these are a bit @@ -1303,19 +1312,10 @@ EEXXAAMMPPLLEESS Runas_Alias ADMINGRP = adm, oper # Host alias specification - Host_Alias SPARC = bigtime, eclipse, moet, anchor :\ - SGI = grolsch, dandelion, black :\ - ALPHA = widget, thalamus, foobar :\ - HPPA = boa, nag, python - Host_Alias CUNETS = 128.138.0.0/255.255.0.0 - Host_Alias CSNETS = 128.138.243.0, 128.138.204.0/24, 128.138.242.0 - Host_Alias SERVERS = master, mail, www, ns - Host_Alias CDROM = orion, perseus, hercules - -1.7.3b2 April 7, 2010 20 +1.7.3b3 June 3, 2010 20 @@ -1324,6 +1324,15 @@ EEXXAAMMPPLLEESS SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) + Host_Alias SPARC = bigtime, eclipse, moet, anchor :\ + SGI = grolsch, dandelion, black :\ + ALPHA = widget, thalamus, foobar :\ + HPPA = boa, nag, python + Host_Alias CUNETS = 128.138.0.0/255.255.0.0 + Host_Alias CSNETS = 128.138.243.0, 128.138.204.0/24, 128.138.242.0 + Host_Alias SERVERS = master, mail, www, ns + Host_Alias CDROM = orion, perseus, hercules + # Cmnd alias specification Cmnd_Alias DUMPS = /usr/bin/mt, /usr/sbin/dump, /usr/sbin/rdump,\ /usr/sbin/restore, /usr/sbin/rrestore @@ -1369,26 +1378,27 @@ SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) FULLTIMERS ALL = NOPASSWD: ALL Full time sysadmins (mmiilllleerrtt, mmiikkeeff, and ddoowwddyy) may run any command on - any host without authenticating themselves. - PARTTIMERS ALL = ALL - Part time sysadmins (bboossttlleeyy, jjwwffooxx, and ccrraawwll) may run any command on - any host but they must authenticate themselves first (since the entry - lacks the NOPASSWD tag). - jack CSNETS = ALL +1.7.3b3 June 3, 2010 21 -1.7.3b2 April 7, 2010 21 +SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) + any host without authenticating themselves. -SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) + PARTTIMERS ALL = ALL + + Part time sysadmins (bboossttlleeyy, jjwwffooxx, and ccrraawwll) may run any command on + any host but they must authenticate themselves first (since the entry + lacks the NOPASSWD tag). + jack CSNETS = ALL The user jjaacckk may run any command on the machines in the _C_S_N_E_T_S alias (the networks 128.138.243.0, 128.138.204.0, and 128.138.242.0). Of @@ -1434,27 +1444,28 @@ SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) The user jjiimm may run any command on machines in the _b_i_g_l_a_b netgroup. ssuuddoo knows that "biglab" is a netgroup due to the '+' prefix. - +secretaries ALL = PRINTING, /usr/bin/adduser, /usr/bin/rmuser - Users in the sseeccrreettaarriieess netgroup need to help manage the printers as - well as add and remove users, so they are allowed to run those commands - on all machines. - fred ALL = (DB) NOPASSWD: ALL - The user ffrreedd can run commands as any user in the _D_B Runas_Alias - (oorraaccllee or ssyybbaassee) without giving a password. +1.7.3b3 June 3, 2010 22 -1.7.3b2 April 7, 2010 22 +SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) + +secretaries ALL = PRINTING, /usr/bin/adduser, /usr/bin/rmuser -SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) + Users in the sseeccrreettaarriieess netgroup need to help manage the printers as + well as add and remove users, so they are allowed to run those commands + on all machines. + + fred ALL = (DB) NOPASSWD: ALL + The user ffrreedd can run commands as any user in the _D_B Runas_Alias + (oorraaccllee or ssyybbaassee) without giving a password. john ALPHA = /usr/bin/su [!-]*, !/usr/bin/su *root* @@ -1499,28 +1510,28 @@ SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) SSEECCUURRIITTYY NNOOTTEESS It is generally not effective to "subtract" commands from ALL using the '!' operator. A user can trivially circumvent this by copying the - desired command to a different name and then executing that. For - example: - bill ALL = ALL, !SU, !SHELLS - Doesn't really prevent bbiillll from running the commands listed in _S_U or - _S_H_E_L_L_S since he can simply copy those commands to a different name, or - use a shell escape from an editor or other program. Therefore, these - kind of restrictions should be considered advisory at best (and - reinforced by policy). +1.7.3b3 June 3, 2010 23 -1.7.3b2 April 7, 2010 23 +SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) + desired command to a different name and then executing that. For + example: -SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) + bill ALL = ALL, !SU, !SHELLS + Doesn't really prevent bbiillll from running the commands listed in _S_U or + _S_H_E_L_L_S since he can simply copy those commands to a different name, or + use a shell escape from an editor or other program. Therefore, these + kind of restrictions should be considered advisory at best (and + reinforced by policy). Furthermore, if the _f_a_s_t___g_l_o_b option is in use, it is not possible to reliably negate commands where the path name includes globbing (aka @@ -1565,29 +1576,29 @@ PPRREEVVEENNTTIINNGG SSHHEELLLL EESSCCAAPPEESS emulation are not affected. To tell whether or not ssuuddoo supports _n_o_e_x_e_c, you can run the - following as root: - sudo -V | grep "dummy exec" - If the resulting output contains a line that begins with: - File containing dummy exec functions: +1.7.3b3 June 3, 2010 24 - then ssuuddoo may be able to replace the exec family of functions - in the standard library with its own that simply return an - error. Unfortunately, there is no foolproof way to know -1.7.3b2 April 7, 2010 24 +SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) + following as root: + sudo -V | grep "dummy exec" -SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) + If the resulting output contains a line that begins with: + File containing dummy exec functions: + then ssuuddoo may be able to replace the exec family of functions + in the standard library with its own that simply return an + error. Unfortunately, there is no foolproof way to know whether or not _n_o_e_x_e_c will work at compile-time. _n_o_e_x_e_c should work on SunOS, Solaris, *BSD, Linux, IRIX, Tru64 UNIX, MacOS X, and HP-UX 11.x. It is known nnoott to work on AIX and @@ -1631,29 +1642,29 @@ CCAAVVEEAATTSS BBUUGGSS If you feel you have found a bug in ssuuddoo, please submit a bug report at - http://www.sudo.ws/sudo/bugs/ -SSUUPPPPOORRTT - Limited free support is available via the sudo-users mailing list, see - http://www.sudo.ws/mailman/listinfo/sudo-users to subscribe or search - the archives. -DDIISSCCLLAAIIMMEERR - ssuuddoo is provided ``AS IS'' and any express or implied warranties, - including, but not limited to, the implied warranties of - merchantability and fitness for a particular purpose are disclaimed. +1.7.3b3 June 3, 2010 25 -1.7.3b2 April 7, 2010 25 +SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) -SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) + http://www.sudo.ws/sudo/bugs/ +SSUUPPPPOORRTT + Limited free support is available via the sudo-users mailing list, see + http://www.sudo.ws/mailman/listinfo/sudo-users to subscribe or search + the archives. +DDIISSCCLLAAIIMMEERR + ssuuddoo is provided ``AS IS'' and any express or implied warranties, + including, but not limited to, the implied warranties of + merchantability and fitness for a particular purpose are disclaimed. See the LICENSE file distributed with ssuuddoo or http://www.sudo.ws/sudo/license.html for complete details. @@ -1700,17 +1711,6 @@ SUDOERS(4) MAINTENANCE COMMANDS SUDOERS(4) - - - - - - - - - - - -1.7.3b2 April 7, 2010 26 +1.7.3b3 June 3, 2010 26 diff --git a/sudoers.man.in b/sudoers.man.in index 3a6b38eb0..84bdd5da0 100644 --- a/sudoers.man.in +++ b/sudoers.man.in @@ -18,6 +18,10 @@ .\" Agency (DARPA) and Air Force Research Laboratory, Air Force .\" Materiel Command, USAF, under agreement number F39502-99-1-0512. .\" +.nr SL @SEMAN@ +.nr BA @BAMAN@ +.nr LC @LCMAN@ +.\" .\" Automatically generated by Pod::Man 2.22 (Pod::Simple 3.07) .\" .\" Standard preamble: @@ -144,7 +148,7 @@ .\" ======================================================================== .\" .IX Title "SUDOERS @mansectform@" -.TH SUDOERS @mansectform@ "April 7, 2010" "1.7.3b2" "MAINTENANCE COMMANDS" +.TH SUDOERS @mansectform@ "June 3, 2010" "1.7.3b3" "MAINTENANCE COMMANDS" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l @@ -403,12 +407,18 @@ See \*(L"\s-1SUDOERS\s0 \s-1OPTIONS\s0\*(R" for a list of supported Defaults par \& Cmnd_Spec_List ::= Cmnd_Spec | \& Cmnd_Spec \*(Aq,\*(Aq Cmnd_Spec_List \& -\& Cmnd_Spec ::= Runas_Spec? Tag_Spec* Cmnd +.ie \n(SL \& Cmnd_Spec ::= Runas_Spec? SELinux_Spec? Tag_Spec* Cmnd +.el \& Cmnd_Spec ::= Runas_Spec? Tag_Spec* Cmnd \& \& Runas_Spec ::= \*(Aq(\*(Aq Runas_List? (\*(Aq:\*(Aq Runas_List)? \*(Aq)\*(Aq \& +.if \n(SL \{\ +\& SELinux_Spec ::= (\*(AqROLE=role\*(Aq | \*(AqTYPE=type\*(Aq) +\& +\} \& Tag_Spec ::= (\*(AqNOPASSWD:\*(Aq | \*(AqPASSWD:\*(Aq | \*(AqNOEXEC:\*(Aq | \*(AqEXEC:\*(Aq | -\& \*(AqSETENV:\*(Aq | \*(AqNOSETENV:\*(Aq | \*(AqTRANSCRIPT:\*(Aq | \*(AqNOTRANSCRIPT:\*(Aq) +\& \*(AqSETENV:\*(Aq | \*(AqNOSETENV:\*(Aq | \*(AqLOG_INPUT:\*(Aq | \*(AqNOLOG_INPUT:\*(Aq | +\& \*(AqLOG_OUTPUT:\*(Aq | \*(AqNOLOG_OUTPUT:\*(Aq) .Ve .PP A \fBuser specification\fR determines which commands a user may run @@ -475,15 +485,24 @@ only the group will be set, the command still runs as user \fBtcm\fR. \& tcm boulder = (:dialer) /usr/bin/tip, /usr/bin/cu, \e \& /usr/local/bin/minicom .Ve +.if \n(SL \{\ +.SS "SELinux_Spec" +.IX Subsection "SELinux_Spec" +On systems with SELinux support, \fIsudoers\fR entries may optionally have +an SELinux role and/or type associated with a command. If a role or +type is specified with the command it will override any default values +specified in \fIsudoers\fR. A role or type specified on the command line, +however, will supercede the values in \fIsudoers\fR. +\} .SS "Tag_Spec" .IX Subsection "Tag_Spec" A command may have zero or more tags associated with it. There are eight possible tag values, \f(CW\*(C`NOPASSWD\*(C'\fR, \f(CW\*(C`PASSWD\*(C'\fR, \f(CW\*(C`NOEXEC\*(C'\fR, -\&\f(CW\*(C`EXEC\*(C'\fR, \f(CW\*(C`SETENV\*(C'\fR, \f(CW\*(C`NOSETENV\*(C'\fR, \f(CW\*(C`TRANSCRIPT\*(C'\fR and \f(CW\*(C`NOTRANSCRIPT\*(C'\fR. -Once a tag is set on a \f(CW\*(C`Cmnd\*(C'\fR, subsequent \f(CW\*(C`Cmnd\*(C'\fRs in the -\&\f(CW\*(C`Cmnd_Spec_List\*(C'\fR, inherit the tag unless it is overridden by the -opposite tag (i.e.: \f(CW\*(C`PASSWD\*(C'\fR overrides \f(CW\*(C`NOPASSWD\*(C'\fR and \f(CW\*(C`NOEXEC\*(C'\fR -overrides \f(CW\*(C`EXEC\*(C'\fR). +\&\f(CW\*(C`EXEC\*(C'\fR, \f(CW\*(C`SETENV\*(C'\fR, \f(CW\*(C`NOSETENV\*(C'\fR, \f(CW\*(C`LOG_INPUT\*(C'\fR, \f(CW\*(C`NOLOG_INPUT\*(C'\fR, +\&\f(CW\*(C`LOG_OUTPUT\*(C'\fR and \f(CW\*(C`NOLOG_OUTPUT\*(C'\fR. Once a tag is set on a \f(CW\*(C`Cmnd\*(C'\fR, +subsequent \f(CW\*(C`Cmnd\*(C'\fRs in the \f(CW\*(C`Cmnd_Spec_List\*(C'\fR, inherit the tag unless +it is overridden by the opposite tag (i.e.: \f(CW\*(C`PASSWD\*(C'\fR overrides +\&\f(CW\*(C`NOPASSWD\*(C'\fR and \f(CW\*(C`NOEXEC\*(C'\fR overrides \f(CW\*(C`EXEC\*(C'\fR). .PP \fI\s-1NOPASSWD\s0 and \s-1PASSWD\s0\fR .IX Subsection "NOPASSWD and PASSWD" @@ -547,12 +566,19 @@ variables in this manner. If the command matched is \fB\s-1ALL\s0\fR, the \&\f(CW\*(C`SETENV\*(C'\fR tag is implied for that command; this default may be overridden by use of the \f(CW\*(C`UNSETENV\*(C'\fR tag. .PP -\fI\s-1TRANSCRIPT\s0 and \s-1NOTRANSCRIPT\s0\fR -.IX Subsection "TRANSCRIPT and NOTRANSCRIPT" +\fI\s-1LOG_INPUT\s0 and \s-1NOLOG_INPUT\s0\fR +.IX Subsection "LOG_INPUT and NOLOG_INPUT" +.PP +These tags override the value of the \fIlog_input\fR option on a +per-command basis. For more information, see the description of +\&\fIlog_input\fR in the \*(L"\s-1SUDOERS\s0 \s-1OPTIONS\s0\*(R" section below. +.PP +\fI\s-1LOG_OUTPUT\s0 and \s-1NOLOG_OUTPUT\s0\fR +.IX Subsection "LOG_OUTPUT and NOLOG_OUTPUT" .PP -These tags override the value of the \fItranscript\fR option on a +These tags override the value of the \fIlog_output\fR option on a per-command basis. For more information, see the description of -\&\fItranscript\fR in the \*(L"\s-1SUDOERS\s0 \s-1OPTIONS\s0\*(R" section below. +\&\fIlog_output\fR in the \*(L"\s-1SUDOERS\s0 \s-1OPTIONS\s0\*(R" section below. .SS "Wildcards" .IX Subsection "Wildcards" \&\fBsudo\fR allows shell-style \fIwildcards\fR (aka meta or glob characters) @@ -720,11 +746,11 @@ This flag is \fIon\fR by default. If set, the user may use \fBsudo\fR's \fB\-C\fR option which overrides the default starting point at which \fBsudo\fR begins closing open file descriptors. This flag is \fIoff\fR by default. -.IP "compress_transcript" 16 -.IX Item "compress_transcript" -If set, and the \fItranscript\fR flag is also set, \fBsudo\fR will compress -the transcript logs using \fBzlib\fR. This flag is \fIon\fR by default -when \fBsudo\fR is compiled with \fBzlib\fR support. +.IP "compress_io" 16 +.IX Item "compress_io" +If set, and \fBsudo\fR is configured to log a command's input or output, +the I/O logs will be compressed using \fBzlib\fR. This flag is \fIon\fR +by default when \fBsudo\fR is compiled with \fBzlib\fR support. .IP "env_editor" 16 .IX Item "env_editor" If set, \fBvisudo\fR will use the value of the \s-1EDITOR\s0 or \s-1VISUAL\s0 @@ -946,24 +972,32 @@ of the invoking user. In addition, the timestamp file name will include the target user's name. Note that this flag precludes the use of a uid not listed in the passwd database as an argument to the \fB\-u\fR option. This flag is \fIoff\fR by default. -.IP "transcript" 16 -.IX Item "transcript" -If set, \fBsudo\fR will log a transcript of the command being run, -similar to the \fIscript\fR\|(1) command. In this mode \fBsudo\fR will allocate -a new \fIpseudo tty\fR and log all input and output for the command (except -when echo is turned off as when a password is entered). Transcripts -are logged to the \fI/var/log/sudo\-session\fR directory with a unique -transcript \s-1ID\s0 that is included in the normal \fBsudo\fR log line, -prefixed with \fITSID=\fR. +.IP "log_input" 16 +.IX Item "log_input" +If set, \fBsudo\fR will run the command in a \fIpseudo tty\fR and log all +user input. +If the standard input is not connected to the user's tty, due to +I/O redirection or because the command is part of a pipeline, that +input is also captured and stored in a separate log file. +.Sp +Input is logged to the \fI/var/log/sudo\-io\fR directory using a unique +session \s-1ID\s0 that is included in the normal \fBsudo\fR log line, prefixed +with \fITSID=\fR. +.IP "log_output" 16 +.IX Item "log_output" +If set, \fBsudo\fR will run the command in a \fIpseudo tty\fR and log all +output that is sent to the screen, similar to the \fIscript\fR\|(1) command. +If the standard output or standard error is not connected to the +user's tty, due to I/O redirection or because the command is part +of a pipeline, that output is also captured and stored in separate +log files. .Sp -Transcripts may be viewed with the \fIsudoreplay\fR\|(@mansectsu@) utility, which -can also be used to list or search the available transcripts. +Output is logged to the +\&\fI/var/log/sudo\-io\fR directory using a unique session \s-1ID\s0 that is +included in the normal \fBsudo\fR log line, prefixed with \fITSID=\fR. .Sp -A side effect of this mode is that it will not be possible to suspend -the command being run (because it is running in a different tty -with its own job control). If a shell is being run, commands -executed by that shell will have normal job control but the shell -itself may not be suspended. +Output logs may be viewed with the \fIsudoreplay\fR\|(@mansectsu@) utility, which +can also be used to list or search the available logs. .IP "tty_tickets" 16 .IX Item "tty_tickets" If set, users must authenticate on a per-tty basis. Normally, @@ -979,11 +1013,13 @@ umask in \fIsudoers\fR than the user's own umask and matches historical behavior. If \fIumask_override\fR is not set, \fBsudo\fR will set the umask to be the union of the user's umask and what is specified in \&\fIsudoers\fR. This flag is \fIoff\fR by default. -@LCMAN@.IP "use_loginclass" 16 -@LCMAN@.IX Item "use_loginclass" -@LCMAN@If set, \fBsudo\fR will apply the defaults specified for the target user's -@LCMAN@login class if one exists. Only available if \fBsudo\fR is configured with -@LCMAN@the \-\-with\-logincap option. This flag is \fIoff\fR by default. +.if \n(LC \{\ +.IP "use_loginclass" 16 +.IX Item "use_loginclass" +If set, \fBsudo\fR will apply the defaults specified for the target user's +login class if one exists. Only available if \fBsudo\fR is configured with +the \-\-with\-logincap option. This flag is \fIoff\fR by default. +\} .IP "visiblepw" 16 .IX Item "visiblepw" By default, \fBsudo\fR will refuse to run if the user must enter a @@ -1100,12 +1136,14 @@ two consecutive \f(CW\*(C`%\*(C'\fR characters are collapsed into a single \f(CW .Sp The default value is \f(CW\*(C`@passprompt@\*(C'\fR. .RE -@SEMAN@.IP "role" 16 -@SEMAN@.IX Item "role" -@SEMAN@The default SELinux role to use when constructing a new security -@SEMAN@context to run the command. The default role may be overridden on -@SEMAN@a per-command basis in \fIsudoers\fR or via command line options. -@SEMAN@This option is only available whe \fBsudo\fR is built with SELinux support. +.if \n(SL \{\ +.IP "role" 16 +.IX Item "role" +The default SELinux role to use when constructing a new security +context to run the command. The default role may be overridden on +a per-command basis in \fIsudoers\fR or via command line options. +This option is only available whe \fBsudo\fR is built with SELinux support. +\} .IP "runas_default" 16 .IX Item "runas_default" The default user to run commands as if the \fB\-u\fR option is not specified @@ -1133,12 +1171,14 @@ The default is \fI@timedir@\fR. .IX Item "timestampowner" The owner of the timestamp directory and the timestamps stored therein. The default is \f(CW\*(C`root\*(C'\fR. -@SEMAN@.IP "type" 16 -@SEMAN@.IX Item "type" -@SEMAN@The default SELinux type to use when constructing a new security -@SEMAN@context to run the command. The default type may be overridden on -@SEMAN@a per-command basis in \fIsudoers\fR or via command line options. -@SEMAN@This option is only available whe \fBsudo\fR is built with SELinux support. +.if \n(SL \{\ +.IP "type" 16 +.IX Item "type" +The default SELinux type to use when constructing a new security +context to run the command. The default type may be overridden on +a per-command basis in \fIsudoers\fR or via command line options. +This option is only available whe \fBsudo\fR is built with SELinux support. +\} .PP \&\fBStrings that can be used in a boolean context\fR: .IP "askpass" 12 @@ -1335,9 +1375,9 @@ Local groups file .IP "\fI/etc/netgroup\fR" 24 .IX Item "/etc/netgroup" List of network groups -.IP "\fI/var/log/sudo\-session\fR" 24 -.IX Item "/var/log/sudo-session" -Transcript logs +.IP "\fI/var/log/sudo\-io\fR" 24 +.IX Item "/var/log/sudo-io" +I/O log files .SH "EXAMPLES" .IX Header "EXAMPLES" Below are example \fIsudoers\fR entries. Admittedly, some of diff --git a/sudoers.pod b/sudoers.pod index 560ac428a..aadca50dd 100644 --- a/sudoers.pod +++ b/sudoers.pod @@ -282,7 +282,8 @@ See L<"SUDOERS OPTIONS"> for a list of supported Defaults parameters. SELinux_Spec ::= ('ROLE=role' | 'TYPE=type') Tag_Spec ::= ('NOPASSWD:' | 'PASSWD:' | 'NOEXEC:' | 'EXEC:' | - 'SETENV:' | 'NOSETENV:' | 'TRANSCRIPT:' | 'NOTRANSCRIPT:') + 'SETENV:' | 'NOSETENV:' | 'LOG_INPUT:' | 'NOLOG_INPUT:' | + 'LOG_OUTPUT:' | 'NOLOG_OUTPUT:') A B determines which commands a user may run (and as what user) on specified hosts. By default, commands are @@ -352,11 +353,11 @@ however, will supercede the values in I. A command may have zero or more tags associated with it. There are eight possible tag values, C, C, C, -C, C, C, C and C. -Once a tag is set on a C, subsequent Cs in the -C, inherit the tag unless it is overridden by the -opposite tag (i.e.: C overrides C and C -overrides C). +C, C, C, C, C, +C and C. Once a tag is set on a C, +subsequent Cs in the C, inherit the tag unless +it is overridden by the opposite tag (i.e.: C overrides +C and C overrides C). =head3 NOPASSWD and PASSWD @@ -411,11 +412,17 @@ variables in this manner. If the command matched is B, the C tag is implied for that command; this default may be overridden by use of the C tag. -=head3 TRANSCRIPT and NOTRANSCRIPT +=head3 LOG_INPUT and NOLOG_INPUT -These tags override the value of the I option on a +These tags override the value of the I option on a per-command basis. For more information, see the description of -I in the L<"SUDOERS OPTIONS"> section below. +I in the L<"SUDOERS OPTIONS"> section below. + +=head3 LOG_OUTPUT and NOLOG_OUTPUT + +These tags override the value of the I option on a +per-command basis. For more information, see the description of +I in the L<"SUDOERS OPTIONS"> section below. =head2 Wildcards @@ -600,11 +607,11 @@ If set, the user may use B's B<-C> option which overrides the default starting point at which B begins closing open file descriptors. This flag is I by default. -=item compress_transcript +=item compress_io -If set, and the I flag is also set, B will compress -the transcript logs using B. This flag is I by default -when B is compiled with B support. +If set, and B is configured to log a command's input or output, +the I/O logs will be compressed using B. This flag is I +by default when B is compiled with B support. =item env_editor @@ -857,24 +864,33 @@ include the target user's name. Note that this flag precludes the use of a uid not listed in the passwd database as an argument to the B<-u> option. This flag is I by default. -=item transcript +=item log_input + +If set, B will run the command in a I and log all +user input. +If the standard input is not connected to the user's tty, due to +I/O redirection or because the command is part of a pipeline, that +input is also captured and stored in a separate log file. + +Input is logged to the F directory using a unique +session ID that is included in the normal B log line, prefixed +with I. + +=item log_output -If set, B will log a transcript of the command being run, -similar to the script(1) command. In this mode B will allocate -a new I and log all input and output for the command (except -when echo is turned off as when a password is entered). Transcripts -are logged to the F directory with a unique -transcript ID that is included in the normal B log line, -prefixed with I. +If set, B will run the command in a I and log all +output that is sent to the screen, similar to the script(1) command. +If the standard output or standard error is not connected to the +user's tty, due to I/O redirection or because the command is part +of a pipeline, that output is also captured and stored in separate +log files. -Transcripts may be viewed with the L utility, which -can also be used to list or search the available transcripts. +Output is logged to the +F directory using a unique session ID that is +included in the normal B log line, prefixed with I. -A side effect of this mode is that it will not be possible to suspend -the command being run (because it is running in a different tty -with its own job control). If a shell is being run, commands -executed by that shell will have normal job control but the shell -itself may not be suspended. +Output logs may be viewed with the L utility, which +can also be used to list or search the available logs. =item tty_tickets @@ -1324,9 +1340,9 @@ Local groups file List of network groups -=item F +=item F -Transcript logs +I/O log files =back diff --git a/sudoreplay.c b/sudoreplay.c index a67024a49..855975b0c 100644 --- a/sudoreplay.c +++ b/sudoreplay.c @@ -94,10 +94,10 @@ extern int optind; int Argc; char **Argv; -const char *session_dir = _PATH_SUDO_TRANSCRIPT; +const char *session_dir = _PATH_SUDO_IO_LOGDIR; /* - * Info present in the transcript log file + * Info present in the I/O log file */ struct log_info { char *cwd; @@ -614,7 +614,11 @@ list_session_dir(pathbuf, re, user, tty) pathbuf[plen + 0] = '/'; pathbuf[plen + 1] = dp->d_name[0]; pathbuf[plen + 2] = dp->d_name[1]; - pathbuf[plen + 3] = '\0'; + pathbuf[plen + 3] = '/'; + pathbuf[plen + 4] = 'l'; + pathbuf[plen + 5] = 'o'; + pathbuf[plen + 6] = 'g'; + pathbuf[plen + 7] = '\0'; fp = fopen(pathbuf, "r"); if (fp == NULL) { warning("unable to open %s", pathbuf); @@ -720,6 +724,10 @@ list_sessions(argc, argv, pattern, user, tty) #endif /* HAVE_REGCOMP */ sdlen = strlcpy(pathbuf, session_dir, sizeof(pathbuf)); + if (sdlen + sizeof("/00/00/00/log") >= sizeof(pathbuf)) { + errno = ENAMETOOLONG; + error(1, "%s/00/00/00/log", session_dir); + } /* * Three levels of directory, e.g. 00/00/00 .. ZZ/ZZ/ZZ diff --git a/sudoreplay.cat b/sudoreplay.cat index f32100b06..26f906af6 100644 --- a/sudoreplay.cat +++ b/sudoreplay.cat @@ -38,7 +38,7 @@ OOPPTTIIOONNSS -d _d_i_r_e_c_t_o_r_y Use _d_i_r_e_c_t_o_r_y to for the session logs instead of the - default, _/_v_a_r_/_l_o_g_/_s_u_d_o_-_s_e_s_s_i_o_n_s. + default, _/_v_a_r_/_l_o_g_/_s_u_d_o_-_i_o. -l Enable "list mode". In this mode, ssuuddoorreeppllaayy will list available session IDs. If a _s_e_a_r_c_h _e_x_p_r_e_s_s_i_o_n is @@ -61,7 +61,7 @@ OOPPTTIIOONNSS -1.7.3b2 December 19, 2009 1 +1.7.3b3 June 3, 2010 1 @@ -127,7 +127,7 @@ SUDOREPLAY(1m) MAINTENANCE COMMANDS SUDOREPLAY(1m) -1.7.3b2 December 19, 2009 2 +1.7.3b3 June 3, 2010 2 @@ -193,7 +193,7 @@ SUDOREPLAY(1m) MAINTENANCE COMMANDS SUDOREPLAY(1m) -1.7.3b2 December 19, 2009 3 +1.7.3b3 June 3, 2010 3 @@ -214,17 +214,32 @@ SUDOREPLAY(1m) MAINTENANCE COMMANDS SUDOREPLAY(1m) 10:01 am, September 17, 2009. FFIILLEESS - _/_v_a_r_/_l_o_g_/_s_u_d_o_-_s_e_s_s_i_o_n The default session directory. + _/_v_a_r_/_l_o_g_/_s_u_d_o_-_i_o The default I/O log directory. - _/_v_a_r_/_l_o_g_/_s_u_d_o_-_s_e_s_s_i_o_n_/_0_0_/_0_0_/_0_1 + _/_v_a_r_/_l_o_g_/_s_u_d_o_-_i_o_/_0_0_/_0_0_/_0_1_/_l_o_g Example session log info. - _/_v_a_r_/_l_o_g_/_s_u_d_o_-_s_e_s_s_i_o_n_/_0_0_/_0_0_/_0_1_._s_c_r - Example session transcript file. + _/_v_a_r_/_l_o_g_/_s_u_d_o_-_i_o_/_0_0_/_0_0_/_0_1_/_s_t_d_i_n + Example session standard input log. - _/_v_a_r_/_l_o_g_/_s_u_d_o_-_s_e_s_s_i_o_n_/_0_0_/_0_0_/_0_1_._t_i_m + _/_v_a_r_/_l_o_g_/_s_u_d_o_-_i_o_/_0_0_/_0_0_/_0_1_/_s_t_d_o_u_t + Example session standard output log. + + _/_v_a_r_/_l_o_g_/_s_u_d_o_-_i_o_/_0_0_/_0_0_/_0_1_/_s_t_d_e_r_r + Example session standard error log. + + _/_v_a_r_/_l_o_g_/_s_u_d_o_-_i_o_/_0_0_/_0_0_/_0_1_/_t_t_y_i_n + Example session tty input file. + + _/_v_a_r_/_l_o_g_/_s_u_d_o_-_i_o_/_0_0_/_0_0_/_0_1_/_t_t_y_o_u_t + Example session tty output file. + + _/_v_a_r_/_l_o_g_/_s_u_d_o_-_i_o_/_0_0_/_0_0_/_0_1_/_t_i_m_i_n_g Example session timing file. + Note that the _s_t_d_i_n, _s_t_d_o_u_t and _s_t_d_e_r_r files will be empty unless ssuuddoo + was used as part of a pipeline for a particular command. + EEXXAAMMPPLLEESS List sessions run by user _m_i_l_l_e_r_t: @@ -242,6 +257,17 @@ EEXXAAMMPPLLEESS sudoreplay -l ( user jeff or user bob ) tty console + + +1.7.3b3 June 3, 2010 4 + + + + + +SUDOREPLAY(1m) MAINTENANCE COMMANDS SUDOREPLAY(1m) + + SSEEEE AALLSSOO _s_u_d_o(1m), _s_c_r_i_p_t(1) @@ -257,17 +283,6 @@ SSUUPPPPOORRTT http://www.sudo.ws/mailman/listinfo/sudo-users to subscribe or search the archives. - - -1.7.3b2 December 19, 2009 4 - - - - - -SUDOREPLAY(1m) MAINTENANCE COMMANDS SUDOREPLAY(1m) - - DDIISSCCLLAAIIMMEERR ssuuddoorreeppllaayy is provided ``AS IS'' and any express or implied warranties, including, but not limited to, the implied warranties of @@ -310,21 +325,6 @@ DDIISSCCLLAAIIMMEERR - - - - - - - - - - - - - - - -1.7.3b2 December 19, 2009 5 +1.7.3b3 June 3, 2010 5 diff --git a/sudoreplay.man.in b/sudoreplay.man.in index 93a1a82cf..11d17bcd5 100644 --- a/sudoreplay.man.in +++ b/sudoreplay.man.in @@ -1,4 +1,4 @@ -.\" Copyright (c) 2009 Todd C. Miller +.\" Copyright (c) 2009-2010 Todd C. Miller .\" .\" Permission to use, copy, modify, and distribute this software for any .\" purpose with or without fee is hereby granted, provided that the above @@ -139,7 +139,7 @@ .\" ======================================================================== .\" .IX Title "SUDOREPLAY @mansectsu@" -.TH SUDOREPLAY @mansectsu@ "December 19, 2009" "1.7.3b2" "MAINTENANCE COMMANDS" +.TH SUDOREPLAY @mansectsu@ "June 3, 2010" "1.7.3b3" "MAINTENANCE COMMANDS" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l @@ -179,7 +179,7 @@ Double the playback speed. .IP "\-d \fIdirectory\fR" 12 .IX Item "-d directory" Use \fIdirectory\fR to for the session logs instead of the default, -\&\fI/var/log/sudo\-sessions\fR. +\&\fI/var/log/sudo\-io\fR. .IP "\-l" 12 .IX Item "-l" Enable \*(L"list mode\*(R". In this mode, \fBsudoreplay\fR will list available @@ -320,18 +320,34 @@ The current time but 14 days ago. 10:01 am, September 17, 2009. .SH "FILES" .IX Header "FILES" -.IP "\fI/var/log/sudo\-session\fR" 24 -.IX Item "/var/log/sudo-session" -The default session directory. -.IP "\fI/var/log/sudo\-session/00/00/01\fR" 24 -.IX Item "/var/log/sudo-session/00/00/01" +.IP "\fI/var/log/sudo\-io\fR" 24 +.IX Item "/var/log/sudo-io" +The default I/O log directory. +.IP "\fI/var/log/sudo\-io/00/00/01/log\fR" 24 +.IX Item "/var/log/sudo-io/00/00/01/log" Example session log info. -.IP "\fI/var/log/sudo\-session/00/00/01.scr\fR" 24 -.IX Item "/var/log/sudo-session/00/00/01.scr" -Example session transcript file. -.IP "\fI/var/log/sudo\-session/00/00/01.tim\fR" 24 -.IX Item "/var/log/sudo-session/00/00/01.tim" +.IP "\fI/var/log/sudo\-io/00/00/01/stdin\fR" 24 +.IX Item "/var/log/sudo-io/00/00/01/stdin" +Example session standard input log. +.IP "\fI/var/log/sudo\-io/00/00/01/stdout\fR" 24 +.IX Item "/var/log/sudo-io/00/00/01/stdout" +Example session standard output log. +.IP "\fI/var/log/sudo\-io/00/00/01/stderr\fR" 24 +.IX Item "/var/log/sudo-io/00/00/01/stderr" +Example session standard error log. +.IP "\fI/var/log/sudo\-io/00/00/01/ttyin\fR" 24 +.IX Item "/var/log/sudo-io/00/00/01/ttyin" +Example session tty input file. +.IP "\fI/var/log/sudo\-io/00/00/01/ttyout\fR" 24 +.IX Item "/var/log/sudo-io/00/00/01/ttyout" +Example session tty output file. +.IP "\fI/var/log/sudo\-io/00/00/01/timing\fR" 24 +.IX Item "/var/log/sudo-io/00/00/01/timing" Example session timing file. +.PP +Note that the \fIstdin\fR, \fIstdout\fR and \fIstderr\fR files will be empty +unless \fBsudo\fR was used as part of a pipeline for a particular +command. .SH "EXAMPLES" .IX Header "EXAMPLES" List sessions run by user \fImillert\fR: diff --git a/sudoreplay.pod b/sudoreplay.pod index cf2f29c2c..568de5ce1 100644 --- a/sudoreplay.pod +++ b/sudoreplay.pod @@ -1,4 +1,4 @@ -Copyright (c) 2009 Todd C. Miller +Copyright (c) 2009-2010 Todd C. Miller Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -66,7 +66,7 @@ B accepts the following command line options: =item -d I Use I to for the session logs instead of the default, -F. +F. =item -l @@ -249,24 +249,44 @@ The current time but 14 days ago. =over 24 -=item F +=item F -The default session directory. +The default I/O log directory. -=item F +=item F Example session log info. -=item F +=item F -Example session transcript file. +Example session standard input log. -=item F +=item F + +Example session standard output log. + +=item F + +Example session standard error log. + +=item F + +Example session tty input file. + +=item F + +Example session tty output file. + +=item F Example session timing file. =back +Note that the I, I and I files will be empty +unless B was used as part of a pipeline for a particular +command. + =head1 EXAMPLES List sessions run by user I: diff --git a/term.c b/term.c index 221a28474..a21a4b655 100644 --- a/term.c +++ b/term.c @@ -140,9 +140,8 @@ term_noecho(fd) #if defined(HAVE_TERMIOS_H) || defined(HAVE_TERMIO_H) int -term_raw(fd, opost, isig) +term_raw(fd, isig) int fd; - int opost; int isig; { struct termios term; @@ -157,9 +156,6 @@ term_raw(fd, opost, isig) if (isig) SET(term.c_lflag, ISIG); CLR(term.c_iflag, ICRNL | IGNCR | INLCR | IUCLC | IXON); - /* Only retain output post-processing opost flag set. */ - if (!opost) - CLR(term.c_oflag, OPOST); if (tcsetattr(fd, TCSADRAIN|TCSASOFT, &term) == 0) { changed = 1; return(1); @@ -192,18 +188,14 @@ term_cbreak(fd) } int -term_copy(src, dst, opost) +term_copy(src, dst) int src; int dst; - int opost; { struct termios tt; if (tcgetattr(src, &tt) != 0) return(0); - /* Do not do post-processing unless opost set. */ - if (!opost) - CLR(tt.c_oflag, OPOST); /* XXX - add TCSANOW compat define */ if (tcsetattr(dst, TCSANOW|TCSASOFT, &tt) != 0) return(0); @@ -213,19 +205,17 @@ term_copy(src, dst, opost) #else /* SGTTY */ int -term_raw(fd, onlcr) +term_raw(fd, isig) int fd; - int onlcr; + int isig; { if (!changed && ioctl(fd, TIOCGETP, &oterm) != 0) return(0); (void) memcpy(&term, &oterm, sizeof(term)); /* Set terminal to raw mode */ + /* XXX - how to support isig? */ CLR(term.c_lflag, ECHO); SET(term.sg_flags, RAW); - /* Retain NL to NLCR conversion if onlcr flag set. */ - if (onlcr) - SET(term.sg_flags, CRMOD); if (ioctl(fd, TIOCSETP, &term) == 0) { changed = 1; return(1); @@ -253,10 +243,9 @@ term_cbreak(fd) } int -term_copy(src, dst, onlcr) +term_copy(src, dst) int src; int dst; - int onlcr; { struct sgttyb b; struct tchars tc; @@ -268,9 +257,6 @@ term_copy(src, dst, onlcr) ioctl(src, TIOCLGET, &lb)) { return(0); } - /* Do not convert line endings from NL to NLCR. */ - if (!onlcr) - CLR(b.sg_flags, CRMOD); if (ioctl(dst, TIOCSETP, &b) != 0 || ioctl(dst, TIOCSETC, &tc) != 0 || ioctl(dst, TIOCSLTC, &lc) != 0 || ioctl(dst, TIOCLSET, &lb) != 0 || ioctl(dst, TIOCSETD, &l) != 0) {