1/*
2 * (c) 2006 Quest Software, Inc.  All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are met:
6 *
7 *   1. Redistributions of source code must retain the above copyright notice,
8 *   this list of conditions and the following disclaimer.
9 *
10 *   2. Redistributions in binary form must reproduce the above copyright
11 *   notice, this list of conditions and the following disclaimer in the
12 *   documentation and/or other materials provided with the distribution.
13 *
14 *   3. Neither the name of Quest Software, Inc. nor the names of its
15 *   contributors may be used to endorse or promote products derived from this
16 *   software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
22 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28 * POSSIBILITY OF SUCH DAMAGE.
29 */
30
31#include <config.h>
32
33#include <stdlib.h>
34#include <sys/types.h>
35#include <pwd.h>
36#include <string.h>
37#include <errno.h>
38#include <stdio.h>
39#include <dlfcn.h>
40
41#include <vas.h>
42
43#include "missing.h"
44#include "logging.h"
45#include "nonunix.h"
46#include "sudo.h"
47#include "parse.h"
48
49
50/* Pseudo-boolean types */
51#undef TRUE
52#undef FALSE
53#define FALSE 0
54#define TRUE  1
55
56
57static vas_ctx_t *sudo_vas_ctx;
58static vas_id_t  *sudo_vas_id;
59/* Don't use VAS_NAME_FLAG_NO_CACHE or lookups just won't work.
60 * -tedp, 2006-08-29 */
61static const int update_flags = 0;
62static int sudo_vas_available = 0;
63static char *err_msg = NULL;
64static void *libvas_handle = NULL;
65
66/* libvas functions */
67static vas_err_t	(*v_ctx_alloc) (vas_ctx_t **ctx);
68static void		(*v_ctx_free) (vas_ctx_t *ctx);
69static vas_err_t	(*v_id_alloc) (vas_ctx_t *ctx, const char *name, vas_id_t **id);
70static void		(*v_id_free) (vas_ctx_t *ctx, vas_id_t *id);
71static vas_err_t	(*v_id_establish_cred_keytab) (vas_ctx_t *ctx, vas_id_t *id, int credflags, const char *keytab);
72static vas_err_t	(*v_user_init) (vas_ctx_t *ctx, vas_id_t *id, const char *name, int flags, vas_user_t **user);
73static void		(*v_user_free) (vas_ctx_t *ctx, vas_user_t *user);
74static vas_err_t	(*v_group_init) (vas_ctx_t *ctx, vas_id_t *id, const char *name, int flags, vas_group_t **group);
75static void		(*v_group_free) (vas_ctx_t *ctx, vas_group_t *group);
76static vas_err_t	(*v_user_is_member) (vas_ctx_t *ctx, vas_id_t *id, vas_user_t *user, vas_group_t *group);
77static const char*	(*v_err_get_string) (vas_ctx_t *ctx, int with_cause);
78
79
80static int	resolve_vas_funcs(void);
81
82
83/**
84 * Whether nonunix group lookups are available.
85 * @return 1 if available, 0 if not.
86 */
87int
88sudo_nonunix_groupcheck_available(void)
89{
90    return sudo_vas_available;
91}
92
93
94/**
95 * Check if the user is in the group
96 * @param group group name which can be in DOMAIN\sam format or just the group
97 *              name
98 * @param user user name
99 * @param pwd (unused)
100 * @return 1 if user is a member of the group, 0 if not (or error occurred)
101 */
102int
103sudo_nonunix_groupcheck( const char* group, const char* user, const struct passwd* pwd )
104{
105    static int          error_cause_shown = FALSE;
106    int                 rval = FALSE;
107    vas_err_t           vaserr;
108    vas_user_t*         vas_user = NULL;
109    vas_group_t*        vas_group = NULL;
110
111    if (!sudo_vas_available) {
112	if (error_cause_shown == FALSE) {
113	    /* Produce the saved error reason */
114	    warningx("Non-unix group checking unavailable: %s",
115		    err_msg ? err_msg
116		    : "(unknown cause)");
117	    error_cause_shown = TRUE;
118	}
119	return 0;
120    }
121
122    /* resolve the user and group. The user will be a real Unix account name,
123     * while the group may be a unix name, or any group name accepted by
124     * vas_name_to_dn, which means any of:
125     * - Group Name
126     * - Group Name@FULLY.QUALIFIED.DOMAIN
127     * - CN=sudoers,CN=Users,DC=rcdev,DC=vintela,DC=com
128     * - S-1-2-34-5678901234-5678901234-5678901234-567
129     *
130     * XXX - we may get non-VAS user accounts here. You can add local users to an
131     * Active Directory group through override files. Should we handle that case?
132     * */
133    if( (vaserr = v_user_init( sudo_vas_ctx, sudo_vas_id, user, update_flags, &vas_user )) != VAS_ERR_SUCCESS ) {
134	if (vaserr == VAS_ERR_NOT_FOUND) {
135	     /* No such user in AD. Probably a local user. */
136	    vaserr = VAS_ERR_SUCCESS;
137	}
138        goto FINISHED;
139    }
140
141    if( (vaserr = v_group_init( sudo_vas_ctx, sudo_vas_id, group, update_flags, &vas_group )) != VAS_ERR_SUCCESS ) {
142        goto FINISHED;
143    }
144
145    /* do the membership check */
146    if( (vaserr = v_user_is_member( sudo_vas_ctx, sudo_vas_id, vas_user, vas_group )) == VAS_ERR_SUCCESS ) {
147        rval = TRUE;
148    }
149    else if (vaserr == VAS_ERR_NOT_FOUND) {
150	/* fake the vaserr code so no error is triggered */
151	vaserr = VAS_ERR_SUCCESS;
152    }
153
154
155FINISHED: /* cleanups */
156    if (vaserr != VAS_ERR_SUCCESS && vaserr != VAS_ERR_NOT_FOUND ) {
157	warningx("Error while checking group membership "
158		"for user \"%s\", group \"%s\", error: %s%s.", user, group,
159		v_err_get_string(sudo_vas_ctx, 1),
160		/* A helpful hint if there seems to be a non-FQDN as the domain */
161		(strchr(group, '@') && !strchr(group, '.'))
162		 ? "\nMake sure the fully qualified domain name is specified"
163		 : "");
164    }
165    if( vas_group )              v_group_free( sudo_vas_ctx, vas_group );
166    if( vas_user )              v_user_free( sudo_vas_ctx, vas_user );
167
168    return rval;
169}
170
171
172static void
173set_err_msg(const char *msg, ...) {
174    va_list ap;
175
176    if (!msg) /* assert */
177	return;
178
179    if (err_msg)
180	free(err_msg);
181
182    va_start(ap, msg);
183
184    if (vasprintf(&err_msg, msg, ap) == -1)
185	err_msg = NULL;
186
187    va_end(ap);
188}
189
190
191/**
192 * Initialise nonunix_groupcheck state.
193 */
194void
195sudo_nonunix_groupcheck_init(void)
196{
197    vas_err_t vaserr;
198    void *libvas;
199
200    if (err_msg) {
201	free(err_msg);
202	err_msg = NULL;
203    }
204
205    libvas = dlopen(LIBVAS_SO, RTLD_LAZY);
206    if (!libvas) {
207	set_err_msg("dlopen() failed: %s", dlerror());
208	return;
209    }
210
211    libvas_handle = libvas;
212
213    if (resolve_vas_funcs() != 0)
214	return;
215
216    if (VAS_ERR_SUCCESS == (vaserr = v_ctx_alloc(&sudo_vas_ctx))) {
217
218	if (VAS_ERR_SUCCESS == (vaserr = v_id_alloc(sudo_vas_ctx, "host/", &sudo_vas_id))) {
219
220	    if (update_flags & VAS_NAME_FLAG_NO_LDAP) {
221		sudo_vas_available = 1;
222		return; /* OK */
223	    } else { /* Get a keytab */
224		if ((vaserr = v_id_establish_cred_keytab( sudo_vas_ctx,
225						    sudo_vas_id,
226						      VAS_ID_FLAG_USE_MEMORY_CCACHE
227						    | VAS_ID_FLAG_KEEP_COPY_OF_CRED
228						    | VAS_ID_FLAG_NO_INITIAL_TGT,
229						    NULL )) == VAS_ERR_SUCCESS) {
230		    sudo_vas_available = 1;
231		    return; /* OK */
232		}
233
234		if (!err_msg)
235		    set_err_msg("unable to establish creds: %s",
236			    v_err_get_string(sudo_vas_ctx, 1));
237	    }
238
239	    v_id_free(sudo_vas_ctx, sudo_vas_id);
240	    sudo_vas_id = NULL;
241	}
242
243	/* This is the last opportunity to get an error message from libvas */
244	if (!err_msg)
245	    set_err_msg("Error initializing non-unix group checking: %s",
246		    v_err_get_string(sudo_vas_ctx, 1));
247
248	v_ctx_free(sudo_vas_ctx);
249	sudo_vas_ctx = NULL;
250    }
251
252    if (!err_msg)
253	set_err_msg("Failed to get a libvas handle for non-unix group checking (unknown cause)");
254
255    sudo_vas_available = 0;
256}
257
258
259/**
260 * Clean up nonunix_groupcheck state.
261 */
262void
263sudo_nonunix_groupcheck_cleanup()
264{
265    if (err_msg) {
266	free(err_msg);
267	err_msg = NULL;
268    }
269
270    if (sudo_vas_available) {
271	v_id_free(sudo_vas_ctx, sudo_vas_id);
272	sudo_vas_id = NULL;
273
274	v_ctx_free(sudo_vas_ctx);
275	sudo_vas_ctx = NULL;
276
277	sudo_vas_available = FALSE;
278    }
279
280    if (libvas_handle) {
281	if (dlclose(libvas_handle) != 0)
282	    warningx("dlclose() failed: %s", dlerror());
283	libvas_handle = NULL;
284    }
285}
286
287#define RESOLVE_OR_ERR(fptr, sym) \
288    do { \
289	void *_fptr = dlsym(libvas_handle, (sym)); \
290	if (!_fptr) { \
291	    set_err_msg("dlsym() failed: %s", dlerror()); \
292	    return -1; \
293	} \
294	fptr = _fptr; \
295    } while (0)
296
297
298/**
299 * Resolve all the libvas functions.
300 * Returns -1 and sets err_msg if something went wrong, or 0 on success.
301 */
302int
303resolve_vas_funcs(void)
304{
305    if (!libvas_handle) /* assert */
306	return -1;
307
308    RESOLVE_OR_ERR(v_ctx_alloc,	"vas_ctx_alloc");
309    RESOLVE_OR_ERR(v_ctx_free,	"vas_ctx_free");
310    RESOLVE_OR_ERR(v_id_alloc,	"vas_id_alloc");
311    RESOLVE_OR_ERR(v_id_free,	"vas_id_free");
312    RESOLVE_OR_ERR(v_id_establish_cred_keytab,	"vas_id_establish_cred_keytab");
313    RESOLVE_OR_ERR(v_user_init,	"vas_user_init");
314    RESOLVE_OR_ERR(v_user_free,	"vas_user_free");
315    RESOLVE_OR_ERR(v_group_init,	"vas_group_init");
316    RESOLVE_OR_ERR(v_group_free,	"vas_group_free");
317    RESOLVE_OR_ERR(v_user_is_member,	"vas_user_is_member");
318    RESOLVE_OR_ERR(v_err_get_string,	"vas_err_get_string");
319
320    return 0;
321}
322