Imported Upstream version 1.8.2
[debian/sudo] / plugins / sudoers / parse.c
1 /*
2  * Copyright (c) 2004-2005, 2007-2011 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  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
16  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
17  */
18
19 #include <config.h>
20
21 #include <sys/types.h>
22 #include <sys/param.h>
23 #include <stdio.h>
24 #ifdef STDC_HEADERS
25 # include <stdlib.h>
26 # include <stddef.h>
27 #else
28 # ifdef HAVE_STDLIB_H
29 #  include <stdlib.h>
30 # endif
31 #endif /* STDC_HEADERS */
32 #ifdef HAVE_STRING_H
33 # include <string.h>
34 #endif /* HAVE_STRING_H */
35 #ifdef HAVE_STRINGS_H
36 # include <strings.h>
37 #endif /* HAVE_STRINGS_H */
38 #ifdef HAVE_UNISTD_H
39 # include <unistd.h>
40 #endif /* HAVE_UNISTD_H */
41 #include <ctype.h>
42 #include <pwd.h>
43 #include <grp.h>
44
45 #include "sudoers.h"
46 #include "parse.h"
47 #include "lbuf.h"
48 #include <gram.h>
49
50 /* Characters that must be quoted in sudoers */
51 #define SUDOERS_QUOTED  ":\\,=#\""
52
53 /* sudoers nsswitch routines */
54 struct sudo_nss sudo_nss_file = {
55     &sudo_nss_file,
56     NULL,
57     sudo_file_open,
58     sudo_file_close,
59     sudo_file_parse,
60     sudo_file_setdefs,
61     sudo_file_lookup,
62     sudo_file_display_cmnd,
63     sudo_file_display_defaults,
64     sudo_file_display_bound_defaults,
65     sudo_file_display_privs
66 };
67
68 /*
69  * Parser externs.
70  */
71 extern FILE *yyin;
72 extern char *errorfile;
73 extern int errorlineno, parse_error;
74
75 /*
76  * Local prototypes.
77  */
78 static void print_member(struct lbuf *, char *, int, int, int);
79 static int display_bound_defaults(int, struct lbuf *);
80
81 int
82 sudo_file_open(struct sudo_nss *nss)
83 {
84     if (def_ignore_local_sudoers)
85         return -1;
86     nss->handle = open_sudoers(sudoers_file, FALSE, NULL);
87     return nss->handle ? 0 : -1;
88 }
89
90 int
91 sudo_file_close(struct sudo_nss *nss)
92 {
93     /* Free parser data structures and close sudoers file. */
94     init_parser(NULL, 0);
95     if (nss->handle != NULL) {
96         fclose(nss->handle);
97         nss->handle = NULL;
98         yyin = NULL;
99     }
100     return 0;
101 }
102
103 /*
104  * Parse the specified sudoers file.
105  */
106 int
107 sudo_file_parse(struct sudo_nss *nss)
108 {
109     if (nss->handle == NULL)
110         return -1;
111
112     init_parser(sudoers_file, 0);
113     yyin = nss->handle;
114     if (yyparse() != 0 || parse_error) {
115         log_error(NO_EXIT, _("parse error in %s near line %d"),
116             errorfile, errorlineno);
117         return -1;
118     }
119     return 0;
120 }
121
122 /*
123  * Wrapper around update_defaults() for nsswitch code.
124  */
125 int
126 sudo_file_setdefs(struct sudo_nss *nss)
127 {
128     if (nss->handle == NULL)
129         return -1;
130
131     if (!update_defaults(SETDEF_GENERIC|SETDEF_HOST|SETDEF_USER))
132         return -1;
133     return 0;
134 }
135
136 /*
137  * Look up the user in the parsed sudoers file and check to see if they are
138  * allowed to run the specified command on this host as the target user.
139  */
140 int
141 sudo_file_lookup(struct sudo_nss *nss, int validated, int pwflag)
142 {
143     int match, host_match, runas_match, cmnd_match;
144     struct cmndspec *cs;
145     struct cmndtag *tags = NULL;
146     struct privilege *priv;
147     struct userspec *us;
148
149     if (nss->handle == NULL)
150         return validated;
151
152     /*
153      * Only check the actual command if pwflag is not set.
154      * It is set for the "validate", "list" and "kill" pseudo-commands.
155      * Always check the host and user.
156      */
157     if (pwflag) {
158         int nopass;
159         enum def_tuple pwcheck;
160
161         pwcheck = (pwflag == -1) ? never : sudo_defs_table[pwflag].sd_un.tuple;
162         nopass = (pwcheck == all) ? TRUE : FALSE;
163
164         if (list_pw == NULL)
165             SET(validated, FLAG_NO_CHECK);
166         CLR(validated, FLAG_NO_USER);
167         CLR(validated, FLAG_NO_HOST);
168         match = DENY;
169         tq_foreach_fwd(&userspecs, us) {
170             if (userlist_matches(sudo_user.pw, &us->users) != ALLOW)
171                 continue;
172             tq_foreach_fwd(&us->privileges, priv) {
173                 if (hostlist_matches(&priv->hostlist) != ALLOW)
174                     continue;
175                 tq_foreach_fwd(&priv->cmndlist, cs) {
176                     /* Only check the command when listing another user. */
177                     if (user_uid == 0 || list_pw == NULL ||
178                         user_uid == list_pw->pw_uid ||
179                         cmnd_matches(cs->cmnd) == ALLOW)
180                             match = ALLOW;
181                     if ((pwcheck == any && cs->tags.nopasswd == TRUE) ||
182                         (pwcheck == all && cs->tags.nopasswd != TRUE))
183                         nopass = cs->tags.nopasswd;
184                 }
185             }
186         }
187         if (match == ALLOW || user_uid == 0) {
188             /* User has an entry for this host. */
189             SET(validated, VALIDATE_OK);
190         } else if (match == DENY)
191             SET(validated, VALIDATE_NOT_OK);
192         if (pwcheck == always && def_authenticate)
193             SET(validated, FLAG_CHECK_USER);
194         else if (pwcheck == never || nopass == TRUE)
195             def_authenticate = FALSE;
196         return validated;
197     }
198
199     /* Need to be runas user while stat'ing things. */
200     set_perms(PERM_RUNAS);
201
202     match = UNSPEC;
203     tq_foreach_rev(&userspecs, us) {
204         if (userlist_matches(sudo_user.pw, &us->users) != ALLOW)
205             continue;
206         CLR(validated, FLAG_NO_USER);
207         tq_foreach_rev(&us->privileges, priv) {
208             host_match = hostlist_matches(&priv->hostlist);
209             if (host_match == ALLOW)
210                 CLR(validated, FLAG_NO_HOST);
211             else
212                 continue;
213             tq_foreach_rev(&priv->cmndlist, cs) {
214                 runas_match = runaslist_matches(&cs->runasuserlist,
215                     &cs->runasgrouplist);
216                 if (runas_match == ALLOW) {
217                     cmnd_match = cmnd_matches(cs->cmnd);
218                     if (cmnd_match != UNSPEC) {
219                         match = cmnd_match;
220                         tags = &cs->tags;
221 #ifdef HAVE_SELINUX
222                         /* Set role and type if not specified on command line. */
223                         if (user_role == NULL)
224                             user_role = cs->role ? estrdup(cs->role) : def_role;
225                         if (user_type == NULL)
226                             user_type = cs->type ? estrdup(cs->type) : def_type;
227 #endif /* HAVE_SELINUX */
228                         goto matched2;
229                     }
230                 }
231             }
232         }
233     }
234     matched2:
235     if (match == ALLOW) {
236         SET(validated, VALIDATE_OK);
237         CLR(validated, VALIDATE_NOT_OK);
238         if (tags != NULL) {
239             if (tags->nopasswd != UNSPEC)
240                 def_authenticate = !tags->nopasswd;
241             if (tags->noexec != UNSPEC)
242                 def_noexec = tags->noexec;
243             if (tags->setenv != UNSPEC)
244                 def_setenv = tags->setenv;
245             if (tags->log_input != UNSPEC)
246                 def_log_input = tags->log_input;
247             if (tags->log_output != UNSPEC)
248                 def_log_output = tags->log_output;
249         }
250     } else if (match == DENY) {
251         SET(validated, VALIDATE_NOT_OK);
252         CLR(validated, VALIDATE_OK);
253     }
254     restore_perms();
255     return validated;
256 }
257
258 #define TAG_CHANGED(t) \
259         (cs->tags.t != UNSPEC && cs->tags.t != IMPLIED && cs->tags.t != tags->t)
260
261 static void
262 sudo_file_append_cmnd(struct cmndspec *cs, struct cmndtag *tags,
263     struct lbuf *lbuf)
264 {
265     struct member *m;
266
267 #ifdef HAVE_SELINUX
268     if (cs->role)
269         lbuf_append(lbuf, "ROLE=%s ", cs->role);
270     if (cs->type)
271         lbuf_append(lbuf, "TYPE=%s ", cs->type);
272 #endif /* HAVE_SELINUX */
273     if (TAG_CHANGED(setenv)) {
274         lbuf_append(lbuf, cs->tags.setenv ? "SETENV: " : "NOSETENV: ");
275         tags->setenv = cs->tags.setenv;
276     }
277     if (TAG_CHANGED(noexec)) {
278         lbuf_append(lbuf, cs->tags.noexec ? "NOEXEC: " : "EXEC: ");
279         tags->noexec = cs->tags.noexec;
280     }
281     if (TAG_CHANGED(nopasswd)) {
282         lbuf_append(lbuf, cs->tags.nopasswd ? "NOPASSWD: " : "PASSWD: ");
283         tags->nopasswd = cs->tags.nopasswd;
284     }
285     if (TAG_CHANGED(log_input)) {
286         lbuf_append(lbuf, cs->tags.log_input ? "LOG_INPUT: " : "NOLOG_INPUT: ");
287         tags->log_input = cs->tags.log_input;
288     }
289     if (TAG_CHANGED(log_output)) {
290         lbuf_append(lbuf, cs->tags.log_output ? "LOG_OUTPUT: " : "NOLOG_OUTPUT: ");
291         tags->log_output = cs->tags.log_output;
292     }
293     m = cs->cmnd;
294     print_member(lbuf, m->name, m->type, m->negated,
295         CMNDALIAS);
296 }
297
298 static int
299 sudo_file_display_priv_short(struct passwd *pw, struct userspec *us,
300     struct lbuf *lbuf)
301 {
302     struct cmndspec *cs;
303     struct member *m;
304     struct privilege *priv;
305     struct cmndtag tags;
306     int nfound = 0;
307
308     tq_foreach_fwd(&us->privileges, priv) {
309         if (hostlist_matches(&priv->hostlist) != ALLOW)
310             continue;
311         tags.noexec = UNSPEC;
312         tags.setenv = UNSPEC;
313         tags.nopasswd = UNSPEC;
314         tags.log_input = UNSPEC;
315         tags.log_output = UNSPEC;
316         lbuf_append(lbuf, "    ");
317         tq_foreach_fwd(&priv->cmndlist, cs) {
318             if (cs != tq_first(&priv->cmndlist))
319                 lbuf_append(lbuf, ", ");
320             lbuf_append(lbuf, "(");
321             if (!tq_empty(&cs->runasuserlist)) {
322                 tq_foreach_fwd(&cs->runasuserlist, m) {
323                     if (m != tq_first(&cs->runasuserlist))
324                         lbuf_append(lbuf, ", ");
325                     print_member(lbuf, m->name, m->type, m->negated,
326                         RUNASALIAS);
327                 }
328             } else if (tq_empty(&cs->runasgrouplist)) {
329                 lbuf_append(lbuf, "%s", def_runas_default);
330             } else {
331                 lbuf_append(lbuf, "%s", pw->pw_name);
332             }
333             if (!tq_empty(&cs->runasgrouplist)) {
334                 lbuf_append(lbuf, " : ");
335                 tq_foreach_fwd(&cs->runasgrouplist, m) {
336                     if (m != tq_first(&cs->runasgrouplist))
337                         lbuf_append(lbuf, ", ");
338                     print_member(lbuf, m->name, m->type, m->negated,
339                         RUNASALIAS);
340                 }
341             }
342             lbuf_append(lbuf, ") ");
343             sudo_file_append_cmnd(cs, &tags, lbuf);
344             nfound++;
345         }
346         lbuf_append(lbuf, "\n");
347     }
348     return nfound;
349 }
350
351 static int
352 sudo_file_display_priv_long(struct passwd *pw, struct userspec *us,
353     struct lbuf *lbuf)
354 {
355     struct cmndspec *cs;
356     struct member *m;
357     struct privilege *priv;
358     struct cmndtag tags;
359     int nfound = 0;
360
361     tq_foreach_fwd(&us->privileges, priv) {
362         if (hostlist_matches(&priv->hostlist) != ALLOW)
363             continue;
364         tags.noexec = UNSPEC;
365         tags.setenv = UNSPEC;
366         tags.nopasswd = UNSPEC;
367         tags.log_input = UNSPEC;
368         tags.log_output = UNSPEC;
369         lbuf_append(lbuf, _("\nSudoers entry:\n"));
370         tq_foreach_fwd(&priv->cmndlist, cs) {
371             lbuf_append(lbuf, _("    RunAsUsers: "));
372             if (!tq_empty(&cs->runasuserlist)) {
373                 tq_foreach_fwd(&cs->runasuserlist, m) {
374                     if (m != tq_first(&cs->runasuserlist))
375                         lbuf_append(lbuf, ", ");
376                     print_member(lbuf, m->name, m->type, m->negated,
377                         RUNASALIAS);
378                 }
379             } else if (tq_empty(&cs->runasgrouplist)) {
380                 lbuf_append(lbuf, "%s", def_runas_default);
381             } else {
382                 lbuf_append(lbuf, "%s", pw->pw_name);
383             }
384             lbuf_append(lbuf, "\n");
385             if (!tq_empty(&cs->runasgrouplist)) {
386                 lbuf_append(lbuf, _("    RunAsGroups: "));
387                 tq_foreach_fwd(&cs->runasgrouplist, m) {
388                     if (m != tq_first(&cs->runasgrouplist))
389                         lbuf_append(lbuf, ", ");
390                     print_member(lbuf, m->name, m->type, m->negated,
391                         RUNASALIAS);
392                 }
393                 lbuf_append(lbuf, "\n");
394             }
395             lbuf_append(lbuf, _("    Commands:\n\t"));
396             sudo_file_append_cmnd(cs, &tags, lbuf);
397             lbuf_append(lbuf, "\n");
398             nfound++;
399         }
400     }
401     return nfound;
402 }
403
404 int
405 sudo_file_display_privs(struct sudo_nss *nss, struct passwd *pw,
406     struct lbuf *lbuf)
407 {
408     struct userspec *us;
409     int nfound = 0;
410
411     if (nss->handle == NULL)
412         goto done;
413
414     tq_foreach_fwd(&userspecs, us) {
415         if (userlist_matches(pw, &us->users) != ALLOW)
416             continue;
417
418         if (long_list)
419             nfound += sudo_file_display_priv_long(pw, us, lbuf);
420         else
421             nfound += sudo_file_display_priv_short(pw, us, lbuf);
422     }
423 done:
424     return nfound;
425 }
426
427 /*
428  * Display matching Defaults entries for the given user on this host.
429  */
430 int
431 sudo_file_display_defaults(struct sudo_nss *nss, struct passwd *pw,
432     struct lbuf *lbuf)
433 {
434     struct defaults *d;
435     char *prefix;
436     int nfound = 0;
437
438     if (nss->handle == NULL)
439         goto done;
440
441     if (lbuf->len == 0 || isspace((unsigned char)lbuf->buf[lbuf->len - 1]))
442         prefix = "    ";
443     else
444         prefix = ", ";
445
446     tq_foreach_fwd(&defaults, d) {
447         switch (d->type) {
448             case DEFAULTS_HOST:
449                 if (hostlist_matches(&d->binding) != ALLOW)
450                     continue;
451                 break;
452             case DEFAULTS_USER:
453                 if (userlist_matches(pw, &d->binding) != ALLOW)
454                     continue;
455                 break;
456             case DEFAULTS_RUNAS:
457             case DEFAULTS_CMND:
458                 continue;
459         }
460         if (d->val != NULL) {
461             lbuf_append(lbuf, "%s%s%s", prefix, d->var,
462                 d->op == '+' ? "+=" : d->op == '-' ? "-=" : "=");
463             if (strpbrk(d->val, " \t") != NULL) {
464                 lbuf_append(lbuf, "\"");
465                 lbuf_append_quoted(lbuf, "\"", "%s", d->val);
466                 lbuf_append(lbuf, "\"");
467             } else
468                 lbuf_append_quoted(lbuf, SUDOERS_QUOTED, "%s", d->val);
469         } else
470             lbuf_append(lbuf, "%s%s%s", prefix,
471                 d->op == FALSE ? "!" : "", d->var);
472         prefix = ", ";
473         nfound++;
474     }
475 done:
476     return nfound;
477 }
478
479 /*
480  * Display Defaults entries that are per-runas or per-command
481  */
482 int
483 sudo_file_display_bound_defaults(struct sudo_nss *nss, struct passwd *pw,
484     struct lbuf *lbuf)
485 {
486     int nfound = 0;
487
488     /* XXX - should only print ones that match what the user can do. */
489     nfound += display_bound_defaults(DEFAULTS_RUNAS, lbuf);
490     nfound += display_bound_defaults(DEFAULTS_CMND, lbuf);
491
492     return nfound;
493 }
494
495 /*
496  * Display Defaults entries of the given type.
497  */
498 static int
499 display_bound_defaults(int dtype, struct lbuf *lbuf)
500 {
501     struct defaults *d;
502     struct member *m, *binding = NULL;
503     char *dsep;
504     int atype, nfound = 0;
505
506     switch (dtype) {
507         case DEFAULTS_HOST:
508             atype = HOSTALIAS;
509             dsep = "@";
510             break;
511         case DEFAULTS_USER:
512             atype = USERALIAS;
513             dsep = ":";
514             break;
515         case DEFAULTS_RUNAS:
516             atype = RUNASALIAS;
517             dsep = ">";
518             break;
519         case DEFAULTS_CMND:
520             atype = CMNDALIAS;
521             dsep = "!";
522             break;
523         default:
524             return -1;
525     }
526     tq_foreach_fwd(&defaults, d) {
527         if (d->type != dtype)
528             continue;
529
530         nfound++;
531         if (binding != tq_first(&d->binding)) {
532             binding = tq_first(&d->binding);
533             if (nfound != 1)
534                 lbuf_append(lbuf, "\n");
535             lbuf_append(lbuf, "    Defaults%s", dsep);
536             for (m = binding; m != NULL; m = m->next) {
537                 if (m != binding)
538                     lbuf_append(lbuf, ",");
539                 print_member(lbuf, m->name, m->type, m->negated, atype);
540                 lbuf_append(lbuf, " ");
541             }
542         } else
543             lbuf_append(lbuf, ", ");
544         if (d->val != NULL) {
545             lbuf_append(lbuf, "%s%s%s", d->var, d->op == '+' ? "+=" :
546                 d->op == '-' ? "-=" : "=", d->val);
547         } else
548             lbuf_append(lbuf, "%s%s", d->op == FALSE ? "!" : "", d->var);
549     }
550
551     return nfound;
552 }
553
554 int
555 sudo_file_display_cmnd(struct sudo_nss *nss, struct passwd *pw)
556 {
557     struct cmndspec *cs;
558     struct member *match;
559     struct privilege *priv;
560     struct userspec *us;
561     int rval = 1;
562     int host_match, runas_match, cmnd_match;
563
564     if (nss->handle == NULL)
565         goto done;
566
567     match = NULL;
568     tq_foreach_rev(&userspecs, us) {
569         if (userlist_matches(pw, &us->users) != ALLOW)
570             continue;
571
572         tq_foreach_rev(&us->privileges, priv) {
573             host_match = hostlist_matches(&priv->hostlist);
574             if (host_match != ALLOW)
575                 continue;
576             tq_foreach_rev(&priv->cmndlist, cs) {
577                 runas_match = runaslist_matches(&cs->runasuserlist,
578                     &cs->runasgrouplist);
579                 if (runas_match == ALLOW) {
580                     cmnd_match = cmnd_matches(cs->cmnd);
581                     if (cmnd_match != UNSPEC) {
582                         match = host_match && runas_match ? cs->cmnd : NULL;
583                         goto matched;
584                     }
585                 }
586             }
587         }
588     }
589     matched:
590     if (match != NULL && !match->negated) {
591         sudo_printf(SUDO_CONV_INFO_MSG, "%s%s%s\n",
592             safe_cmnd, user_args ? " " : "", user_args ? user_args : "");
593         rval = 0;
594     }
595 done:
596     return rval;
597 }
598
599 /*
600  * Print the contents of a struct member to stdout
601  */
602 static void
603 _print_member(struct lbuf *lbuf, char *name, int type, int negated,
604     int alias_type)
605 {
606     struct alias *a;
607     struct member *m;
608     struct sudo_command *c;
609
610     switch (type) {
611         case ALL:
612             lbuf_append(lbuf, "%sALL", negated ? "!" : "");
613             break;
614         case COMMAND:
615             c = (struct sudo_command *) name;
616             if (negated)
617                 lbuf_append(lbuf, "!");
618             lbuf_append_quoted(lbuf, SUDOERS_QUOTED, "%s", c->cmnd);
619             if (c->args) {
620                 lbuf_append(lbuf, " ");
621                 lbuf_append_quoted(lbuf, SUDOERS_QUOTED, "%s", c->args);
622             }
623             break;
624         case ALIAS:
625             if ((a = alias_find(name, alias_type)) != NULL) {
626                 tq_foreach_fwd(&a->members, m) {
627                     if (m != tq_first(&a->members))
628                         lbuf_append(lbuf, ", ");
629                     _print_member(lbuf, m->name, m->type,
630                         negated ? !m->negated : m->negated, alias_type);
631                 }
632                 break;
633             }
634             /* FALLTHROUGH */
635         default:
636             lbuf_append(lbuf, "%s%s", negated ? "!" : "", name);
637             break;
638     }
639 }
640
641 static void
642 print_member(struct lbuf *lbuf, char *name, int type, int negated,
643     int alias_type)
644 {
645     alias_seqno++;
646     _print_member(lbuf, name, type, negated, alias_type);
647 }