1/* 2 * Copyright (c) 2010 - 2011 Apple Inc. All rights reserved. 3 * 4 * @APPLE_LICENSE_HEADER_START@ 5 * 6 * This file contains Original Code and/or Modifications of Original Code 7 * as defined in and that are subject to the Apple Public Source License 8 * Version 2.0 (the 'License'). You may not use this file except in 9 * compliance with the License. Please obtain a copy of the License at 10 * http://www.opensource.apple.com/apsl/ and read it before using this 11 * file. 12 * 13 * The Original Code and all software distributed under the License are 14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER 15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, 16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, 17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. 18 * Please see the License for the specific language governing rights and 19 * limitations under the License. 20 * 21 * @APPLE_LICENSE_HEADER_END@ 22 */ 23 24#include <errno.h> 25#include <dispatch/dispatch.h> 26#include <smb_lib.h> 27#include <smb_conn.h> 28#include "gss.h" 29#include <GSS/gssapi_spi.h> 30#include <Heimdal/krb5.h> 31#include <KerberosHelper/KerberosHelper.h> 32#include <KerberosHelper/NetworkAuthenticationHelper.h> 33 34 35#define MAX_GSS_HOSTBASE_NAME (SMB_MAX_DNS_SRVNAMELEN + 5 + 1) 36 37static void 38acquire_cred_complete(void *ctx, OM_uint32 maj, gss_status_id_t status __unused, 39 gss_cred_id_t creds, gss_OID_set oids, OM_uint32 time_rec __unused) 40{ 41 uint32_t min; 42 struct smb_gss_cred_ctx *aq_cred_ctx = ctx; 43 44 gss_release_oid_set(&min, &oids); 45 aq_cred_ctx->creds = creds; 46 aq_cred_ctx->maj = maj; 47 dispatch_semaphore_signal(aq_cred_ctx->sem); 48} 49 50static int 51smb_acquire_cred(const char *user, const char *domain, const char *password, 52 gss_OID mech, void **gssCreds) 53{ 54 gss_auth_identity_desc identity; 55 struct smb_gss_cred_ctx aq_cred_ctx; 56 uint32_t maj = !GSS_S_COMPLETE; 57 58 if (password == NULL || user == NULL || *user == '\0') 59 return 0; 60 61 identity.type = GSS_AUTH_IDENTITY_TYPE_1; 62 identity.flags = 0; 63 identity.username = strdup(user); 64 identity.realm = strdup(domain ? domain : ""); 65 identity.password = strdup(password); 66 identity.credentialsRef = NULL; 67 68 if (identity.username == NULL || 69 identity.realm == NULL || 70 identity.password == NULL) 71 goto out; 72 73 aq_cred_ctx.sem = dispatch_semaphore_create(0); 74 if (aq_cred_ctx.sem == NULL) 75 goto out; 76 77 maj = gss_acquire_cred_ex_f(NULL, 78 GSS_C_NO_NAME, 79 0, 80 GSS_C_INDEFINITE, 81 mech, 82 GSS_C_INITIATE, 83 &identity, 84 &aq_cred_ctx, 85 acquire_cred_complete); 86 87 if (maj == GSS_S_COMPLETE) { 88 dispatch_semaphore_wait(aq_cred_ctx.sem, DISPATCH_TIME_FOREVER); 89 maj = aq_cred_ctx.maj; 90 *gssCreds = aq_cred_ctx.creds; 91 } 92 93 if (maj != GSS_S_COMPLETE) 94 smb_log_info("Acquiring NTLM creds for %s\%s failed. GSS returned %d", 95 ASL_LEVEL_INFO, domain, user, maj); 96 97 dispatch_release(aq_cred_ctx.sem); 98out: 99 free(identity.username); 100 free(identity.realm); 101 free(identity.password); 102 103 return (maj == GSS_S_COMPLETE); 104} 105 106/* 107 * Create the target name using the host name. Note we will use the 108 * GSS_C_NT_HOSTBASE name type of cifs@<server> and will return a CFString 109 */ 110CFStringRef TargetNameCreateWithHostName(struct smb_ctx *ctx) 111{ 112 CFStringRef hostName; 113 CFMutableStringRef kerbHintsHostname; 114 115 /* We need to add "cifs@ server part" */ 116 kerbHintsHostname = CFStringCreateMutableCopy(kCFAllocatorDefault, 0, 117 CFSTR("cifs@")); 118 if (kerbHintsHostname == NULL) { 119 return NULL; 120 } 121 /* 122 * The old code would return an IP dot address if the name was NetBIOS. This 123 * was done for Leopard gss. After talking this over with LHA we now always 124 * return the server name. The IP dot address never worked and was causing 125 * Dfs links to fail. 126 */ 127 hostName = CFStringCreateWithCString(kCFAllocatorDefault, ctx->serverName, kCFStringEncodingUTF8); 128 if (hostName) { 129 CFStringAppend(kerbHintsHostname, hostName); 130 CFRelease(hostName); 131 } else { 132 CFRelease(kerbHintsHostname); 133 kerbHintsHostname = NULL; 134 } 135 return kerbHintsHostname; 136} 137 138/* 139 * Create a GSS_C_NT_HOSTBASE target name 140 */ 141void GetTargetNameUsingHostName(struct smb_ctx *ctx) 142{ 143 CFStringRef targetNameRef = TargetNameCreateWithHostName(ctx); 144 char targetName[MAX_GSS_HOSTBASE_NAME]; 145 146 if (targetNameRef == NULL) { 147 goto done; 148 } 149 targetName[0] = 0; 150 CFStringGetCString(targetNameRef, targetName, sizeof(targetName), kCFStringEncodingUTF8); 151 ctx->ct_setup.ioc_gss_target_name = CAST_USER_ADDR_T(strdup(targetName)); 152 if (ctx->ct_setup.ioc_gss_target_name == USER_ADDR_NULL) { 153 goto done; 154 } 155 ctx->ct_setup.ioc_gss_target_size = (uint32_t)strnlen(targetName, sizeof(targetName)); 156done: 157 if (targetNameRef) { 158 CFRelease(targetNameRef); 159 } 160} 161 162int serverSupportsKerberos(CFDictionaryRef mechDict) 163{ 164 if (mechDict == NULL) { 165 return FALSE; 166 } 167 mechDict = CFDictionaryGetValue(mechDict, kSPNEGONegTokenInitMechs); 168 if (mechDict == NULL) { 169 return FALSE; 170 } 171 if (CFDictionaryGetValue(mechDict, kGSSAPIMechKerberosOID)) 172 return TRUE; 173 if (CFDictionaryGetValue(mechDict, kGSSAPIMechKerberosU2UOID)) 174 return TRUE; 175 if (CFDictionaryGetValue(mechDict, kGSSAPIMechKerberosMicrosoftOID)) 176 return TRUE; 177 if (CFDictionaryGetValue(mechDict, kGSSAPIMechPKU2UOID)) 178 return TRUE; 179 return FALSE; 180} 181 182void 183smb_release_gss_cred(void *gssCreds, int error) 184{ 185 OM_uint32 minor_status; 186 187 if (gssCreds == NULL) { 188 return; 189 } 190 if (error == 0) { 191 (void)gss_cred_unhold(&minor_status, gssCreds); 192 (void)gss_release_cred(&minor_status, (gss_cred_id_t *)&gssCreds); 193 } else { 194 (void)gss_destroy_cred(NULL, (gss_cred_id_t *)&gssCreds); 195 } 196} 197 198int 199smb_acquire_ntlm_cred(const char *user, const char *domain, const char *password, void **gssCreds) 200{ 201 return (smb_acquire_cred(user, domain, password, GSS_NTLM_MECHANISM, gssCreds)); 202} 203 204 205static char * 206get_realm(void) 207{ 208 int error; 209 krb5_context ctx; 210 const char *msg; 211 char *realm; 212 213 error = krb5_init_context(&ctx); 214 if (error) { 215 smb_log_info("%s: Couldn't initialize kerberos: %d", ASL_LEVEL_DEBUG, __FUNCTION__, error); 216 return (NULL); 217 } 218 error = krb5_get_default_realm(ctx, &realm); 219 if (error) { 220 msg = krb5_get_error_message(ctx, error); 221 smb_log_info("%s: Couldn't get kerberos default realm: %s", ASL_LEVEL_DEBUG, __FUNCTION__, msg); 222 krb5_free_error_message(ctx, msg); 223 return (NULL); 224 } 225 krb5_free_context(ctx); 226 227 return (realm); 228} 229 230int 231smb_acquire_krb5_cred(const char *user, const char *domain, const char *password, void **gssCreds) 232{ 233 int status; 234 char *default_realm = NULL; 235 236 if (domain == NULL) { 237 domain = default_realm = get_realm(); 238 if (domain == NULL) 239 return (EAUTH); 240 } 241 status = smb_acquire_cred(user, domain, password, GSS_KRB5_MECHANISM, gssCreds); 242 free(default_realm); 243 return (status); 244} 245 246char * 247smb_gss_principal_from_cred(void *smb_cred) 248{ 249 gss_cred_id_t cred = (gss_cred_id_t)smb_cred; 250 gss_buffer_desc buf; 251 gss_name_t name; 252 uint32_t M, m; 253 char *principal = NULL; 254 255 if (cred == GSS_C_NO_CREDENTIAL) 256 return (NULL); 257 M = gss_inquire_cred(&m, cred, &name, NULL, NULL, NULL); 258 if (M != GSS_S_COMPLETE) 259 return (NULL); 260 M = gss_display_name(&m, name, &buf, NULL); 261 (void) gss_release_name(&m, &name); 262 if (M == GSS_S_COMPLETE) { 263 asprintf(&principal, "%.*s", (int)buf.length, (char *)buf.value); 264 (void) gss_release_buffer(&m, &buf); 265 } 266 267 return (principal); 268} 269 270static void 271smb_gss_add_cred(struct smb_gss_cred_list *list, gss_OID oid, gss_cred_id_t cred) 272{ 273 gss_buffer_desc buf; 274 gss_name_t name; 275 uint32_t M, m; 276 uint32_t ltime; 277 struct smb_gss_cred_list_entry *ep; 278 279 if (cred == GSS_C_NO_CREDENTIAL) 280 return; 281 282 M = gss_inquire_cred(&m, cred, &name, <ime, NULL, NULL); 283 if (M != GSS_S_COMPLETE) 284 goto out; 285 if (ltime != GSS_C_INDEFINITE && ltime == 0) 286 goto out; 287 M = gss_display_name(&m, name, &buf, NULL); 288 (void) gss_release_name(&m, &name); 289 if (M != GSS_S_COMPLETE) 290 goto out; 291 292 ep = calloc(1, sizeof (struct smb_gss_cred_list_entry)); 293 if (ep) { 294 ep->expire = ltime; 295 ep->mech = oid; 296 asprintf(&ep->principal, "%.*s", (int)buf.length, (char *)buf.value); 297 if (ep->principal) { 298 TAILQ_INSERT_TAIL(list, ep, next); 299 } else { 300 free(ep); 301 } 302 } 303 (void) gss_release_buffer(&m, &buf); 304 305out: 306 (void)gss_release_cred(&m, &cred); 307} 308 309struct cred_iter_ctx { 310 struct smb_gss_cred_list *clist; 311 dispatch_semaphore_t s; 312}; 313 314static void 315cred_iter(void *ctx, gss_OID moid, gss_cred_id_t cred) 316{ 317 struct cred_iter_ctx *context = (struct cred_iter_ctx *)ctx; 318 if (cred == GSS_C_NO_CREDENTIAL) { 319 dispatch_semaphore_signal(context->s); 320 return; 321 } 322 smb_gss_add_cred(context->clist, moid, cred); 323} 324 325int 326smb_gss_get_cred_list(struct smb_gss_cred_list **list, gss_OID mech) 327{ 328 struct cred_iter_ctx ctx; 329 uint32_t M; 330 331 ctx.s = dispatch_semaphore_create(0); 332 if (ctx.s == NULL) 333 return ENOMEM; 334 335 ctx.clist = malloc(sizeof (struct smb_gss_cred_list)); 336 337 if (ctx.clist == NULL) { 338 dispatch_release(ctx.s); 339 return ENOMEM; 340 } 341 342 TAILQ_INIT(ctx.clist); 343 M = gss_iter_creds_f(NULL, 0, mech, &ctx, cred_iter); 344 345 if (M == GSS_S_COMPLETE) { 346 /* Only wait if gss_iter_creds_f did NOT return an error */ 347 dispatch_semaphore_wait(ctx.s, DISPATCH_TIME_FOREVER); 348 } else { 349 smb_log_info("%s: gss_iter_creds returned: 0x%x", ASL_LEVEL_DEBUG, __FUNCTION__, M); 350 } 351 352 dispatch_release(ctx.s); 353 *list = ctx.clist; 354 355 return 0; 356} 357 358void 359smb_gss_free_cred_entry(struct smb_gss_cred_list_entry **entry) 360{ 361 struct smb_gss_cred_list_entry *ep; 362 363 if (entry == NULL) 364 return; 365 ep = *entry; 366 free(ep->principal); 367 *entry = NULL; 368} 369 370void 371smb_gss_free_cred_list(struct smb_gss_cred_list **list) 372{ 373 struct smb_gss_cred_list *cl; 374 struct smb_gss_cred_list_entry *ep, *tmp; 375 376 if (list == NULL) 377 return; 378 cl = *list; 379 380 TAILQ_FOREACH_SAFE(ep, cl, next, tmp) { 381 TAILQ_REMOVE(cl, ep, next); 382 smb_gss_free_cred_entry(&ep); 383 } 384 free(cl); 385 *list = NULL; 386} 387 388int 389smb_gss_match_cred_entry(struct smb_gss_cred_list_entry *entry, const gss_OID mech, const char *name, const char *domain) 390{ 391 int match = FALSE; 392 char *n, *r, *principal, *tofree, *realm; 393 394 if (mech == GSS_C_NO_OID) 395 return (FALSE); 396 397 if (entry->principal == NULL) 398 return (FALSE); 399 400 if (!gss_oid_equal(mech, entry->mech)) 401 return (FALSE); 402 403 tofree = principal = strdup(entry->principal); 404 if (tofree == NULL) 405 return (FALSE); //XXX? 406 407#if 0 /* Use mech specific format */ 408 if (gss_oid_equal(mech, GSS_KRB5_MECHANISM)) { 409 n = strsep(&principal, "/@"); // Set principal to point to the 410 // instance part or realm. return 411 // null terminated name 412 realm = principal; 413 r = strsep(&principal, "@"); // Find the realm if we don't already 414 // have it. 415 if (r) 416 realm = r; 417 } else if (gss_oid_equal(mech, GSS_NTLM_MECHANISM)) { 418 r = strsep(&principal, "\\"); // Null terminated the domain and set 419 // principal to the name 420 if (principal == NULL || *principal == '\0') { // No name means no realm 421 n = r; 422 realm = ""; 423 } else { 424 n = principal; 425 } 426 } 427#else /* Currently ntlm is mapped to the kerberos syntax of user@domain */ 428 n = strsep(&principal, "/@"); 429 realm = principal; 430 r = strsep(&principal, "@"); 431 if (r) 432 realm = r; 433#endif 434 if (name) { 435 match = (strncmp(name, n, strlen(name) + 1) == 0); 436 } else { 437 match = TRUE; 438 } 439 if (match && domain && realm) { 440 match = (strncmp(realm, domain, strlen(domain) + 1) == 0); 441 } 442 443 free(tofree); 444 445 return (match); 446} 447