| /* |
| * (c) 2006 Quest Software, Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright notice, |
| * this list of conditions and the following disclaimer. |
| * |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * 3. Neither the name of Quest Software, Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived from this |
| * software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
| * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| * POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| |
| #include <stdlib.h> |
| #include <sys/types.h> |
| #include <pwd.h> |
| #include <string.h> |
| #include <errno.h> |
| #include <stdio.h> |
| #include <dlfcn.h> |
| |
| #include <vas.h> |
| |
| #include "compat.h" |
| #include "logging.h" |
| #include "nonunix.h" |
| #include "sudo.h" |
| #include "parse.h" |
| |
| |
| /* Pseudo-boolean types */ |
| #undef TRUE |
| #undef FALSE |
| #define FALSE 0 |
| #define TRUE 1 |
| |
| |
| static vas_ctx_t *sudo_vas_ctx; |
| static vas_id_t *sudo_vas_id; |
| /* Don't use VAS_NAME_FLAG_NO_CACHE or lookups just won't work. |
| * -tedp, 2006-08-29 */ |
| static const int update_flags = 0; |
| static int sudo_vas_available = 0; |
| static char *err_msg = NULL; |
| static void *libvas_handle = NULL; |
| |
| /* libvas functions */ |
| static vas_err_t (*v_ctx_alloc) (vas_ctx_t **ctx); |
| static void (*v_ctx_free) (vas_ctx_t *ctx); |
| static vas_err_t (*v_id_alloc) (vas_ctx_t *ctx, const char *name, vas_id_t **id); |
| static void (*v_id_free) (vas_ctx_t *ctx, vas_id_t *id); |
| static vas_err_t (*v_id_establish_cred_keytab) (vas_ctx_t *ctx, vas_id_t *id, int credflags, const char *keytab); |
| static vas_err_t (*v_user_init) (vas_ctx_t *ctx, vas_id_t *id, const char *name, int flags, vas_user_t **user); |
| static void (*v_user_free) (vas_ctx_t *ctx, vas_user_t *user); |
| static vas_err_t (*v_group_init) (vas_ctx_t *ctx, vas_id_t *id, const char *name, int flags, vas_group_t **group); |
| static void (*v_group_free) (vas_ctx_t *ctx, vas_group_t *group); |
| static vas_err_t (*v_user_is_member) (vas_ctx_t *ctx, vas_id_t *id, vas_user_t *user, vas_group_t *group); |
| static const char* (*v_err_get_string) (vas_ctx_t *ctx, int with_cause); |
| |
| |
| static int resolve_vas_funcs(void); |
| |
| |
| /** |
| * Whether nonunix group lookups are available. |
| * @return 1 if available, 0 if not. |
| */ |
| int |
| sudo_nonunix_groupcheck_available(void) |
| { |
| return sudo_vas_available; |
| } |
| |
| |
| /** |
| * Check if the user is in the group |
| * @param group group name which can be in DOMAIN\sam format or just the group |
| * name |
| * @param user user name |
| * @param pwd (unused) |
| * @return 1 if user is a member of the group, 0 if not (or error occurred) |
| */ |
| int |
| sudo_nonunix_groupcheck( const char* group, const char* user, const struct passwd* pwd ) |
| { |
| static int error_cause_shown = FALSE; |
| int rval = FALSE; |
| vas_err_t vaserr; |
| vas_user_t* vas_user = NULL; |
| vas_group_t* vas_group = NULL; |
| |
| if (!sudo_vas_available) { |
| if (error_cause_shown == FALSE) { |
| /* Produce the saved error reason */ |
| warningx("Non-unix group checking unavailable: %s", |
| err_msg ? err_msg |
| : "(unknown cause)"); |
| error_cause_shown = TRUE; |
| } |
| return 0; |
| } |
| |
| /* resolve the user and group. The user will be a real Unix account name, |
| * while the group may be a unix name, or any group name accepted by |
| * vas_name_to_dn, which means any of: |
| * - Group Name |
| * - Group Name@FULLY.QUALIFIED.DOMAIN |
| * - CN=sudoers,CN=Users,DC=rcdev,DC=vintela,DC=com |
| * - S-1-2-34-5678901234-5678901234-5678901234-567 |
| * |
| * XXX - we may get non-VAS user accounts here. You can add local users to an |
| * Active Directory group through override files. Should we handle that case? |
| * */ |
| if( (vaserr = v_user_init( sudo_vas_ctx, sudo_vas_id, user, update_flags, &vas_user )) != VAS_ERR_SUCCESS ) { |
| if (vaserr == VAS_ERR_NOT_FOUND) { |
| /* No such user in AD. Probably a local user. */ |
| vaserr = VAS_ERR_SUCCESS; |
| } |
| goto FINISHED; |
| } |
| |
| if( (vaserr = v_group_init( sudo_vas_ctx, sudo_vas_id, group, update_flags, &vas_group )) != VAS_ERR_SUCCESS ) { |
| goto FINISHED; |
| } |
| |
| /* do the membership check */ |
| if( (vaserr = v_user_is_member( sudo_vas_ctx, sudo_vas_id, vas_user, vas_group )) == VAS_ERR_SUCCESS ) { |
| rval = TRUE; |
| } |
| else if (vaserr == VAS_ERR_NOT_FOUND) { |
| /* fake the vaserr code so no error is triggered */ |
| vaserr = VAS_ERR_SUCCESS; |
| } |
| |
| |
| FINISHED: /* cleanups */ |
| if (vaserr != VAS_ERR_SUCCESS && vaserr != VAS_ERR_NOT_FOUND ) { |
| warningx("Error while checking group membership " |
| "for user \"%s\", group \"%s\", error: %s%s.", user, group, |
| v_err_get_string(sudo_vas_ctx, 1), |
| /* A helpful hint if there seems to be a non-FQDN as the domain */ |
| (strchr(group, '@') && !strchr(group, '.')) |
| ? "\nMake sure the fully qualified domain name is specified" |
| : ""); |
| } |
| if( vas_group ) v_group_free( sudo_vas_ctx, vas_group ); |
| if( vas_user ) v_user_free( sudo_vas_ctx, vas_user ); |
| |
| return(rval); |
| } |
| |
| |
| static void |
| set_err_msg(const char *msg, ...) { |
| va_list ap; |
| |
| if (!msg) /* assert */ |
| return; |
| |
| if (err_msg) |
| free(err_msg); |
| |
| va_start(ap, msg); |
| |
| if (vasprintf(&err_msg, msg, ap) == -1) |
| err_msg = NULL; |
| |
| va_end(ap); |
| } |
| |
| |
| /** |
| * Initialise nonunix_groupcheck state. |
| */ |
| void |
| sudo_nonunix_groupcheck_init(void) |
| { |
| vas_err_t vaserr; |
| void *libvas; |
| |
| if (err_msg) { |
| free(err_msg); |
| err_msg = NULL; |
| } |
| |
| libvas = dlopen(LIBVAS_SO, RTLD_LAZY); |
| if (!libvas) { |
| set_err_msg("dlopen() failed: %s", dlerror()); |
| return; |
| } |
| |
| libvas_handle = libvas; |
| |
| if (resolve_vas_funcs() != 0) |
| return; |
| |
| if (VAS_ERR_SUCCESS == (vaserr = v_ctx_alloc(&sudo_vas_ctx))) { |
| |
| if (VAS_ERR_SUCCESS == (vaserr = v_id_alloc(sudo_vas_ctx, "host/", &sudo_vas_id))) { |
| |
| if (update_flags & VAS_NAME_FLAG_NO_LDAP) { |
| sudo_vas_available = 1; |
| return; /* OK */ |
| } else { /* Get a keytab */ |
| if ((vaserr = v_id_establish_cred_keytab( sudo_vas_ctx, |
| sudo_vas_id, |
| VAS_ID_FLAG_USE_MEMORY_CCACHE |
| | VAS_ID_FLAG_KEEP_COPY_OF_CRED |
| | VAS_ID_FLAG_NO_INITIAL_TGT, |
| NULL )) == VAS_ERR_SUCCESS) { |
| sudo_vas_available = 1; |
| return; /* OK */ |
| } |
| |
| if (!err_msg) |
| set_err_msg("unable to establish creds: %s", |
| v_err_get_string(sudo_vas_ctx, 1)); |
| } |
| |
| v_id_free(sudo_vas_ctx, sudo_vas_id); |
| sudo_vas_id = NULL; |
| } |
| |
| /* This is the last opportunity to get an error message from libvas */ |
| if (!err_msg) |
| set_err_msg("Error initializing non-unix group checking: %s", |
| v_err_get_string(sudo_vas_ctx, 1)); |
| |
| v_ctx_free(sudo_vas_ctx); |
| sudo_vas_ctx = NULL; |
| } |
| |
| if (!err_msg) |
| set_err_msg("Failed to get a libvas handle for non-unix group checking (unknown cause)"); |
| |
| sudo_vas_available = 0; |
| } |
| |
| |
| /** |
| * Clean up nonunix_groupcheck state. |
| */ |
| void |
| sudo_nonunix_groupcheck_cleanup() |
| { |
| if (err_msg) { |
| free(err_msg); |
| err_msg = NULL; |
| } |
| |
| if (sudo_vas_available) { |
| v_id_free(sudo_vas_ctx, sudo_vas_id); |
| sudo_vas_id = NULL; |
| |
| v_ctx_free(sudo_vas_ctx); |
| sudo_vas_ctx = NULL; |
| |
| sudo_vas_available = FALSE; |
| } |
| |
| if (libvas_handle) { |
| if (dlclose(libvas_handle) != 0) |
| warningx("dlclose() failed: %s", dlerror()); |
| libvas_handle = NULL; |
| } |
| } |
| |
| #define RESOLVE_OR_ERR(fptr, sym) \ |
| do { \ |
| void *_fptr = dlsym(libvas_handle, (sym)); \ |
| if (!_fptr) { \ |
| set_err_msg("dlsym() failed: %s", dlerror()); \ |
| return -1; \ |
| } \ |
| fptr = _fptr; \ |
| } while (0) |
| |
| |
| /** |
| * Resolve all the libvas functions. |
| * Returns -1 and sets err_msg if something went wrong, or 0 on success. |
| */ |
| int |
| resolve_vas_funcs(void) |
| { |
| if (!libvas_handle) /* assert */ |
| return -1; |
| |
| RESOLVE_OR_ERR(v_ctx_alloc, "vas_ctx_alloc"); |
| RESOLVE_OR_ERR(v_ctx_free, "vas_ctx_free"); |
| RESOLVE_OR_ERR(v_id_alloc, "vas_id_alloc"); |
| RESOLVE_OR_ERR(v_id_free, "vas_id_free"); |
| RESOLVE_OR_ERR(v_id_establish_cred_keytab, "vas_id_establish_cred_keytab"); |
| RESOLVE_OR_ERR(v_user_init, "vas_user_init"); |
| RESOLVE_OR_ERR(v_user_free, "vas_user_free"); |
| RESOLVE_OR_ERR(v_group_init, "vas_group_init"); |
| RESOLVE_OR_ERR(v_group_free, "vas_group_free"); |
| RESOLVE_OR_ERR(v_user_is_member, "vas_user_is_member"); |
| RESOLVE_OR_ERR(v_err_get_string, "vas_err_get_string"); |
| |
| return 0; |
| } |