/* * Copyright (c) 1993-1996, 1998-2013 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. * * Sponsored in part by the Defense Advanced Research Projects * Agency (DARPA) and Air Force Research Laboratory, Air Force * Materiel Command, USAF, under agreement number F39502-99-1-0512. */ #include #include #include #ifdef STDC_HEADERS # include # include #else # ifdef HAVE_STDLIB_H # include # endif #endif /* STDC_HEADERS */ #ifdef HAVE_STRING_H # include #endif /* HAVE_STRING_H */ #ifdef HAVE_STRINGS_H # include #endif /* HAVE_STRINGS_H */ #ifdef HAVE_UNISTD_H # include #endif /* HAVE_UNISTD_H */ #ifdef HAVE_GETOPT_LONG # include # else # include "compat/getopt.h" #endif /* HAVE_GETOPT_LONG */ #include #include #include #include #include "sudo.h" #include "lbuf.h" int tgetpass_flags; /* * Local functions. */ static void help(void) __attribute__((__noreturn__)); static void usage_excl(int); /* * Mapping of command line flags to name/value settings. */ static struct sudo_settings { const char *name; const char *value; } sudo_settings[] = { #define ARG_BSDAUTH_TYPE 0 { "bsdauth_type" }, #define ARG_LOGIN_CLASS 1 { "login_class" }, #define ARG_DEBUG_FLAGS 2 { "debug_flags" }, #define ARG_PRESERVE_ENVIRONMENT 3 { "preserve_environment" }, #define ARG_RUNAS_GROUP 4 { "runas_group" }, #define ARG_SET_HOME 5 { "set_home" }, #define ARG_USER_SHELL 6 { "run_shell" }, #define ARG_LOGIN_SHELL 7 { "login_shell" }, #define ARG_IGNORE_TICKET 8 { "ignore_ticket" }, #define ARG_PROMPT 9 { "prompt" }, #define ARG_SELINUX_ROLE 10 { "selinux_role" }, #define ARG_SELINUX_TYPE 11 { "selinux_type" }, #define ARG_RUNAS_USER 12 { "runas_user" }, #define ARG_PROGNAME 13 { "progname" }, #define ARG_IMPLIED_SHELL 14 { "implied_shell" }, #define ARG_PRESERVE_GROUPS 15 { "preserve_groups" }, #define ARG_NONINTERACTIVE 16 { "noninteractive" }, #define ARG_SUDOEDIT 17 { "sudoedit" }, #define ARG_CLOSEFROM 18 { "closefrom" }, #define ARG_NET_ADDRS 19 { "network_addrs" }, #define ARG_MAX_GROUPS 20 { "max_groups" }, #define ARG_PLUGIN_DIR 21 { "plugin_dir" }, #define ARG_REMOTE_HOST 22 { "remote_host" }, #define NUM_SETTINGS 23 { NULL } }; /* * Default flags allowed when running a command. */ #define DEFAULT_VALID_FLAGS (MODE_BACKGROUND|MODE_PRESERVE_ENV|MODE_RESET_HOME|MODE_LOGIN_SHELL|MODE_NONINTERACTIVE|MODE_SHELL) /* Option number for the --host long option due to ambiguity of the -h flag. */ #define OPT_HOSTNAME 256 /* * Available command line options, both short and long. * Note that we must disable arg permutation to support setting environment * variables and to better support the optional arg of the -h flag. */ static const char short_opts[] = "+Aa:bC:c:D:Eeg:Hh::iKklnPp:r:Sst:U:u:Vv"; static struct option long_opts[] = { { "askpass", no_argument, NULL, 'A' }, { "auth-type", required_argument, NULL, 'a' }, { "background", no_argument, NULL, 'b' }, { "close-from", required_argument, NULL, 'C' }, { "login-class", required_argument, NULL, 'c' }, { "preserve-env", no_argument, NULL, 'E' }, { "edit", no_argument, NULL, 'e' }, { "group", required_argument, NULL, 'g' }, { "set-home", no_argument, NULL, 'H' }, { "help", no_argument, NULL, 'h' }, { "host", required_argument, NULL, OPT_HOSTNAME }, { "login", no_argument, NULL, 'i' }, { "remove-timestamp", no_argument, NULL, 'K' }, { "reset-timestamp", no_argument, NULL, 'k' }, { "list", no_argument, NULL, 'l' }, { "non-interactive", no_argument, NULL, 'n' }, { "preserve-groups", no_argument, NULL, 'P' }, { "prompt", required_argument, NULL, 'p' }, { "role", required_argument, NULL, 'r' }, { "stdin", no_argument, NULL, 'S' }, { "shell", no_argument, NULL, 's' }, { "type", required_argument, NULL, 't' }, { "other-user", required_argument, NULL, 'U' }, { "user", required_argument, NULL, 'u' }, { "version", no_argument, NULL, 'V' }, { "validate", no_argument, NULL, 'v' }, { NULL, no_argument, NULL, '\0' }, }; /* * Command line argument parsing. * Sets nargc and nargv which corresponds to the argc/argv we'll use * for the command to be run (if we are running one). */ int parse_args(int argc, char **argv, int *nargc, char ***nargv, char ***settingsp, char ***env_addp) { int mode = 0; /* what mode is sudo to be run in? */ int flags = 0; /* mode flags */ int valid_flags = DEFAULT_VALID_FLAGS; int ch, i, j; char *cp, **env_add, **settings; const char *runas_user = NULL; const char *runas_group = NULL; const char *debug_flags; int nenv = 0; int env_size = 32; debug_decl(parse_args, SUDO_DEBUG_ARGS) env_add = emalloc2(env_size, sizeof(char *)); /* Pass progname to plugin so it can call initprogname() */ sudo_settings[ARG_PROGNAME].value = getprogname(); /* First, check to see if we were invoked as "sudoedit". */ if (strcmp(getprogname(), "sudoedit") == 0) { mode = MODE_EDIT; sudo_settings[ARG_SUDOEDIT].value = "true"; } /* Load local IP addresses and masks. */ if (get_net_ifs(&cp) > 0) sudo_settings[ARG_NET_ADDRS].value = cp; /* Set debug file and flags from sudo.conf. */ debug_flags = sudo_conf_debug_flags(); if (debug_flags != NULL) sudo_settings[ARG_DEBUG_FLAGS].value = debug_flags; /* Set max_groups from sudo.conf. */ i = sudo_conf_max_groups(); if (i != -1) { easprintf(&cp, "%d", i); sudo_settings[ARG_MAX_GROUPS].value = cp; } /* Returns true if the last option string was "-h" */ #define got_host_flag (optind > 1 && argv[optind - 1][0] == '-' && \ argv[optind - 1][1] == 'h' && argv[optind - 1][2] == '\0') /* Returns true if the last option string was "--" */ #define got_end_of_args (optind > 1 && argv[optind - 1][0] == '-' && \ argv[optind - 1][1] == '-' && argv[optind - 1][2] == '\0') /* Returns true if next option is an environment variable */ #define is_envar (optind < argc && argv[optind][0] != '/' && \ strchr(argv[optind], '=') != NULL) /* XXX - should fill in settings at the end to avoid dupes */ for (;;) { /* * Some trickiness is required to allow environment variables * to be interspersed with command line options. */ if ((ch = getopt_long(argc, argv, short_opts, long_opts, NULL)) != -1) { switch (ch) { case 'A': SET(tgetpass_flags, TGP_ASKPASS); break; #ifdef HAVE_BSD_AUTH_H case 'a': sudo_settings[ARG_BSDAUTH_TYPE].value = optarg; break; #endif case 'b': SET(flags, MODE_BACKGROUND); break; case 'C': if (strtonum(optarg, 3, INT_MAX, NULL) == 0) { warningx(_("the argument to -C must be a number greater than or equal to 3")); usage(1); } sudo_settings[ARG_CLOSEFROM].value = optarg; break; #ifdef HAVE_LOGIN_CAP_H case 'c': sudo_settings[ARG_LOGIN_CLASS].value = optarg; break; #endif case 'D': /* Ignored for backwards compatibility. */ break; case 'E': sudo_settings[ARG_PRESERVE_ENVIRONMENT].value = "true"; break; case 'e': if (mode && mode != MODE_EDIT) usage_excl(1); mode = MODE_EDIT; sudo_settings[ARG_SUDOEDIT].value = "true"; valid_flags = MODE_NONINTERACTIVE; break; case 'g': runas_group = optarg; sudo_settings[ARG_RUNAS_GROUP].value = optarg; break; case 'H': sudo_settings[ARG_SET_HOME].value = "true"; break; case 'h': if (optarg == NULL) { /* * Optional args support -hhostname, not -h hostname. * If we see a non-option after the -h flag, treat as * remote host and bump optind to skip over it. */ if (got_host_flag && !is_envar && argv[optind] != NULL && argv[optind][0] != '-') { sudo_settings[ARG_REMOTE_HOST].value = argv[optind++]; continue; } if (mode && mode != MODE_HELP) { if (strcmp(getprogname(), "sudoedit") != 0) usage_excl(1); } mode = MODE_HELP; valid_flags = 0; break; } /* FALLTHROUGH */ case OPT_HOSTNAME: sudo_settings[ARG_REMOTE_HOST].value = optarg; break; case 'i': sudo_settings[ARG_LOGIN_SHELL].value = "true"; SET(flags, MODE_LOGIN_SHELL); break; case 'k': sudo_settings[ARG_IGNORE_TICKET].value = "true"; break; case 'K': sudo_settings[ARG_IGNORE_TICKET].value = "true"; if (mode && mode != MODE_KILL) usage_excl(1); mode = MODE_KILL; valid_flags = 0; break; case 'l': if (mode) { if (mode == MODE_LIST) SET(flags, MODE_LONG_LIST); else usage_excl(1); } mode = MODE_LIST; valid_flags = MODE_NONINTERACTIVE|MODE_LONG_LIST; break; case 'n': SET(flags, MODE_NONINTERACTIVE); sudo_settings[ARG_NONINTERACTIVE].value = "true"; break; case 'P': sudo_settings[ARG_PRESERVE_GROUPS].value = "true"; break; case 'p': sudo_settings[ARG_PROMPT].value = optarg; break; #ifdef HAVE_SELINUX case 'r': sudo_settings[ARG_SELINUX_ROLE].value = optarg; break; case 't': sudo_settings[ARG_SELINUX_TYPE].value = optarg; break; #endif case 'S': SET(tgetpass_flags, TGP_STDIN); break; case 's': sudo_settings[ARG_USER_SHELL].value = "true"; SET(flags, MODE_SHELL); break; case 'U': list_user = optarg; break; case 'u': runas_user = optarg; sudo_settings[ARG_RUNAS_USER].value = optarg; break; case 'v': if (mode && mode != MODE_VALIDATE) usage_excl(1); mode = MODE_VALIDATE; valid_flags = MODE_NONINTERACTIVE; break; case 'V': if (mode && mode != MODE_VERSION) usage_excl(1); mode = MODE_VERSION; valid_flags = 0; break; default: usage(1); } } else if (!got_end_of_args && is_envar) { if (nenv == env_size - 2) { env_size *= 2; env_add = erealloc3(env_add, env_size, sizeof(char *)); } env_add[nenv++] = argv[optind]; /* Crank optind and resume getopt. */ optind++; } else { /* Not an option or an environment variable -- we're done. */ break; } } env_add[nenv] = NULL; argc -= optind; argv += optind; if (!mode) { /* Defer -k mode setting until we know whether it is a flag or not */ if (sudo_settings[ARG_IGNORE_TICKET].value != NULL) { if (argc == 0 && !(flags & (MODE_SHELL|MODE_LOGIN_SHELL))) { mode = MODE_INVALIDATE; /* -k by itself */ sudo_settings[ARG_IGNORE_TICKET].value = NULL; valid_flags = 0; } } if (!mode) mode = MODE_RUN; /* running a command */ } if (argc > 0 && mode == MODE_LIST) mode = MODE_CHECK; if (ISSET(flags, MODE_LOGIN_SHELL)) { if (ISSET(flags, MODE_SHELL)) { warningx(U_("you may not specify both the `-i' and `-s' options")); usage(1); } if (ISSET(flags, MODE_PRESERVE_ENV)) { warningx(U_("you may not specify both the `-i' and `-E' options")); usage(1); } SET(flags, MODE_SHELL); } if ((flags & valid_flags) != flags) usage(1); if (mode == MODE_EDIT && (ISSET(flags, MODE_PRESERVE_ENV) || env_add[0] != NULL)) { if (ISSET(mode, MODE_PRESERVE_ENV)) warningx(U_("the `-E' option is not valid in edit mode")); if (env_add[0] != NULL) warningx(U_("you may not specify environment variables in edit mode")); usage(1); } if ((runas_user != NULL || runas_group != NULL) && !ISSET(mode, MODE_EDIT | MODE_RUN | MODE_CHECK | MODE_VALIDATE)) { usage(1); } if (list_user != NULL && mode != MODE_LIST && mode != MODE_CHECK) { warningx(U_("the `-U' option may only be used with the `-l' option")); usage(1); } if (ISSET(tgetpass_flags, TGP_STDIN) && ISSET(tgetpass_flags, TGP_ASKPASS)) { warningx(U_("the `-A' and `-S' options may not be used together")); usage(1); } if ((argc == 0 && mode == MODE_EDIT) || (argc > 0 && !ISSET(mode, MODE_RUN | MODE_EDIT | MODE_CHECK))) usage(1); if (argc == 0 && mode == MODE_RUN && !ISSET(flags, MODE_SHELL)) { SET(flags, (MODE_IMPLIED_SHELL | MODE_SHELL)); sudo_settings[ARG_IMPLIED_SHELL].value = "true"; } if (mode == MODE_HELP) help(); /* * For shell mode we need to rewrite argv */ if (ISSET(mode, MODE_RUN) && ISSET(flags, MODE_SHELL)) { char **av, *cmnd = NULL; int ac = 1; if (argc != 0) { /* shell -c "command" */ char *src, *dst; size_t cmnd_size = (size_t) (argv[argc - 1] - argv[0]) + strlen(argv[argc - 1]) + 1; cmnd = dst = emalloc2(cmnd_size, 2); for (av = argv; *av != NULL; av++) { for (src = *av; *src != '\0'; src++) { /* quote potential meta characters */ if (!isalnum((unsigned char)*src) && *src != '_' && *src != '-' && *src != '$') *dst++ = '\\'; *dst++ = *src; } *dst++ = ' '; } if (cmnd != dst) dst--; /* replace last space with a NUL */ *dst = '\0'; ac += 2; /* -c cmnd */ } av = emalloc2(ac + 1, sizeof(char *)); av[0] = (char *)user_details.shell; /* plugin may override shell */ if (cmnd != NULL) { av[1] = "-c"; av[2] = cmnd; } av[ac] = NULL; argv = av; argc = ac; } /* * Format setting_pairs into settings array. */ #ifdef _PATH_SUDO_PLUGIN_DIR sudo_settings[ARG_PLUGIN_DIR].value = sudo_conf_plugin_dir_path(); #endif settings = emalloc2(NUM_SETTINGS + 1, sizeof(char *)); for (i = 0, j = 0; i < NUM_SETTINGS; i++) { if (sudo_settings[i].value) { sudo_debug_printf(SUDO_DEBUG_INFO, "settings: %s=%s", sudo_settings[i].name, sudo_settings[i].value); settings[j] = fmt_string(sudo_settings[i].name, sudo_settings[i].value); if (settings[j] == NULL) fatal(NULL); j++; } } settings[j] = NULL; if (mode == MODE_EDIT) { #if defined(HAVE_SETRESUID) || defined(HAVE_SETREUID) || defined(HAVE_SETEUID) /* Must have the command in argv[0]. */ argc++; argv--; argv[0] = "sudoedit"; #else fatalx(U_("sudoedit is not supported on this platform")); #endif } *settingsp = settings; *env_addp = env_add; *nargc = argc; *nargv = argv; debug_return_int(mode | flags); } static int usage_err(const char *buf) { return fputs(buf, stderr); } static int usage_out(const char *buf) { return fputs(buf, stdout); } /* * Give usage message and exit. * The actual usage strings are in sudo_usage.h for configure substitution. */ void usage(int fatal) { struct lbuf lbuf; char *uvec[6]; int i, ulen; /* * Use usage vectors appropriate to the progname. */ if (strcmp(getprogname(), "sudoedit") == 0) { uvec[0] = SUDO_USAGE5 + 3; uvec[1] = NULL; } else { uvec[0] = SUDO_USAGE1; uvec[1] = SUDO_USAGE2; uvec[2] = SUDO_USAGE3; uvec[3] = SUDO_USAGE4; uvec[4] = SUDO_USAGE5; uvec[5] = NULL; } /* * Print usage and wrap lines as needed, depending on the * tty width. */ ulen = (int)strlen(getprogname()) + 8; lbuf_init(&lbuf, fatal ? usage_err : usage_out, ulen, NULL, user_details.ts_cols); for (i = 0; uvec[i] != NULL; i++) { lbuf_append(&lbuf, "usage: %s%s", getprogname(), uvec[i]); lbuf_print(&lbuf); } lbuf_destroy(&lbuf); if (fatal) exit(1); } /* * Tell which options are mutually exclusive and exit. */ static void usage_excl(int fatal) { debug_decl(usage_excl, SUDO_DEBUG_ARGS) warningx(U_("Only one of the -e, -h, -i, -K, -l, -s, -v or -V options may be specified")); usage(fatal); } static void help(void) { struct lbuf lbuf; const int indent = 30; const char *pname = getprogname(); debug_decl(help, SUDO_DEBUG_ARGS) lbuf_init(&lbuf, usage_out, indent, NULL, user_details.ts_cols); if (strcmp(pname, "sudoedit") == 0) lbuf_append(&lbuf, _("%s - edit files as another user\n\n"), pname); else lbuf_append(&lbuf, _("%s - execute a command as another user\n\n"), pname); lbuf_print(&lbuf); usage(0); lbuf_append(&lbuf, _("\nOptions:\n")); lbuf_append(&lbuf, " -A, --askpass %s\n", _("use a helper program for password prompting")); #ifdef HAVE_BSD_AUTH_H lbuf_append(&lbuf, " -a, --auth-type=type %s\n", _("use specified BSD authentication type")); #endif lbuf_append(&lbuf, " -b, --background %s\n", _("run command in the background")); lbuf_append(&lbuf, " -C, --close-from=num %s\n", _("close all file descriptors >= num")); #ifdef HAVE_LOGIN_CAP_H lbuf_append(&lbuf, " -c, --login-class=class %s\n", _("run command with the specified BSD login class")); #endif lbuf_append(&lbuf, " -E, --preserve-env %s\n", _("preserve user environment when running command")); lbuf_append(&lbuf, " -e, --edit %s\n", _("edit files instead of running a command")); lbuf_append(&lbuf, " -g, --group=group %s\n", _("run command as the specified group name or ID")); lbuf_append(&lbuf, " -H, --set-home %s\n", _("set HOME variable to target user's home dir")); lbuf_append(&lbuf, " -h, --help %s\n", _("display help message and exit")); lbuf_append(&lbuf, " -h, --host=host %s\n", _("run command on host (if supported by plugin)")); lbuf_append(&lbuf, " -i, --login %s\n", _("run login shell as the target user; a command may also be specified")); lbuf_append(&lbuf, " -K, --remove-timestamp %s\n", _("remove timestamp file completely")); lbuf_append(&lbuf, " -k, --reset-timestamp %s\n", _("invalidate timestamp file")); lbuf_append(&lbuf, " -l, --list %s\n", _("list user's privileges or check a specific command; use twice for longer format")); lbuf_append(&lbuf, " -n, --non-interactive %s\n", _("non-interactive mode, no prompts are used")); lbuf_append(&lbuf, " -P, --preserve-groups %s\n", _("preserve group vector instead of setting to target's")); lbuf_append(&lbuf, " -p, --prompt=prompt %s\n", _("use the specified password prompt")); #ifdef HAVE_SELINUX lbuf_append(&lbuf, " -r, --role=role %s\n", _("create SELinux security context with specified role")); #endif lbuf_append(&lbuf, " -S, --stdin %s\n", _("read password from standard input")); lbuf_append(&lbuf, " -s, --shell %s\n", _("run shell as the target user; a command may also be specified")); #ifdef HAVE_SELINUX lbuf_append(&lbuf, " -t, --type=type %s\n", _("create SELinux security context with specified type")); #endif lbuf_append(&lbuf, " -U, --other-user=user %s\n", _("in list mode, display privileges for user")); lbuf_append(&lbuf, " -u, --user=user %s\n", _("run command (or edit file) as specified user name or ID")); lbuf_append(&lbuf, " -V, --version %s\n", _("display version information and exit")); lbuf_append(&lbuf, " -v, --validate %s\n", _("update user's timestamp without running a command")); lbuf_append(&lbuf, " -- %s\n", _("stop processing command line arguments")); lbuf_print(&lbuf); lbuf_destroy(&lbuf); sudo_debug_exit_int(__func__, __FILE__, __LINE__, sudo_debug_subsys, 0); exit(0); }