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