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