Imported Upstream version 1.8.7
[debian/sudo] / src / parse_args.c
1 /*
2  * Copyright (c) 1993-1996, 1998-2013 Todd C. Miller <Todd.Miller@courtesan.com>
3  *
4  * Permission to use, copy, modify, and distribute this software for any
5  * purpose with or without fee is hereby granted, provided that the above
6  * copyright notice and this permission notice appear in all copies.
7  *
8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15  *
16  * Sponsored in part by the Defense Advanced Research Projects
17  * Agency (DARPA) and Air Force Research Laboratory, Air Force
18  * Materiel Command, USAF, under agreement number F39502-99-1-0512.
19  */
20
21 #include <config.h>
22
23 #include <sys/types.h>
24
25 #include <stdio.h>
26 #ifdef STDC_HEADERS
27 # include <stdlib.h>
28 # include <stddef.h>
29 #else
30 # ifdef HAVE_STDLIB_H
31 #  include <stdlib.h>
32 # endif
33 #endif /* STDC_HEADERS */
34 #ifdef HAVE_STRING_H
35 # include <string.h>
36 #endif /* HAVE_STRING_H */
37 #ifdef HAVE_STRINGS_H
38 # include <strings.h>
39 #endif /* HAVE_STRINGS_H */
40 #ifdef HAVE_UNISTD_H
41 # include <unistd.h>
42 #endif /* HAVE_UNISTD_H */
43 #include <ctype.h>
44 #include <grp.h>
45 #include <pwd.h>
46
47 #include "sudo_usage.h"
48 #include "sudo.h"
49 #include "lbuf.h"
50
51 /* For getopt(3) */
52 extern char *optarg;
53 extern int optind;
54
55 int tgetpass_flags;
56
57 /*
58  * Local functions.
59  */
60 static void help(void) __attribute__((__noreturn__));
61 static void usage_excl(int);
62
63 /*
64  * Mapping of command line flags to name/value settings.
65  */
66 static struct sudo_settings {
67     const char *name;
68     const char *value;
69 } sudo_settings[] = {
70 #define ARG_BSDAUTH_TYPE 0
71     { "bsdauth_type" },
72 #define ARG_LOGIN_CLASS 1
73     { "login_class" },
74 #define ARG_DEBUG_FLAGS 2
75     { "debug_flags" },
76 #define ARG_PRESERVE_ENVIRONMENT 3
77     { "preserve_environment" },
78 #define ARG_RUNAS_GROUP 4
79     { "runas_group" },
80 #define ARG_SET_HOME 5
81     { "set_home" },
82 #define ARG_USER_SHELL 6
83     { "run_shell" },
84 #define ARG_LOGIN_SHELL 7
85     { "login_shell" },
86 #define ARG_IGNORE_TICKET 8
87     { "ignore_ticket" },
88 #define ARG_PROMPT 9
89     { "prompt" },
90 #define ARG_SELINUX_ROLE 10
91     { "selinux_role" },
92 #define ARG_SELINUX_TYPE 11
93     { "selinux_type" },
94 #define ARG_RUNAS_USER 12
95     { "runas_user" },
96 #define ARG_PROGNAME 13
97     { "progname" },
98 #define ARG_IMPLIED_SHELL 14
99     { "implied_shell" },
100 #define ARG_PRESERVE_GROUPS 15
101     { "preserve_groups" },
102 #define ARG_NONINTERACTIVE 16
103     { "noninteractive" },
104 #define ARG_SUDOEDIT 17
105     { "sudoedit" },
106 #define ARG_CLOSEFROM 18
107     { "closefrom" },
108 #define ARG_NET_ADDRS 19
109     { "network_addrs" },
110 #define ARG_MAX_GROUPS 20
111     { "max_groups" },
112 #define ARG_PLUGIN_DIR 21
113     { "plugin_dir" },
114 #define NUM_SETTINGS 22
115     { NULL }
116 };
117
118 /*
119  * Command line argument parsing.
120  * Sets nargc and nargv which corresponds to the argc/argv we'll use
121  * for the command to be run (if we are running one).
122  */
123 int
124 parse_args(int argc, char **argv, int *nargc, char ***nargv, char ***settingsp,
125     char ***env_addp)
126 {
127     int mode = 0;               /* what mode is sudo to be run in? */
128     int flags = 0;              /* mode flags */
129     int valid_flags, ch;
130     int i, j;
131     char *cp, **env_add, **settings;
132     const char *debug_flags;
133     int nenv = 0;
134     int env_size = 32;
135     debug_decl(parse_args, SUDO_DEBUG_ARGS)
136
137     env_add = emalloc2(env_size, sizeof(char *));
138
139     /* Pass progname to plugin so it can call setprogname() */
140     sudo_settings[ARG_PROGNAME].value = getprogname();
141
142     /* First, check to see if we were invoked as "sudoedit". */
143     if (strcmp(getprogname(), "sudoedit") == 0) {
144         mode = MODE_EDIT;
145         sudo_settings[ARG_SUDOEDIT].value = "true";
146     }
147
148     /* Load local IP addresses and masks. */
149     if (get_net_ifs(&cp) > 0)
150         sudo_settings[ARG_NET_ADDRS].value = cp;
151
152     /* Set debug file and flags from sudo.conf. */
153     debug_flags = sudo_conf_debug_flags();
154     if (debug_flags != NULL)
155         sudo_settings[ARG_DEBUG_FLAGS].value = debug_flags;
156
157     /* Set max_groups from sudo.conf. */
158     i = sudo_conf_max_groups();
159     if (i != -1) {
160         easprintf(&cp, "%d", i);
161         sudo_settings[ARG_MAX_GROUPS].value = cp;
162     }
163
164     /* Returns true if the last option string was "--" */
165 #define got_end_of_args (optind > 1 && argv[optind - 1][0] == '-' && \
166             argv[optind - 1][1] == '-' && argv[optind - 1][2] == '\0')
167
168     /* Returns true if next option is an environment variable */
169 #define is_envar (optind < argc && argv[optind][0] != '/' && \
170             strchr(argv[optind], '=') != NULL)
171
172     /* Flags allowed when running a command */
173     valid_flags = MODE_BACKGROUND|MODE_PRESERVE_ENV|MODE_RESET_HOME|
174                   MODE_LOGIN_SHELL|MODE_NONINTERACTIVE|MODE_SHELL;
175     /* XXX - should fill in settings at the end to avoid dupes */
176     for (;;) {
177         /*
178          * We disable arg permutation for GNU getopt().
179          * Some trickiness is required to allow environment variables
180          * to be interspersed with command line options.
181          */
182         if ((ch = getopt(argc, argv, "+Aa:bC:c:D:Eeg:HhiKklnPp:r:Sst:U:u:Vv")) != -1) {
183             switch (ch) {
184                 case 'A':
185                     SET(tgetpass_flags, TGP_ASKPASS);
186                     break;
187 #ifdef HAVE_BSD_AUTH_H
188                 case 'a':
189                     sudo_settings[ARG_BSDAUTH_TYPE].value = optarg;
190                     break;
191 #endif
192                 case 'b':
193                     SET(flags, MODE_BACKGROUND);
194                     break;
195                 case 'C':
196                     if (atoi(optarg) < 3) {
197                         warningx(_("the argument to -C must be a number greater than or equal to 3"));
198                         usage(1);
199                     }
200                     sudo_settings[ARG_CLOSEFROM].value = optarg;
201                     break;
202 #ifdef HAVE_LOGIN_CAP_H
203                 case 'c':
204                     sudo_settings[ARG_LOGIN_CLASS].value = optarg;
205                     break;
206 #endif
207                 case 'D':
208                     /* Ignored for backwards compatibility. */
209                     break;
210                 case 'E':
211                     sudo_settings[ARG_PRESERVE_ENVIRONMENT].value = "true";
212                     break;
213                 case 'e':
214                     if (mode && mode != MODE_EDIT)
215                         usage_excl(1);
216                     mode = MODE_EDIT;
217                     sudo_settings[ARG_SUDOEDIT].value = "true";
218                     valid_flags = MODE_NONINTERACTIVE;
219                     break;
220                 case 'g':
221                     runas_group = optarg;
222                     sudo_settings[ARG_RUNAS_GROUP].value = optarg;
223                     break;
224                 case 'H':
225                     sudo_settings[ARG_SET_HOME].value = "true";
226                     break;
227                 case 'h':
228                     if (mode && mode != MODE_HELP) {
229                         if (strcmp(getprogname(), "sudoedit") != 0)
230                             usage_excl(1);
231                     }
232                     mode = MODE_HELP;
233                     valid_flags = 0;
234                     break;
235                 case 'i':
236                     sudo_settings[ARG_LOGIN_SHELL].value = "true";
237                     SET(flags, MODE_LOGIN_SHELL);
238                     break;
239                 case 'k':
240                     sudo_settings[ARG_IGNORE_TICKET].value = "true";
241                     break;
242                 case 'K':
243                     sudo_settings[ARG_IGNORE_TICKET].value = "true";
244                     if (mode && mode != MODE_KILL)
245                         usage_excl(1);
246                     mode = MODE_KILL;
247                     valid_flags = 0;
248                     break;
249                 case 'l':
250                     if (mode) {
251                         if (mode == MODE_LIST)
252                             SET(flags, MODE_LONG_LIST);
253                         else
254                             usage_excl(1);
255                     }
256                     mode = MODE_LIST;
257                     valid_flags = MODE_NONINTERACTIVE|MODE_LONG_LIST;
258                     break;
259                 case 'n':
260                     SET(flags, MODE_NONINTERACTIVE);
261                     sudo_settings[ARG_NONINTERACTIVE].value = "true";
262                     break;
263                 case 'P':
264                     sudo_settings[ARG_PRESERVE_GROUPS].value = "true";
265                     break;
266                 case 'p':
267                     sudo_settings[ARG_PROMPT].value = optarg;
268                     break;
269 #ifdef HAVE_SELINUX
270                 case 'r':
271                     sudo_settings[ARG_SELINUX_ROLE].value = optarg;
272                     break;
273                 case 't':
274                     sudo_settings[ARG_SELINUX_TYPE].value = optarg;
275                     break;
276 #endif
277                 case 'S':
278                     SET(tgetpass_flags, TGP_STDIN);
279                     break;
280                 case 's':
281                     sudo_settings[ARG_USER_SHELL].value = "true";
282                     SET(flags, MODE_SHELL);
283                     break;
284                 case 'U':
285                     if ((getpwnam(optarg)) == NULL)
286                         fatalx(_("unknown user: %s"), optarg);
287                     list_user = optarg;
288                     break;
289                 case 'u':
290                     runas_user = optarg;
291                     sudo_settings[ARG_RUNAS_USER].value = optarg;
292                     break;
293                 case 'v':
294                     if (mode && mode != MODE_VALIDATE)
295                         usage_excl(1);
296                     mode = MODE_VALIDATE;
297                     valid_flags = MODE_NONINTERACTIVE;
298                     break;
299                 case 'V':
300                     if (mode && mode != MODE_VERSION)
301                         usage_excl(1);
302                     mode = MODE_VERSION;
303                     valid_flags = 0;
304                     break;
305                 default:
306                     usage(1);
307             }
308         } else if (!got_end_of_args && is_envar) {
309             if (nenv == env_size - 2) {
310                 env_size *= 2;
311                 env_add = erealloc3(env_add, env_size, sizeof(char *));
312             }
313             env_add[nenv++] = argv[optind];
314
315             /* Crank optind and resume getopt. */
316             optind++;
317         } else {
318             /* Not an option or an environment variable -- we're done. */
319             break;
320         }
321     }
322     env_add[nenv] = NULL;
323
324     argc -= optind;
325     argv += optind;
326
327     if (!mode) {
328         /* Defer -k mode setting until we know whether it is a flag or not */
329         if (sudo_settings[ARG_IGNORE_TICKET].value != NULL) {
330             if (argc == 0 && !(flags & (MODE_SHELL|MODE_LOGIN_SHELL))) {
331                 mode = MODE_INVALIDATE; /* -k by itself */
332                 sudo_settings[ARG_IGNORE_TICKET].value = NULL;
333                 valid_flags = 0;
334             }
335         }
336         if (!mode)
337             mode = MODE_RUN;            /* running a command */
338     }
339
340     if (argc > 0 && mode == MODE_LIST)
341         mode = MODE_CHECK;
342
343     if (ISSET(flags, MODE_LOGIN_SHELL)) {
344         if (ISSET(flags, MODE_SHELL)) {
345             warningx(_("you may not specify both the `-i' and `-s' options"));
346             usage(1);
347         }
348         if (ISSET(flags, MODE_PRESERVE_ENV)) {
349             warningx(_("you may not specify both the `-i' and `-E' options"));
350             usage(1);
351         }
352         SET(flags, MODE_SHELL);
353     }
354     if ((flags & valid_flags) != flags)
355         usage(1);
356     if (mode == MODE_EDIT &&
357        (ISSET(flags, MODE_PRESERVE_ENV) || env_add[0] != NULL)) {
358         if (ISSET(mode, MODE_PRESERVE_ENV))
359             warningx(_("the `-E' option is not valid in edit mode"));
360         if (env_add[0] != NULL)
361             warningx(_("you may not specify environment variables in edit mode"));
362         usage(1);
363     }
364     if ((runas_user != NULL || runas_group != NULL) &&
365         !ISSET(mode, MODE_EDIT | MODE_RUN | MODE_CHECK | MODE_VALIDATE)) {
366         usage(1);
367     }
368     if (list_user != NULL && mode != MODE_LIST && mode != MODE_CHECK) {
369         warningx(_("the `-U' option may only be used with the `-l' option"));
370         usage(1);
371     }
372     if (ISSET(tgetpass_flags, TGP_STDIN) && ISSET(tgetpass_flags, TGP_ASKPASS)) {
373         warningx(_("the `-A' and `-S' options may not be used together"));
374         usage(1);
375     }
376     if ((argc == 0 && mode == MODE_EDIT) ||
377         (argc > 0 && !ISSET(mode, MODE_RUN | MODE_EDIT | MODE_CHECK)))
378         usage(1);
379     if (argc == 0 && mode == MODE_RUN && !ISSET(flags, MODE_SHELL)) {
380         SET(flags, (MODE_IMPLIED_SHELL | MODE_SHELL));
381         sudo_settings[ARG_IMPLIED_SHELL].value = "true";
382     }
383
384     if (mode == MODE_HELP)
385         help();
386
387     /*
388      * For shell mode we need to rewrite argv
389      */
390     if (ISSET(mode, MODE_RUN) && ISSET(flags, MODE_SHELL)) {
391         char **av, *cmnd = NULL;
392         int ac = 1;
393
394         if (argc != 0) {
395             /* shell -c "command" */
396             char *src, *dst;
397             size_t cmnd_size = (size_t) (argv[argc - 1] - argv[0]) +
398                 strlen(argv[argc - 1]) + 1;
399
400             cmnd = dst = emalloc2(cmnd_size, 2);
401             for (av = argv; *av != NULL; av++) {
402                 for (src = *av; *src != '\0'; src++) {
403                     /* quote potential meta characters */
404                     if (!isalnum((unsigned char)*src) && *src != '_' && *src != '-')
405                         *dst++ = '\\';
406                     *dst++ = *src;
407                 }
408                 *dst++ = ' ';
409             }
410             if (cmnd != dst)
411                 dst--;  /* replace last space with a NUL */
412             *dst = '\0';
413
414             ac += 2; /* -c cmnd */
415         }
416
417         av = emalloc2(ac + 1, sizeof(char *));
418         av[0] = (char *)user_details.shell; /* plugin may override shell */
419         if (cmnd != NULL) {
420             av[1] = "-c";
421             av[2] = cmnd;
422         }
423         av[ac] = NULL;
424
425         argv = av;
426         argc = ac;
427     }
428
429     /*
430      * Format setting_pairs into settings array.
431      */
432 #ifdef _PATH_SUDO_PLUGIN_DIR
433     sudo_settings[ARG_PLUGIN_DIR].value = _PATH_SUDO_PLUGIN_DIR;
434 #endif
435     settings = emalloc2(NUM_SETTINGS + 1, sizeof(char *));
436     for (i = 0, j = 0; i < NUM_SETTINGS; i++) {
437         if (sudo_settings[i].value) {
438             sudo_debug_printf(SUDO_DEBUG_INFO, "settings: %s=%s",
439                 sudo_settings[i].name, sudo_settings[i].value);
440             settings[j] = fmt_string(sudo_settings[i].name,
441                 sudo_settings[i].value);
442             if (settings[j] == NULL)
443                 fatalx(NULL);
444             j++;
445         }
446     }
447     settings[j] = NULL;
448
449     if (mode == MODE_EDIT) {
450 #if defined(HAVE_SETRESUID) || defined(HAVE_SETREUID) || defined(HAVE_SETEUID)
451         /* Must have the command in argv[0]. */
452         argc++;
453         argv--;
454         argv[0] = "sudoedit";
455 #else
456         fatalx(_("sudoedit is not supported on this platform"));
457 #endif
458     }
459
460     *settingsp = settings;
461     *env_addp = env_add;
462     *nargc = argc;
463     *nargv = argv;
464     debug_return_int(mode | flags);
465 }
466
467 static int
468 usage_err(const char *buf)
469 {
470     return fputs(buf, stderr);
471 }
472
473 static int
474 usage_out(const char *buf)
475 {
476     return fputs(buf, stdout);
477 }
478
479 /*
480  * Give usage message and exit.
481  * The actual usage strings are in sudo_usage.h for configure substitution.
482  */
483 void
484 usage(int fatal)
485 {
486     struct lbuf lbuf;
487     char *uvec[6];
488     int i, ulen;
489
490     /*
491      * Use usage vectors appropriate to the progname.
492      */
493     if (strcmp(getprogname(), "sudoedit") == 0) {
494         uvec[0] = SUDO_USAGE5 + 3;
495         uvec[1] = NULL;
496     } else {
497         uvec[0] = SUDO_USAGE1;
498         uvec[1] = SUDO_USAGE2;
499         uvec[2] = SUDO_USAGE3;
500         uvec[3] = SUDO_USAGE4;
501         uvec[4] = SUDO_USAGE5;
502         uvec[5] = NULL;
503     }
504
505     /*
506      * Print usage and wrap lines as needed, depending on the
507      * tty width.
508      */
509     ulen = (int)strlen(getprogname()) + 8;
510     lbuf_init(&lbuf, fatal ? usage_err : usage_out, ulen, NULL,
511         user_details.ts_cols);
512     for (i = 0; uvec[i] != NULL; i++) {
513         lbuf_append(&lbuf, "usage: %s%s", getprogname(), uvec[i]);
514         lbuf_print(&lbuf);
515     }
516     lbuf_destroy(&lbuf);
517     if (fatal)
518         exit(1);
519 }
520
521 /*
522  * Tell which options are mutually exclusive and exit.
523  */
524 static void
525 usage_excl(int fatal)
526 {
527     debug_decl(usage_excl, SUDO_DEBUG_ARGS)
528
529     warningx(_("Only one of the -e, -h, -i, -K, -l, -s, -v or -V options may be specified"));
530     usage(fatal);
531 }
532
533 static void
534 help(void)
535 {
536     struct lbuf lbuf;
537     int indent = 16;
538     const char *pname = getprogname();
539     debug_decl(help, SUDO_DEBUG_ARGS)
540
541     lbuf_init(&lbuf, usage_out, indent, NULL, user_details.ts_cols);
542     if (strcmp(pname, "sudoedit") == 0)
543         lbuf_append(&lbuf, _("%s - edit files as another user\n\n"), pname);
544     else
545         lbuf_append(&lbuf, _("%s - execute a command as another user\n\n"), pname);
546     lbuf_print(&lbuf);
547
548     usage(0);
549
550     lbuf_append(&lbuf, _("\nOptions:\n"));
551     lbuf_append(&lbuf, "  -A            %s",
552         _("use helper program for password prompting\n"));
553 #ifdef HAVE_BSD_AUTH_H
554     lbuf_append(&lbuf, "  -a type       %s",
555         _("use specified BSD authentication type\n"));
556 #endif
557     lbuf_append(&lbuf, "  -b            %s",
558         _("run command in the background\n"));
559     lbuf_append(&lbuf, "  -C fd         %s",
560         _("close all file descriptors >= fd\n"));
561 #ifdef HAVE_LOGIN_CAP_H
562     lbuf_append(&lbuf, "  -c class      %s",
563         _("run command with specified login class\n"));
564 #endif
565     lbuf_append(&lbuf, "  -E            %s",
566         _("preserve user environment when executing command\n"));
567     lbuf_append(&lbuf, "  -e            %s",
568         _("edit files instead of running a command\n"));
569     lbuf_append(&lbuf, "  -g group      %s",
570         _("execute command as the specified group\n"));
571     lbuf_append(&lbuf, "  -H            %s",
572         _("set HOME variable to target user's home dir.\n"));
573     lbuf_append(&lbuf, "  -h            %s",
574         _("display help message and exit\n"));
575     lbuf_append(&lbuf, "  -i [command]  %s",
576         _("run a login shell as target user\n"));
577     lbuf_append(&lbuf, "  -K            %s",
578         _("remove timestamp file completely\n"));
579     lbuf_append(&lbuf, "  -k            %s",
580         _("invalidate timestamp file\n"));
581     lbuf_append(&lbuf, "  -l[l] command %s",
582         _("list user's available commands\n"));
583     lbuf_append(&lbuf, "  -n            %s",
584         _("non-interactive mode, will not prompt user\n"));
585     lbuf_append(&lbuf, "  -P            %s",
586         _("preserve group vector instead of setting to target's\n"));
587     lbuf_append(&lbuf, "  -p prompt     %s",
588         _("use specified password prompt\n"));
589 #ifdef HAVE_SELINUX
590     lbuf_append(&lbuf, "  -r role       %s",
591         _("create SELinux security context with specified role\n"));
592 #endif
593     lbuf_append(&lbuf, "  -S            %s",
594         _("read password from standard input\n"));
595     lbuf_append(&lbuf,
596         "  -s [command]  %s", _("run a shell as target user\n"));
597 #ifdef HAVE_SELINUX
598     lbuf_append(&lbuf, "  -t type       %s",
599         _("create SELinux security context with specified role\n"));
600 #endif
601     lbuf_append(&lbuf, "  -U user       %s",
602         _("when listing, list specified user's privileges\n"));
603     lbuf_append(&lbuf, "  -u user       %s",
604         _("run command (or edit file) as specified user\n"));
605     lbuf_append(&lbuf, "  -V            %s",
606         _("display version information and exit\n"));
607     lbuf_append(&lbuf, "  -v            %s",
608         _("update user's timestamp without running a command\n"));
609     lbuf_append(&lbuf, "  --            %s",
610         _("stop processing command line arguments\n"));
611     lbuf_print(&lbuf);
612     lbuf_destroy(&lbuf);
613     sudo_debug_exit_int(__func__, __FILE__, __LINE__, sudo_debug_subsys, 0);
614     exit(0);
615 }