/* * 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 */ #include #include #include #include "sudo_usage.h" #include "sudo.h" #include "lbuf.h" /* For getopt(3) */ extern char *optarg; extern int optind; 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 NUM_SETTINGS 22 { NULL } }; /* * Command line argument parsing. * Sets nargc and nargv which corresponds to the argc/argv we'll use * for the command to be run (if we are running one). */ int parse_args(int argc, char **argv, int *nargc, char ***nargv, char ***settingsp, char ***env_addp) { int mode = 0; /* what mode is sudo to be run in? */ int flags = 0; /* mode flags */ int valid_flags, ch; int i, j; char *cp, **env_add, **settings; 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 setprogname() */ 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 "--" */ #define got_end_of_args (optind > 1 && argv[optind - 1][0] == '-' && \ argv[optind - 1][1] == '-' && argv[optind - 1][2] == '\0') /* Returns true if next option is an environment variable */ #define is_envar (optind < argc && argv[optind][0] != '/' && \ strchr(argv[optind], '=') != NULL) /* Flags allowed when running a command */ valid_flags = MODE_BACKGROUND|MODE_PRESERVE_ENV|MODE_RESET_HOME| MODE_LOGIN_SHELL|MODE_NONINTERACTIVE|MODE_SHELL; /* XXX - should fill in settings at the end to avoid dupes */ for (;;) { /* * We disable arg permutation for GNU getopt(). * Some trickiness is required to allow environment variables * to be interspersed with command line options. */ if ((ch = getopt(argc, argv, "+Aa:bC:c:D:Eeg:HhiKklnPp:r:Sst:U:u:Vv")) != -1) { switch (ch) { case 'A': SET(tgetpass_flags, TGP_ASKPASS); break; #ifdef HAVE_BSD_AUTH_H case 'a': sudo_settings[ARG_BSDAUTH_TYPE].value = optarg; break; #endif case 'b': SET(flags, MODE_BACKGROUND); break; case 'C': if (atoi(optarg) < 3) { 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 (mode && mode != MODE_HELP) { if (strcmp(getprogname(), "sudoedit") != 0) usage_excl(1); } mode = MODE_HELP; valid_flags = 0; 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': if ((getpwnam(optarg)) == NULL) fatalx(_("unknown user: %s"), optarg); 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(_("you may not specify both the `-i' and `-s' options")); usage(1); } if (ISSET(flags, MODE_PRESERVE_ENV)) { warningx(_("you may not specify both the `-i' and `-E' options")); usage(1); } SET(flags, MODE_SHELL); } if ((flags & valid_flags) != flags) usage(1); if (mode == MODE_EDIT && (ISSET(flags, MODE_PRESERVE_ENV) || env_add[0] != NULL)) { if (ISSET(mode, MODE_PRESERVE_ENV)) warningx(_("the `-E' option is not valid in edit mode")); if (env_add[0] != NULL) warningx(_("you may not specify environment variables in edit mode")); usage(1); } if ((runas_user != NULL || runas_group != NULL) && !ISSET(mode, MODE_EDIT | MODE_RUN | MODE_CHECK | MODE_VALIDATE)) { usage(1); } if (list_user != NULL && mode != MODE_LIST && mode != MODE_CHECK) { warningx(_("the `-U' option may only be used with the `-l' option")); usage(1); } if (ISSET(tgetpass_flags, TGP_STDIN) && ISSET(tgetpass_flags, TGP_ASKPASS)) { warningx(_("the `-A' and `-S' options may not be used together")); usage(1); } if ((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 != '-') *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 = _PATH_SUDO_PLUGIN_DIR; #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) fatalx(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(_("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(_("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; int indent = 16; 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 %s", _("use helper program for password prompting\n")); #ifdef HAVE_BSD_AUTH_H lbuf_append(&lbuf, " -a type %s", _("use specified BSD authentication type\n")); #endif lbuf_append(&lbuf, " -b %s", _("run command in the background\n")); lbuf_append(&lbuf, " -C fd %s", _("close all file descriptors >= fd\n")); #ifdef HAVE_LOGIN_CAP_H lbuf_append(&lbuf, " -c class %s", _("run command with specified login class\n")); #endif lbuf_append(&lbuf, " -E %s", _("preserve user environment when executing command\n")); lbuf_append(&lbuf, " -e %s", _("edit files instead of running a command\n")); lbuf_append(&lbuf, " -g group %s", _("execute command as the specified group\n")); lbuf_append(&lbuf, " -H %s", _("set HOME variable to target user's home dir.\n")); lbuf_append(&lbuf, " -h %s", _("display help message and exit\n")); lbuf_append(&lbuf, " -i [command] %s", _("run a login shell as target user\n")); lbuf_append(&lbuf, " -K %s", _("remove timestamp file completely\n")); lbuf_append(&lbuf, " -k %s", _("invalidate timestamp file\n")); lbuf_append(&lbuf, " -l[l] command %s", _("list user's available commands\n")); lbuf_append(&lbuf, " -n %s", _("non-interactive mode, will not prompt user\n")); lbuf_append(&lbuf, " -P %s", _("preserve group vector instead of setting to target's\n")); lbuf_append(&lbuf, " -p prompt %s", _("use specified password prompt\n")); #ifdef HAVE_SELINUX lbuf_append(&lbuf, " -r role %s", _("create SELinux security context with specified role\n")); #endif lbuf_append(&lbuf, " -S %s", _("read password from standard input\n")); lbuf_append(&lbuf, " -s [command] %s", _("run a shell as target user\n")); #ifdef HAVE_SELINUX lbuf_append(&lbuf, " -t type %s", _("create SELinux security context with specified role\n")); #endif lbuf_append(&lbuf, " -U user %s", _("when listing, list specified user's privileges\n")); lbuf_append(&lbuf, " -u user %s", _("run command (or edit file) as specified user\n")); lbuf_append(&lbuf, " -V %s", _("display version information and exit\n")); lbuf_append(&lbuf, " -v %s", _("update user's timestamp without running a command\n")); lbuf_append(&lbuf, " -- %s", _("stop processing command line arguments\n")); lbuf_print(&lbuf); lbuf_destroy(&lbuf); sudo_debug_exit_int(__func__, __FILE__, __LINE__, sudo_debug_subsys, 0); exit(0); }