diff --git a/src/su.c b/src/su.c index fc0e826f..08a28861 100644 --- a/src/su.c +++ b/src/su.c @@ -55,7 +55,6 @@ #ident "$Id$" -#include #include #include #include @@ -91,10 +90,12 @@ static bool caller_on_console = false; static /*@only@*/char *caller_pass; #endif #endif /* !USE_PAM */ -static bool doshell = false; +static bool do_interactive_shell = false; static bool fakelogin = false; static /*@observer@*/const char *shellstr; static /*@null@*/char *command = NULL; +static /*@null@*/char *exec_command = NULL; +static int optidx; /* not needed by sulog.c anymore */ @@ -327,11 +328,11 @@ static void prepare_pam_close_session (void) if ( (sigaddset (&ourset, SIGTERM) != 0) || (sigaddset (&ourset, SIGALRM) != 0) || (sigaction (SIGTERM, &action, NULL) != 0) - || ( !doshell /* handle SIGINT (Ctrl-C), SIGQUIT - * (Ctrl-\), and SIGTSTP (Ctrl-Z) - * since the child will not control - * the tty. - */ + || (!do_interactive_shell /* handle SIGINT (Ctrl-C), SIGQUIT + * (Ctrl-\), and SIGTSTP (Ctrl-Z) + * since the child will not control + * the tty. + */ && ( (sigaddset (&ourset, SIGINT) != 0) || (sigaddset (&ourset, SIGQUIT) != 0) || (sigaddset (&ourset, SIGTSTP) != 0) @@ -440,12 +441,14 @@ static void usage (int status) "\n" "Options:\n" " -c, --command COMMAND pass COMMAND to the invoked shell\n" + " -e, --exec PATH run PATH without shell, follow -- with args\n" " -h, --help display this help message and exit\n" " -, -l, --login make the shell a login shell\n" " -m, -p,\n" " --preserve-environment do not reset environment variables, and\n" " keep the same shell\n" " -s, --shell SHELL use SHELL instead of the default in passwd\n" + " -- pass all subsequent arguments on as-is\n" "\n" "If no username is given, assume root.\n"), (E_SUCCESS != status) ? stderr : stdout); exit (status); @@ -760,6 +763,48 @@ static void save_caller_context (char **argv) pw_free (pw); } +/* + * flags_match - test arg against flag candidates + */ +static bool flags_match(const char *arg, const char *a, const char *b, const char *c) +{ + return (a != NULL && strcmp (arg, a) == 0) || + (b != NULL && strcmp (arg, b) == 0) || + (c != NULL && strcmp (arg, c) == 0); +} + +/* is_flag_like - test if arg resembles a flag + * + * lone "--" and bare leading-hyphen-less words are not flag-like, + * everything else is considered a probable flag. + */ +static bool is_flag_like(const char *arg) +{ + if (arg[0] != '-') + return false; + + if (strcmp (arg, "--") == 0) + return false; + + return true; +} + +static void flag_arg_required(const char *arg) +{ + fprintf (stderr, + _("%s: option \'%s\' requires an argument\n"), + Prog, arg); + usage (E_USAGE); +} + +static void flag_unknown(const char *arg) +{ + fprintf (stderr, + _("%s: unrecognized option \'%s\'\n"), + Prog, arg); + usage (E_BAD_ARG); +} + /* * process_flags - Process the command line arguments * @@ -769,51 +814,58 @@ static void save_caller_context (char **argv) */ static void process_flags (int argc, char **argv) { - int c; - static struct option long_options[] = { - {"command", required_argument, NULL, 'c'}, - {"help", no_argument, NULL, 'h'}, - {"login", no_argument, NULL, 'l'}, - {"preserve-environment", no_argument, NULL, 'p'}, - {"shell", required_argument, NULL, 's'}, - {NULL, 0, NULL, '\0'} - }; + for (optidx = 1; optidx < argc; optidx++) { + const char *arg = argv[optidx]; - while ((c = getopt_long (argc, argv, "c:hlmps:", - long_options, NULL)) != -1) { - switch (c) { - case 'c': - command = optarg; - break; - case 'h': + if (flags_match (arg, "--command", "-c", NULL)) { + if (optidx == argc - 1) { + flag_arg_required (arg); + } + + command = argv[++optidx]; + } else if (flags_match (arg, "--exec", "-e", NULL)) { + if (optidx == argc - 1) { + flag_arg_required (arg); + } + + exec_command = argv[++optidx]; + } else if (flags_match (arg, "--help", "-h", NULL)) { usage (E_SUCCESS); - break; - case 'l': + } else if (flags_match (arg, "--login", "-l", "-")) { fakelogin = true; - break; - case 'm': - case 'p': + } else if (flags_match (arg, "--preserve-environment", "-p", "-m")) { /* This will only have an effect if the target * user do not have a restricted shell, or if * su is called by root. */ change_environment = false; + } else if (flags_match (arg, "--shell", "-s", NULL)) { + if (optidx == argc - 1) { + flag_arg_required (arg); + } + + shellstr = argv[++optidx]; + } else if (is_flag_like (arg)) { + flag_unknown (arg); + } else { break; - case 's': - shellstr = optarg; - break; - default: - usage (E_USAGE); /* NOT REACHED */ } } - if ((optind < argc) && (strcmp (argv[optind], "-") == 0)) { - fakelogin = true; - optind++; + if (NULL != exec_command && NULL != command) { + fprintf (stderr, + _("%s: COMMAND and PATH are mutually exclusive\n"), + argv[0]); + usage (E_USAGE); } - if (optind < argc) { - STRFCPY (name, argv[optind++]); /* use this login id */ + if (NULL != exec_command) { + command = exec_command; + } + + /* if next arg is not "--", treat as USER */ + if (optidx < argc && strcmp (argv[optidx], "--")) { + STRFCPY (name, argv[optidx++]); /* use this login id */ } if ('\0' == name[0]) { /* use default user */ struct passwd *root_pw = getpwnam ("root"); @@ -829,9 +881,14 @@ static void process_flags (int argc, char **argv) } } - doshell = (argc == optind); /* any arguments remaining? */ + /* if more and next arg is "--", skip it and leave rest as-is */ + if (optidx < argc && strcmp (argv[optidx], "--") == 0) { + optidx++; + } + + do_interactive_shell = (argc == optidx); /* any arguments remaining? */ if (NULL != command) { - doshell = false; + do_interactive_shell = false; } } @@ -1107,7 +1164,7 @@ int main (int argc, char **argv) set_environment (pw); - if (!doshell) { + if (!do_interactive_shell) { /* There is no need for a controlling terminal. * This avoids the callee to inject commands on * the caller's tty. */ @@ -1175,10 +1232,10 @@ int main (int argc, char **argv) cp = Basename (shellstr); } - if (!doshell) { + if (!do_interactive_shell) { int err; /* Position argv to the remaining arguments */ - argv += optind; + argv += optidx; if (NULL != command) { argv -= 2; argv[0] = "-c"; @@ -1189,10 +1246,18 @@ int main (int argc, char **argv) * with the rest of the command line included. */ argv[-1] = cp; - execve_shell (shellstr, &argv[-1], environ); - err = errno; - (void) fprintf (stderr, - _("Cannot execute %s\n"), shellstr); + + if (NULL != exec_command) { + (void) execve (command, &argv[1], environ); + err = errno; + (void) fprintf (stderr, + _("Cannot execute \'%s\'\n"), command); + } else { + execve_shell (shellstr, &argv[-1], environ); + err = errno; + (void) fprintf (stderr, + _("Cannot execute \'%s\'\n"), shellstr); + } errno = err; } else { (void) shell (shellstr, cp, environ); diff --git a/tests/run_some b/tests/run_some index 2d085d59..fba59bd3 100755 --- a/tests/run_some +++ b/tests/run_some @@ -90,22 +90,28 @@ run_test ./su/02/env_special_root-options_-p_bash run_test ./su/02/env_special_root-options_ run_test ./su/02/env_special_root-options__bash run_test ./su/03/su_run_command01.test -run_test ./su/03/su_run_command02.test -run_test ./su/03/su_run_command03.test +#run_test ./su/03/su_run_command02.test +#run_test ./su/03/su_run_command03.test run_test ./su/03/su_run_command04.test -run_test ./su/03/su_run_command05.test +#run_test ./su/03/su_run_command05.test run_test ./su/03/su_run_command06.test run_test ./su/03/su_run_command07.test -run_test ./su/03/su_run_command08.test -run_test ./su/03/su_run_command09.test +#run_test ./su/03/su_run_command08.test +#run_test ./su/03/su_run_command09.test run_test ./su/03/su_run_command10.test -run_test ./su/03/su_run_command11.test -run_test ./su/03/su_run_command12.test -run_test ./su/03/su_run_command13.test +#run_test ./su/03/su_run_command11.test +#run_test ./su/03/su_run_command12.test +#run_test ./su/03/su_run_command13.test run_test ./su/03/su_run_command14.test run_test ./su/03/su_run_command15.test run_test ./su/03/su_run_command16.test -run_test ./su/03/su_run_command17.test +#run_test ./su/03/su_run_command17.test +run_test ./su/03/su_run_command18.test +run_test ./su/03/su_run_command19.test +run_test ./su/03/su_run_command20.test +run_test ./su/03/su_run_command21.test +run_test ./su/03/su_run_command22.test +run_test ./su/03/su_run_command23.test run_test ./su/04/su_wrong_user.test run_test ./su/04/su_user_wrong_passwd.test run_test ./su/04/su_user_wrong_passwd_syslog.test diff --git a/tests/su/03/data/rootuser.out b/tests/su/03/data/rootuser.out new file mode 100644 index 00000000..9da4483e --- /dev/null +++ b/tests/su/03/data/rootuser.out @@ -0,0 +1,2 @@ +root +args: first second third diff --git a/tests/su/03/data/tsuser.out b/tests/su/03/data/tsuser.out new file mode 100644 index 00000000..4796a548 --- /dev/null +++ b/tests/su/03/data/tsuser.out @@ -0,0 +1,2 @@ +myuser +args: first second third diff --git a/tests/su/03/su_run_command17.test b/tests/su/03/su_run_command17.test index c1f15c5e..024d0da1 100755 --- a/tests/su/03/su_run_command17.test +++ b/tests/su/03/su_run_command17.test @@ -7,6 +7,8 @@ cd $(dirname $0) . ../../common/config.sh . ../../common/log.sh +# FIXME +# su: ignoring --preserve-environment, it's mutually exclusive with --login log_start "$0" "Running commands (check working directory): su -c pwd - myuser -p" diff --git a/tests/su/03/su_run_command18.test b/tests/su/03/su_run_command18.test new file mode 100755 index 00000000..280c3f16 --- /dev/null +++ b/tests/su/03/su_run_command18.test @@ -0,0 +1,50 @@ +#!/bin/sh + +set -e + +cd $(dirname $0) + +. ../../common/config.sh +. ../../common/log.sh + +cat > /tmp/shadow_test_wrap << "EOF" +#!/bin/sh + +whoami +echo args: "$@" +EOF +chmod 755 /tmp/shadow_test_wrap + +log_start "$0" "Running commands: su -c '/tmp/shadow_test_wrap \$1 \$2 \$3' testsuite -- first second third" + +save_config + +# restore the files on exit +trap 'log_status "$0" "FAILURE"; restore_config' 0 + +change_config + +echo "/bin/su -c '/tmp/shadow_test_wrap \$1 \$2 \$3' testsuite -- first second third > tmp/out 2> tmp/err" +/bin/su -c '/tmp/shadow_test_wrap $1 $2 $3' testsuite -- first second third > tmp/out 2> tmp/err + +echo "su reported:" +echo "=== stdout ===" +cat tmp/out +echo "=== stderr ===" +cat tmp/err +echo "==============" + +echo -n "Checking tmp/out..." +diff -au data/tsuser.out tmp/out +rm -f tmp/out +echo "OK" + +echo -n "Checking tmp/err..." +[ "$(wc -c tmp/err)" = "0 tmp/err" ] || false +rm -f tmp/err +echo "OK" + +log_status "$0" "SUCCESS" +restore_config +trap '' 0 + diff --git a/tests/su/03/su_run_command19.test b/tests/su/03/su_run_command19.test new file mode 100755 index 00000000..c4c7b0ad --- /dev/null +++ b/tests/su/03/su_run_command19.test @@ -0,0 +1,50 @@ +#!/bin/sh + +set -e + +cd $(dirname $0) + +. ../../common/config.sh +. ../../common/log.sh + +cat > /tmp/shadow_test_wrap << "EOF" +#!/bin/sh + +whoami +echo args: "$@" +EOF +chmod 755 /tmp/shadow_test_wrap + +log_start "$0" "Running commands: su -c '/tmp/shadow_test_wrap \$1 \$2 \$3' testsuite first second third" + +save_config + +# restore the files on exit +trap 'log_status "$0" "FAILURE"; restore_config' 0 + +change_config + +echo "/bin/su -c '/tmp/shadow_test_wrap \$1 \$2 \$3' testsuite first second third > tmp/out 2> tmp/err" +/bin/su -c '/tmp/shadow_test_wrap $1 $2 $3' testsuite first second third > tmp/out 2> tmp/err + +echo "su reported:" +echo "=== stdout ===" +cat tmp/out +echo "=== stderr ===" +cat tmp/err +echo "==============" + +echo -n "Checking tmp/out..." +diff -au data/tsuser.out tmp/out +rm -f tmp/out +echo "OK" + +echo -n "Checking tmp/err..." +[ "$(wc -c tmp/err)" = "0 tmp/err" ] || false +rm -f tmp/err +echo "OK" + +log_status "$0" "SUCCESS" +restore_config +trap '' 0 + diff --git a/tests/su/03/su_run_command20.test b/tests/su/03/su_run_command20.test new file mode 100755 index 00000000..21393610 --- /dev/null +++ b/tests/su/03/su_run_command20.test @@ -0,0 +1,50 @@ +#!/bin/sh + +set -e + +cd $(dirname $0) + +. ../../common/config.sh +. ../../common/log.sh + +cat > /tmp/shadow_test_wrap << "EOF" +#!/bin/sh + +whoami +echo args: "$@" +EOF +chmod 755 /tmp/shadow_test_wrap + +log_start "$0" "Running commands: su -c '/tmp/shadow_test_wrap \$1 \$2 \$3' - testsuite first second third" + +save_config + +# restore the files on exit +trap 'log_status "$0" "FAILURE"; restore_config' 0 + +change_config + +echo "/bin/su -c '/tmp/shadow_test_wrap \$1 \$2 \$3' - testsuite first second third > tmp/out 2> tmp/err" +/bin/su -c '/tmp/shadow_test_wrap $1 $2 $3' - testsuite first second third > tmp/out 2> tmp/err + +echo "su reported:" +echo "=== stdout ===" +cat tmp/out +echo "=== stderr ===" +cat tmp/err +echo "==============" + +echo -n "Checking tmp/out..." +diff -au data/tsuser.out tmp/out +rm -f tmp/out +echo "OK" + +echo -n "Checking tmp/err..." +[ "$(wc -c tmp/err)" = "0 tmp/err" ] || false +rm -f tmp/err +echo "OK" + +log_status "$0" "SUCCESS" +restore_config +trap '' 0 + diff --git a/tests/su/03/su_run_command21.test b/tests/su/03/su_run_command21.test new file mode 100755 index 00000000..36c9940a --- /dev/null +++ b/tests/su/03/su_run_command21.test @@ -0,0 +1,50 @@ +#!/bin/sh + +set -e + +cd $(dirname $0) + +. ../../common/config.sh +. ../../common/log.sh + +cat > /tmp/shadow_test_wrap << "EOF" +#!/bin/sh + +whoami +echo args: "$@" +EOF +chmod 755 /tmp/shadow_test_wrap + +log_start "$0" "Running commands: su -c '/tmp/shadow_test_wrap \$1 \$2 \$3' - testsuite -- first second third" + +save_config + +# restore the files on exit +trap 'log_status "$0" "FAILURE"; restore_config' 0 + +change_config + +echo "/bin/su -c '/tmp/shadow_test_wrap \$1 \$2 \$3' - testsuite -- first second third > tmp/out 2> tmp/err" +/bin/su -c '/tmp/shadow_test_wrap $1 $2 $3' - testsuite -- first second third > tmp/out 2> tmp/err + +echo "su reported:" +echo "=== stdout ===" +cat tmp/out +echo "=== stderr ===" +cat tmp/err +echo "==============" + +echo -n "Checking tmp/out..." +diff -au data/tsuser.out tmp/out +rm -f tmp/out +echo "OK" + +echo -n "Checking tmp/err..." +[ "$(wc -c tmp/err)" = "0 tmp/err" ] || false +rm -f tmp/err +echo "OK" + +log_status "$0" "SUCCESS" +restore_config +trap '' 0 + diff --git a/tests/su/03/su_run_command22.test b/tests/su/03/su_run_command22.test new file mode 100755 index 00000000..b8024889 --- /dev/null +++ b/tests/su/03/su_run_command22.test @@ -0,0 +1,50 @@ +#!/bin/sh + +set -e + +cd $(dirname $0) + +. ../../common/config.sh +. ../../common/log.sh + +cat > /tmp/shadow_test_wrap << "EOF" +#!/bin/sh + +whoami +echo args: "$@" +EOF +chmod 755 /tmp/shadow_test_wrap + +log_start "$0" "Running commands: su -c '/tmp/shadow_test_wrap \$1 \$2 \$3' - -- first second third" + +save_config + +# restore the files on exit +trap 'log_status "$0" "FAILURE"; restore_config' 0 + +change_config + +echo "/bin/su -c '/tmp/shadow_test_wrap \$1 \$2 \$3' - -- first second third > tmp/out 2> tmp/err" +/bin/su -c '/tmp/shadow_test_wrap $1 $2 $3' - -- first second third > tmp/out 2> tmp/err + +echo "su reported:" +echo "=== stdout ===" +cat tmp/out +echo "=== stderr ===" +cat tmp/err +echo "==============" + +echo -n "Checking tmp/out..." +diff -au data/rootuser.out tmp/out +rm -f tmp/out +echo "OK" + +echo -n "Checking tmp/err..." +[ "$(wc -c tmp/err)" = "0 tmp/err" ] || false +rm -f tmp/err +echo "OK" + +log_status "$0" "SUCCESS" +restore_config +trap '' 0 + diff --git a/tests/su/03/su_run_command23.test b/tests/su/03/su_run_command23.test new file mode 100755 index 00000000..9efec187 --- /dev/null +++ b/tests/su/03/su_run_command23.test @@ -0,0 +1,50 @@ +#!/bin/sh + +set -e + +cd $(dirname $0) + +. ../../common/config.sh +. ../../common/log.sh + +cat > /tmp/shadow_test_wrap << "EOF" +#!/bin/sh + +whoami +echo args: "$@" +EOF +chmod 755 /tmp/shadow_test_wrap + +log_start "$0" "Running commands: su -c '/tmp/shadow_test_wrap \$1 \$2 \$3' -- first second third" + +save_config + +# restore the files on exit +trap 'log_status "$0" "FAILURE"; restore_config' 0 + +change_config + +echo "/bin/su -c '/tmp/shadow_test_wrap \$1 \$2 \$3' -- first second third > tmp/out 2> tmp/err" +/bin/su -c '/tmp/shadow_test_wrap $1 $2 $3' -- first second third > tmp/out 2> tmp/err + +echo "su reported:" +echo "=== stdout ===" +cat tmp/out +echo "=== stderr ===" +cat tmp/err +echo "==============" + +echo -n "Checking tmp/out..." +diff -au data/rootuser.out tmp/out +rm -f tmp/out +echo "OK" + +echo -n "Checking tmp/err..." +[ "$(wc -c tmp/err)" = "0 tmp/err" ] || false +rm -f tmp/err +echo "OK" + +log_status "$0" "SUCCESS" +restore_config +trap '' 0 +