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