1/*
2 * Copyright (c) 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 */
16
17#include <config.h>
18
19#include <sys/types.h>
20#include <sys/param.h>
21#include <stdio.h>
22#ifdef STDC_HEADERS
23# include <stdlib.h>
24# include <stddef.h>
25#else
26# ifdef HAVE_STDLIB_H
27#  include <stdlib.h>
28# endif
29#endif /* STDC_HEADERS */
30#ifdef HAVE_STRING_H
31# include <string.h>
32#endif /* HAVE_STRING_H */
33#ifdef HAVE_STRINGS_H
34# include <strings.h>
35#endif /* HAVE_STRINGS_H */
36#ifdef HAVE_UNISTD_H
37# include <unistd.h>
38#endif /* HAVE_UNISTD_H */
39#include <pwd.h>
40#include <grp.h>
41#include <ctype.h>
42
43#include "sudo.h"
44#include "lbuf.h"
45
46extern struct sudo_nss sudo_nss_file;
47#ifdef HAVE_LDAP
48extern struct sudo_nss sudo_nss_ldap;
49#endif
50
51#if defined(HAVE_LDAP) && defined(_PATH_NSSWITCH_CONF)
52/*
53 * Read in /etc/nsswitch.conf
54 * Returns a tail queue of matches.
55 */
56struct sudo_nss_list *
57sudo_read_nss()
58{
59    FILE *fp;
60    char *cp;
61    int saw_files = FALSE;
62    int saw_ldap = FALSE;
63    int got_match = FALSE;
64    static struct sudo_nss_list snl;
65
66    if ((fp = fopen(_PATH_NSSWITCH_CONF, "r")) == NULL)
67	goto nomatch;
68
69    while ((cp = sudo_parseln(fp)) != NULL) {
70	/* Skip blank or comment lines */
71	if (*cp == '\0')
72	    continue;
73
74	/* Look for a line starting with "sudoers:" */
75	if (strncasecmp(cp, "sudoers:", 8) != 0)
76	    continue;
77
78	/* Parse line */
79	for ((cp = strtok(cp + 8, " \t")); cp != NULL; (cp = strtok(NULL, " \t"))) {
80	    if (strcasecmp(cp, "files") == 0 && !saw_files) {
81		tq_append(&snl, &sudo_nss_file);
82		got_match = TRUE;
83	    } else if (strcasecmp(cp, "ldap") == 0 && !saw_ldap) {
84		tq_append(&snl, &sudo_nss_ldap);
85		got_match = TRUE;
86	    } else if (strcasecmp(cp, "[NOTFOUND=return]") == 0 && got_match) {
87		/* NOTFOUND affects the most recent entry */
88		tq_last(&snl)->ret_if_notfound = TRUE;
89		got_match = FALSE;
90	    } else if (strcasecmp(cp, "[SUCCESS=return]") == 0 && got_match) {
91		/* SUCCESS affects the most recent entry */
92		tq_last(&snl)->ret_if_found = TRUE;
93		got_match = FALSE;
94	    } else
95		got_match = FALSE;
96	}
97	/* Only parse the first "sudoers:" line */
98	break;
99    }
100    fclose(fp);
101
102nomatch:
103    /* Default to files only if no matches */
104    if (tq_empty(&snl))
105	tq_append(&snl, &sudo_nss_file);
106
107    return &snl;
108}
109
110#else /* HAVE_LDAP && _PATH_NSSWITCH_CONF */
111
112# if defined(HAVE_LDAP) && defined(_PATH_NETSVC_CONF)
113
114/*
115 * Read in /etc/netsvc.conf (like nsswitch.conf on AIX)
116 * Returns a tail queue of matches.
117 */
118struct sudo_nss_list *
119sudo_read_nss()
120{
121    FILE *fp;
122    char *cp, *ep;
123    int saw_files = FALSE;
124    int saw_ldap = FALSE;
125    int got_match = FALSE;
126    static struct sudo_nss_list snl;
127
128    if ((fp = fopen(_PATH_NETSVC_CONF, "r")) == NULL)
129	goto nomatch;
130
131    while ((cp = sudo_parseln(fp)) != NULL) {
132	/* Skip blank or comment lines */
133	if (*cp == '\0')
134	    continue;
135
136	/* Look for a line starting with "sudoers = " */
137	if (strncasecmp(cp, "sudoers", 7) != 0)
138	    continue;
139	cp += 7;
140	while (isspace((unsigned char)*cp))
141	    cp++;
142	if (*cp++ != '=')
143	    continue;
144
145	/* Parse line */
146	for ((cp = strtok(cp, ",")); cp != NULL; (cp = strtok(NULL, ","))) {
147	    /* Trim leading whitespace. */
148	    while (isspace((unsigned char)*cp))
149		cp++;
150
151	    if (!saw_files && strncasecmp(cp, "files", 5) == 0 &&
152		(isspace((unsigned char)cp[5]) || cp[5] == '\0')) {
153		tq_append(&snl, &sudo_nss_file);
154		got_match = TRUE;
155		ep = &cp[5];
156	    } else if (!saw_ldap && strncasecmp(cp, "ldap", 4) == 0 &&
157		(isspace((unsigned char)cp[4]) || cp[4] == '\0')) {
158		tq_append(&snl, &sudo_nss_ldap);
159		got_match = TRUE;
160		ep = &cp[4];
161	    } else {
162		got_match = FALSE;
163	    }
164
165	    /* check for = auth qualifier */
166	    if (got_match && *ep) {
167		cp = ep;
168		while (isspace((unsigned char)*cp) || *cp == '=')
169		    cp++;
170		if (strncasecmp(cp, "auth", 4) == 0 &&
171		    (isspace((unsigned char)cp[4]) || cp[4] == '\0')) {
172		    tq_last(&snl)->ret_if_found = TRUE;
173		}
174	    }
175	}
176	/* Only parse the first "sudoers" line */
177	break;
178    }
179    fclose(fp);
180
181nomatch:
182    /* Default to files only if no matches */
183    if (tq_empty(&snl))
184	tq_append(&snl, &sudo_nss_file);
185
186    return &snl;
187}
188
189# else /* !_PATH_NETSVC_CONF && !_PATH_NSSWITCH_CONF */
190
191/*
192 * Non-nsswitch.conf version with hard-coded order.
193 */
194struct sudo_nss_list *
195sudo_read_nss()
196{
197    static struct sudo_nss_list snl;
198
199#  ifdef HAVE_LDAP
200    tq_append(&snl, &sudo_nss_ldap);
201#  endif
202    tq_append(&snl, &sudo_nss_file);
203
204    return &snl;
205}
206
207# endif /* !HAVE_LDAP || !_PATH_NETSVC_CONF */
208
209#endif /* HAVE_LDAP && _PATH_NSSWITCH_CONF */
210
211/* Reset user_groups based on passwd entry. */
212static void
213reset_groups(pw)
214    struct passwd *pw;
215{
216#if defined(HAVE_INITGROUPS) && defined(HAVE_GETGROUPS)
217    if (pw != sudo_user.pw) {
218# ifdef HAVE_SETAUTHDB
219	aix_setauthdb(pw->pw_name);
220# endif
221	if (initgroups(pw->pw_name, pw->pw_gid) == -1)
222	    log_fatal(USE_ERRNO|MSG_ONLY, "can't reset group vector");
223	efree(user_groups);
224	user_groups = NULL;
225	if ((user_ngroups = getgroups(0, NULL)) > 0) {
226	    user_groups = emalloc2(user_ngroups, sizeof(GETGROUPS_T));
227	    if (getgroups(user_ngroups, user_groups) < 0)
228		log_fatal(USE_ERRNO|MSG_ONLY, "can't get group vector");
229	}
230# ifdef HAVE_SETAUTHDB
231	aix_restoreauthdb();
232# endif
233    }
234#endif /* HAVE_INITGROUPS && HAVE_GETGROUPS */
235}
236
237static int
238output(buf)
239    const char *buf;
240{
241    return fputs(buf, stdout);
242}
243
244/*
245 * Print out privileges for the specified user.
246 * We only get here if the user is allowed to run something on this host.
247 */
248void
249display_privs(snl, pw)
250    struct sudo_nss_list *snl;
251    struct passwd *pw;
252{
253    struct sudo_nss *nss;
254    struct lbuf defs, privs;
255    int count, olen;
256
257    /* Reset group vector so group matching works correctly. */
258    reset_groups(pw);
259
260    lbuf_init(&defs, output, 4, NULL);
261    lbuf_init(&privs, output, 4, NULL);
262
263    /* Display defaults from all sources. */
264    lbuf_append(&defs, "Matching Defaults entries for %s on this host:\n",
265	pw->pw_name);
266    count = 0;
267    tq_foreach_fwd(snl, nss) {
268	count += nss->display_defaults(nss, pw, &defs);
269    }
270    if (count)
271	lbuf_append(&defs, "\n\n");
272    else
273	defs.len = 0;
274
275    /* Display Runas and Cmnd-specific defaults from all sources. */
276    olen = defs.len;
277    lbuf_append(&defs, "Runas and Command-specific defaults for %s:\n",
278	pw->pw_name);
279    count = 0;
280    tq_foreach_fwd(snl, nss) {
281	count += nss->display_bound_defaults(nss, pw, &defs);
282    }
283    if (count)
284	lbuf_append(&defs, "\n\n");
285    else
286	defs.len = olen;
287
288    /* Display privileges from all sources. */
289    lbuf_append(&privs,
290	"User %s may run the following commands on this host:\n", pw->pw_name);
291    count = 0;
292    tq_foreach_fwd(snl, nss) {
293	count += nss->display_privs(nss, pw, &privs);
294    }
295    if (count == 0) {
296	defs.len = 0;
297	privs.len = 0;
298	lbuf_append(&privs, "User %s is not allowed to run sudo on %s.\n",
299	    pw->pw_name, user_shost);
300    }
301    lbuf_print(&defs);
302    lbuf_print(&privs);
303
304    lbuf_destroy(&defs);
305    lbuf_destroy(&privs);
306}
307
308/*
309 * Check user_cmnd against sudoers and print the matching entry if the
310 * command is allowed.
311 */
312int
313display_cmnd(snl, pw)
314    struct sudo_nss_list *snl;
315    struct passwd *pw;
316{
317    struct sudo_nss *nss;
318
319    /* Reset group vector so group matching works correctly. */
320    reset_groups(pw);
321
322    tq_foreach_fwd(snl, nss) {
323	if (nss->display_cmnd(nss, pw) == 0)
324	    return TRUE;
325    }
326    return FALSE;
327}
328