Imported Upstream version 1.7.6p1
[debian/sudo] / vasgroups.c
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
57 static vas_ctx_t *sudo_vas_ctx;
58 static 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 */
61 static const int update_flags = 0;
62 static int sudo_vas_available = 0;
63 static char *err_msg = NULL;
64 static void *libvas_handle = NULL;
65
66 /* libvas functions */
67 static vas_err_t        (*v_ctx_alloc) (vas_ctx_t **ctx);
68 static void             (*v_ctx_free) (vas_ctx_t *ctx);
69 static vas_err_t        (*v_id_alloc) (vas_ctx_t *ctx, const char *name, vas_id_t **id);
70 static void             (*v_id_free) (vas_ctx_t *ctx, vas_id_t *id);
71 static vas_err_t        (*v_id_establish_cred_keytab) (vas_ctx_t *ctx, vas_id_t *id, int credflags, const char *keytab);
72 static vas_err_t        (*v_user_init) (vas_ctx_t *ctx, vas_id_t *id, const char *name, int flags, vas_user_t **user);
73 static void             (*v_user_free) (vas_ctx_t *ctx, vas_user_t *user);
74 static vas_err_t        (*v_group_init) (vas_ctx_t *ctx, vas_id_t *id, const char *name, int flags, vas_group_t **group);
75 static void             (*v_group_free) (vas_ctx_t *ctx, vas_group_t *group);
76 static vas_err_t        (*v_user_is_member) (vas_ctx_t *ctx, vas_id_t *id, vas_user_t *user, vas_group_t *group);
77 static const char*      (*v_err_get_string) (vas_ctx_t *ctx, int with_cause);
78
79
80 static int      resolve_vas_funcs(void);
81
82
83 /**
84  * Whether nonunix group lookups are available.
85  * @return 1 if available, 0 if not.
86  */
87 int
88 sudo_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  */
102 int
103 sudo_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
155 FINISHED: /* 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
172 static void
173 set_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  */
194 void
195 sudo_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  */
262 void
263 sudo_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  */
302 int
303 resolve_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 }