2
0
mirror of https://github.com/sudo-project/sudo.git synced 2025-08-29 05:17:54 +00:00
sudo/src/parse_args.c
Todd C. Miller cbf41b8b96 The OpenBSD strtonum() uses very short error strings that can't
be translated usefully.  Convert them to longer strings on error.
Also use the longer strings for atomode() and atoid().
2013-12-11 13:43:10 -07:00

681 lines
20 KiB
C

/*
* Copyright (c) 1993-1996, 1998-2013 Todd C. Miller <Todd.Miller@courtesan.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* Sponsored in part by the Defense Advanced Research Projects
* Agency (DARPA) and Air Force Research Laboratory, Air Force
* Materiel Command, USAF, under agreement number F39502-99-1-0512.
*/
#include <config.h>
#include <sys/types.h>
#include <stdio.h>
#ifdef STDC_HEADERS
# include <stdlib.h>
# include <stddef.h>
#else
# ifdef HAVE_STDLIB_H
# include <stdlib.h>
# endif
#endif /* STDC_HEADERS */
#ifdef HAVE_STRING_H
# include <string.h>
#endif /* HAVE_STRING_H */
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif /* HAVE_STRINGS_H */
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif /* HAVE_UNISTD_H */
#ifdef HAVE_GETOPT_LONG
# include <getopt.h>
# else
# include "compat/getopt.h"
#endif /* HAVE_GETOPT_LONG */
#include <ctype.h>
#include <grp.h>
#include <pwd.h>
#include <sudo_usage.h>
#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);
}