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, &ltime, 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