1/*
2 * Copyright (c) 2000, Boris Popov
3 * All rights reserved.
4 *
5 * Portions Copyright (C) 2001 - 2013 Apple Inc. All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 * 3. All advertising materials mentioning features or use of this software
16 *    must display the following acknowledgement:
17 *    This product includes software developed by Boris Popov.
18 * 4. Neither the name of the author nor the names of any co-contributors
19 *    may be used to endorse or promote products derived from this software
20 *    without specific prior written permission.
21 *
22 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
23 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32 * SUCH DAMAGE.
33 *
34 */
35#include <sys/ioctl.h>
36#include <sys/mount.h>
37#include <sys/stat.h>
38#include <fcntl.h>
39#include <errno.h>
40#include <string.h>
41#include <stdlib.h>
42#include <pwd.h>
43#include <unistd.h>
44#include <asl.h>
45#include <NetFS/NetFS.h>
46#include <NetFS/NetFSPrivate.h>
47
48/* Needed for parsing the Negotiate Token  */
49#include <KerberosHelper/KerberosHelper.h>
50#include <KerberosHelper/NetworkAuthenticationHelper.h>
51
52/* Needed for Bonjour service name lookup */
53#include <CoreFoundation/CoreFoundation.h>
54#include <CoreServices/CoreServices.h>
55#include <CFNetwork/CFNetServices.h>
56#include <CFNetwork/CFNetServicesPriv.h>	/* Required for _CFNetServiceCreateFromURL */
57
58/* Needed for netfs MessageTracer logging */
59#include <NetFS/NetFSUtilPrivate.h>
60
61#include <Heimdal/krb5.h>
62
63#include "smbclient.h"
64#include <smbclient/ntstatus.h>
65#include <netsmb/smb_lib.h>
66#include <netsmb/netbios.h>
67#include <netsmb/nb_lib.h>
68#include <netsmb/smb_conn.h>
69#include <smbfs/smbfs.h>
70#include <netsmb/smbio.h>
71#include <netsmb/smbio_2.h>
72#include <charsets.h>
73#include <parse_url.h>
74#include <com_err.h>
75#include <netsmb/upi_mbuf.h>
76#include <sys/mchain.h>
77#include "msdfs.h"
78#include <smbclient/smbclient.h>
79#include <smbclient/smbclient_internal.h>
80#include "gss.h"
81
82#define DISCONNECT_ERROR(error) \
83	((error == ENETUNREACH) || (error == ENOTCONN) || (error == ENETRESET) || \
84	(error == ECONNABORTED) || (error == EPIPE))
85
86
87#define SPN_PLEASE_IGNORE_REALM CFSTR("cifs/not_defined_in_RFC4178@please_ignore")
88
89#include <NetFS/NetFSUtilPrivate.h>
90
91/*
92 * GetTraceMessageScheme
93 *
94 * See whether we were passed a scheme to use when logging failures.  If so, we
95 * log them with NetFSLogToMessageTracer. The scheme string needs to be freed
96 * by the calling process.
97 */
98static char *GetTraceMessageScheme(CFDictionaryRef options)
99{
100	CFStringRef schemeRef;
101
102	if (!options) {
103		return NULL;
104	}
105	schemeRef = CFDictionaryGetValue(options, kNetFSTraceMessageSchemeKey);
106	if (!schemeRef) {
107		return NULL;
108	}
109	return CStringCreateWithCFString(schemeRef);
110}
111
112/*
113 * Since ENETFSACCOUNTRESTRICTED, ENETFSPWDNEEDSCHANGE, and ENETFSPWDPOLICY are only
114 * defined in NetFS.h and we can't include NetFS.h in the kernel so lets reset these
115 * herre. In the future we should just pass up the NTSTATUS and do all the translation
116 * in user land.
117*/
118int smb_ioctl_call(int ct_fd, unsigned long cmd, void *info)
119{
120	if (ioctl(ct_fd, cmd, info) == -1) {
121		switch (errno) {
122			case SMB_ENETFSACCOUNTRESTRICTED:
123				errno = ENETFSACCOUNTRESTRICTED;
124				break;
125			case SMB_ENETFSPWDNEEDSCHANGE:
126				errno = ENETFSPWDNEEDSCHANGE;
127				break;
128			case SMB_ENETFSPWDPOLICY:
129				errno = ENETFSPWDPOLICY;
130				break;
131			case SMB_ENETFSNOAUTHMECHSUPP:
132				errno = ENETFSNOAUTHMECHSUPP;
133				break;
134			case SMB_ENETFSNOPROTOVERSSUPP:
135				errno = ENETFSNOPROTOVERSSUPP;
136				break;
137		}
138		return -1;
139	} else {
140		return 0;
141	}
142}
143
144
145/*
146 * Return the OS and Lanman strings.
147 */
148static void smb_get_os_lanman(struct smb_ctx *ctx, CFMutableDictionaryRef mutableDict)
149{
150	struct smbioc_os_lanman OSLanman;
151	CFStringRef NativeOSString;
152	CFStringRef NativeLANManagerString;
153
154	/* See if the kernel has the Native OS and Native Lanman Strings */
155	memset(&OSLanman, 0, sizeof(OSLanman));
156	if (smb_ioctl_call(ctx->ct_fd, SMBIOC_GET_OS_LANMAN, &OSLanman) == -1) {
157		smb_log_info("The SMBIOC_GET_OS_LANMAN call failed, syserr = %s",
158					 ASL_LEVEL_DEBUG, strerror(errno));
159	}
160	/* Didn't get a Native OS, default to UNIX or Windows */
161	if (OSLanman.NativeOS[0] == 0) {
162		if (ctx->ct_vc_caps & SMB_CAP_UNIX)
163			strlcpy(OSLanman.NativeOS, "UNIX", sizeof(OSLanman.NativeOS));
164		else
165			strlcpy(OSLanman.NativeOS, "WINDOWS", sizeof(OSLanman.NativeOS));
166	}
167	/* Get the Native OS and added it to the dictionary */
168	NativeOSString = CFStringCreateWithCString(NULL, OSLanman.NativeOS, kCFStringEncodingUTF8);
169	if (NativeOSString != NULL) {
170		CFDictionarySetValue (mutableDict, kNetFSSMBNativeOSKey, NativeOSString);
171		CFRelease (NativeOSString);
172	}
173	/* Get the Native Lanman and added it to the dictionary */
174	if (OSLanman.NativeLANManager[0]) {
175		NativeLANManagerString = CFStringCreateWithCString(NULL, OSLanman.NativeLANManager, kCFStringEncodingUTF8);
176		if (NativeLANManagerString != NULL) {
177			CFDictionarySetValue (mutableDict, kNetFSSMBNativeLANManagerKey, NativeLANManagerString);
178			CFRelease (NativeLANManagerString);
179		}
180	}
181	smb_log_info("Native OS = '%s' Native Lanman = '%s'", ASL_LEVEL_DEBUG, OSLanman.NativeOS, OSLanman.NativeLANManager);
182}
183
184/*
185 * Create a unique_id, that can be used to find a matching mounted
186 * volume, given the server address, port number, share name and path.
187 */
188static void create_unique_id(struct smb_ctx *ctx, char *UppercaseShareName,
189							 unsigned char *id, int32_t *unique_id_len)
190{
191	struct sockaddr	*ct_saddr;
192	int32_t total_len;
193
194	/* Always just use the real sockaddr when making a unique id */
195	if (ctx->ct_saddr->sa_family == AF_NETBIOS) {
196		ct_saddr = (struct sockaddr *)((void *)&((struct sockaddr_nb *)((void *)ctx->ct_saddr))->snb_addrin);
197	} else {
198		ct_saddr = ctx->ct_saddr;
199	}
200
201	total_len = ct_saddr->sa_len + (int32_t)strlen(UppercaseShareName) + MAXPATHLEN;
202	memset(id, 0, SMB_MAX_UNIQUE_ID);
203	if (total_len > SMB_MAX_UNIQUE_ID) {
204		smb_log_info("create_unique_id '%s' too long", ASL_LEVEL_ERR, ctx->ct_sh.ioc_share);
205		return; /* program error should never happen, but just incase */
206	}
207	memcpy(id, ct_saddr, ct_saddr->sa_len);
208	id += ct_saddr->sa_len;
209	memcpy(id, UppercaseShareName, strlen(UppercaseShareName));
210	id += strlen(UppercaseShareName);
211	/* We have a path make it part of the unique id */
212	if (ctx->mountPath)
213		CFStringGetCString(ctx->mountPath, (char *)id, MAXPATHLEN, kCFStringEncodingUTF8);
214	/* id += MAXPATHLEN; */
215	*unique_id_len = total_len;
216}
217
218
219/*
220 * Get a list of all mount volumes. The calling routine will need to free the memory.
221 */
222static struct statfs *smb_getfsstat(int *fs_cnt)
223{
224	struct statfs *fs;
225	int bufsize = 0;
226
227	/* See what we need to allocate */
228	*fs_cnt = getfsstat(NULL, bufsize, MNT_NOWAIT);
229	if (*fs_cnt <=  0)
230		return NULL;
231	bufsize = *fs_cnt * (int)sizeof(*fs);
232	fs = malloc(bufsize);
233	if (fs == NULL)
234		return NULL;
235
236	*fs_cnt = getfsstat(fs, bufsize, MNT_NOWAIT);
237	if (*fs_cnt < 0) {
238		*fs_cnt = 0;
239		free (fs);
240		fs = NULL;
241	}
242	return fs;
243}
244
245/*
246 * Call the kernel and get the mount information.
247 */
248static int get_share_mount_info(const char *mntonname, CFMutableDictionaryRef mdict, struct UniqueSMBShareID *req)
249{
250	req->error = 0;
251	req->user[0] = 0;
252	if ((fsctl(mntonname, (unsigned int)smbfsUniqueShareIDFSCTL, req, 0 ) == 0) && (req->error == EEXIST)) {
253		CFStringRef tmpString = NULL;
254
255		tmpString = CFStringCreateWithCString (NULL, mntonname, kCFStringEncodingUTF8);
256		if (tmpString) {
257			CFDictionarySetValue (mdict, kNetFSMountPathKey, tmpString);
258			CFRelease (tmpString);
259		}
260
261		if ((req->connection_type == kConnectedByGuest) || (strcasecmp(req->user, kGuestAccountName) == 0)) {
262			CFDictionarySetValue (mdict, kNetFSMountedByGuestKey, kCFBooleanTrue);
263			return EEXIST;
264		}
265		/* Authenticated mount, set the key */
266		CFDictionarySetValue (mdict, kNetFSMountedWithAuthenticationInfoKey, kCFBooleanTrue);
267		if (req->user[0]) {
268			tmpString = CFStringCreateWithCString (NULL, req->user, kCFStringEncodingUTF8);
269			if (tmpString) {
270				CFDictionarySetValue (mdict, kNetFSMountedByUserKey, tmpString);
271				CFRelease (tmpString);
272			}
273		}
274		return EEXIST;
275	}
276	return 0;
277}
278
279int already_mounted(struct smb_ctx *ctx, char *UppercaseShareName, struct statfs *fs,
280					int fs_cnt, CFMutableDictionaryRef mdict, int requestMntFlags)
281{
282	struct UniqueSMBShareID req;
283	int				ii;
284
285	if ((fs == NULL) || (ctx->ct_saddr == NULL))
286		return 0;
287	bzero(&req, sizeof(req));
288	/* now create the unique_id, using tcp address + port + uppercase share */
289	create_unique_id(ctx, UppercaseShareName, req.unique_id, &req.unique_id_len);
290	for (ii = 0; ii < fs_cnt; ii++, fs++) {
291		if (fs->f_owner != ctx->ct_ssn.ioc_owner)
292			continue;
293		if (strcmp(fs->f_fstypename, SMBFS_VFSNAME) != 0)
294			continue;
295		/* Automounts don't count as already mounted */
296		if (fs->f_flags & MNT_AUTOMOUNTED)
297			continue;
298		/*
299		 * See Rusty's comments in Radar 5337352 for more detail.
300		 * If you get a MNT_DONTBROWSE mount request, and find a prior instance
301		 * of that as a MNT_DONTBROWSE. mounted by the same UID, you should
302		 * then return that its already mounted.
303		 */
304		if (requestMntFlags & MNT_DONTBROWSE) {
305			if ((fs->f_flags & MNT_DONTBROWSE) != MNT_DONTBROWSE) {
306				continue;
307			}
308		} else if (fs->f_flags & MNT_DONTBROWSE) {
309			continue;
310		}
311		/* Now call the file system to see if this is the one we are looking for */
312		if (get_share_mount_info(fs->f_mntonname, mdict, &req) == EEXIST) {
313			return EEXIST;
314		}
315	}
316	return 0;
317}
318
319/*
320 * Given a dictionary see if the key has a boolean value to return.
321 * If no dictionary or no value return the passed in default value
322 * otherwise return the value
323 */
324Boolean SMBGetDictBooleanValue(CFDictionaryRef Dict, const void * KeyValue, Boolean DefaultValue)
325{
326	CFBooleanRef booleanRef = NULL;
327
328	if (Dict)
329		booleanRef = (CFBooleanRef)CFDictionaryGetValue(Dict, KeyValue);
330	if (booleanRef == NULL)
331		return DefaultValue;
332
333	return CFBooleanGetValue(booleanRef);
334}
335
336void smb_ctx_get_user_mount_info(const char *mntonname, CFMutableDictionaryRef mdict)
337{
338	struct UniqueSMBShareID req;
339
340	bzero(&req, sizeof(req));
341	req.flags = SMBFS_GET_ACCESS_INFO;
342	if (get_share_mount_info(mntonname, mdict, &req) != EEXIST) {
343		smb_log_info("Failed to get user access for mount %s", ASL_LEVEL_ERR, mntonname);
344	}
345}
346
347/*
348 * Copy the username in and make sure its not to long.
349 */
350int smb_ctx_setuser(struct smb_ctx *ctx, const char *name)
351{
352	if (strlen(name) >= SMB_MAXUSERNAMELEN) {
353		smb_log_info("user name '%s' too long", ASL_LEVEL_ERR, name);
354		return ENAMETOOLONG;
355	}
356	strlcpy(ctx->ct_setup.ioc_user, name, SMB_MAXUSERNAMELEN);
357
358	/* We need to tell the kernel if we are trying to do guest access */
359	if (strcasecmp(ctx->ct_setup.ioc_user, kGuestAccountName) == 0)
360		ctx->ct_setup.ioc_userflags |= SMBV_GUEST_ACCESS;
361	else
362		ctx->ct_setup.ioc_userflags &= ~(SMBV_GUEST_ACCESS | SMBV_PRIV_GUEST_ACCESS);
363
364	return 0;
365}
366
367/*
368 * Never uppercase the Domain/Workgroup name here, because it might come from a Windows codepage encoding.
369 * We can get the domain in several different ways. Never override a domain name generated
370 * by the user. Here is the priority for the domain
371 *		1. Domain came from the URL. done in the parse routine
372 *		2. Domain came from the configuration file.
373 *		3. Domain came from the network.
374 *
375 * Once a user sets the domain then it is always set. The URL will always have first crack.
376 */
377int smb_ctx_setdomain(struct smb_ctx *ctx, const char *name)
378{
379	/* The user already set the domain so don't reset it */
380	if (ctx->ct_setup.ioc_domain[0])
381		return 0;
382
383	if (strlen(name) > SMB_MAXNetBIOSNAMELEN) {
384		smb_log_info("domain/workgroup name '%s' too long", ASL_LEVEL_ERR, name);
385		return ENAMETOOLONG;
386	}
387	strlcpy(ctx->ct_setup.ioc_domain,  name, SMB_MAXNetBIOSNAMELEN+1);
388	return 0;
389}
390
391int smb_ctx_setpassword(struct smb_ctx *ctx, const char *passwd, int setFlags)
392{
393	if (passwd == NULL)
394		return EINVAL;
395	if (strlen(passwd) >= SMB_MAXPASSWORDLEN) {
396		smb_log_info("password too long", ASL_LEVEL_ERR);
397		return ENAMETOOLONG;
398	}
399	/*
400	 * They user wants to mount with an empty password. This is differnet then no password
401	 * but should be treated the same.
402	 */
403	if (*passwd == 0)
404		strlcpy(ctx->ct_setup.ioc_password, "", sizeof(ctx->ct_setup.ioc_password));
405	else
406		strlcpy(ctx->ct_setup.ioc_password, passwd, sizeof(ctx->ct_setup.ioc_password));
407	if (setFlags) {
408		ctx->ct_flags |= SMBCF_EXPLICITPWD;
409	}
410	return 0;
411}
412
413/*
414 * Copy the share in and make sure its not to long.
415 */
416int smb_ctx_setshare(struct smb_ctx *ctx, const char *share)
417{
418	CFStringRef shareRef;
419
420	if (strlen(share) >= SMB_MAXSHARENAMELEN) {
421		smb_log_info("share name '%s' too long", ASL_LEVEL_ERR, share);
422		return ENAMETOOLONG;
423	}
424	if (ctx->ct_origshare)
425		free(ctx->ct_origshare);
426	if ((ctx->ct_origshare = strdup(share)) == NULL) {
427		return ENOMEM;
428	}
429	shareRef = CFStringCreateWithCString(kCFAllocatorDefault, share,  kCFStringEncodingUTF8);
430	if (shareRef) {
431		str_upper(ctx->ct_sh.ioc_share, sizeof(ctx->ct_sh.ioc_share), shareRef);
432		CFRelease(shareRef);
433	} else {
434		/* Nothing else we can do here */
435		strlcpy(ctx->ct_sh.ioc_share, share, sizeof(ctx->ct_sh.ioc_share));
436	}
437
438	return 0;
439}
440
441/*
442 * If the call to nbns_getnodestatus(...) fails we can try one of two other
443 * methods; use a name of "*SMBSERVER", which is supported by Samba (at least)
444 * or, as a last resort, try the "truncate-at-dot" heuristic.
445 * And the heuristic really should attempt truncation at
446 * each dot in turn, left to right.
447 *
448 * These fallback heuristics should be triggered when the attempt to open the
449 * session fails instead of in the code below.
450 *
451 * See http://ietf.org/internet-drafts/draft-crhertel-smb-url-07.txt
452 */
453static int smb_ctx_getnbname(struct smb_ctx *ctx, struct sockaddr *sap)
454{
455	char nbt_server[SMB_MAXNetBIOSNAMELEN + 1];
456	int error;
457
458	nbt_server[0] = '\0';
459	/* Really need to see if we can find a match */
460	error = nbns_getnodestatus(sap, &ctx->ct_nb, &ctx->prefs, NULL, nbt_server, NULL, NULL);
461	/* No error and we found the servers NetBIOS name */
462	if (!error && nbt_server[0])
463		strlcpy(ctx->ct_ssn.ioc_srvname, nbt_server, sizeof(ctx->ct_ssn.ioc_srvname));
464	else
465		error = ENOENT;	/* Couldn't find the NetBIOS name */
466	return error;
467}
468
469static int smb_ctx_gethandle(struct smb_ctx *ctx)
470{
471	int fd, i;
472	char buf[20];
473
474	if (ctx->ct_fd != -1) {
475		close(ctx->ct_fd);
476		ctx->ct_fd = -1;
477		ctx->ct_flags &= ~SMBCF_CONNECT_STATE;	/* Remove all the connect state flags */
478	}
479	/*
480	 * First try to open as clone
481	 */
482	fd = open("/dev/"NSMB_NAME, O_RDWR);
483	if (fd >= 0) {
484		ctx->ct_fd = fd;
485		return 0;
486	}
487	/*
488	 * well, no clone capabilities available - we have to scan
489	 * all devices in order to get free one
490	 */
491	for (i = 0; i < 1024; i++) {
492		snprintf(buf, sizeof(buf), "/dev/%s%x", NSMB_NAME, i);
493		fd = open(buf, O_RDWR);
494		if (fd >= 0) {
495			ctx->ct_fd = fd;
496			return 0;
497		}
498	}
499	smb_log_info("%d failures to open smb device, syserr = %s",
500				 ASL_LEVEL_ERR, i+1, strerror(errno));
501	return ENOENT;
502}
503
504/*
505 * Cancel any outstanding connection
506 */
507void smb_ctx_cancel_connection(struct smb_ctx *ctx)
508{
509	ctx->ct_cancel = TRUE;
510	if ((ctx->ct_fd != -1) && (smb_ioctl_call(ctx->ct_fd, SMBIOC_CANCEL_SESSION, &ctx->ct_cancel) == -1))
511		smb_log_info("can't cancel the connection, syserr = %s",
512					 ASL_LEVEL_DEBUG, strerror(errno));
513}
514
515/*
516 * Return the connection state of the session. Currently only returns ENOTCONN
517 * or EISCONN. May want to expand this in the future.
518 */
519uint16_t smb_ctx_connstate(struct smb_ctx *ctx)
520{
521	uint16_t connstate = 0;
522
523	if (smb_ioctl_call(ctx->ct_fd, SMBIOC_SESSSTATE, &connstate) == -1) {
524		smb_log_info("can't get connection state for the session, syserr = %s",
525					 ASL_LEVEL_DEBUG, strerror(errno));
526		return ENOTCONN;
527	}
528	return connstate;
529}
530
531/*
532 * Should only be called if we are shaing a session, will atempt to obtain
533 * the kernel's version of the client an server principal names.
534 */
535static void
536getAuthInfo(struct smb_ctx *ctx, uint32_t max_client_size, uint32_t	max_target_size)
537{
538	struct smbioc_auth_info info;
539
540	if  (ctx->ct_vc_flags &  (SMBV_ANONYMOUS_ACCESS | SMBV_GUEST_ACCESS | SMBV_SFS_ACCESS)) {
541		/* Not an authenticated sesssion, get out nothing to do here */
542		return;
543	}
544	if (ctx->ct_setup.ioc_gss_client_name && ctx->ct_setup.ioc_gss_target_name) {
545		/* We already have what we need, get out nothing to do here */
546		return;
547	}
548	memset(&info, 0, sizeof(info));
549	info.ioc_version = SMB_IOC_STRUCT_VERSION;
550	info.ioc_client_size = max_client_size;
551	info.ioc_target_size = max_target_size;
552	info.ioc_client_name = CAST_USER_ADDR_T(calloc(max_client_size, 1));
553	info.ioc_target_name = CAST_USER_ADDR_T(calloc(max_target_size, 1));
554	if (!info.ioc_client_name || !info.ioc_target_name) {
555		goto done;
556	}
557	if (smb_ioctl_call(ctx->ct_fd, SMBIOC_AUTH_INFO, &info) == -1) {
558		goto done;
559	}
560	if (!info.ioc_client_size || !info.ioc_target_size) {
561		goto done;
562	}
563	if (ctx->ct_setup.ioc_gss_client_name) {
564		free((void *)(uintptr_t)ctx->ct_setup.ioc_gss_client_name);
565		ctx->ct_setup.ioc_gss_client_name = USER_ADDR_NULL;
566	}
567	ctx->ct_setup.ioc_gss_client_name = info.ioc_client_name;
568	ctx->ct_setup.ioc_gss_client_size = info.ioc_client_size;
569	ctx->ct_setup.ioc_gss_client_nt = info.ioc_client_nt;
570	info.ioc_client_name = USER_ADDR_NULL;
571
572	if (ctx->ct_setup.ioc_gss_target_name) {
573		free((void *)(uintptr_t)ctx->ct_setup.ioc_gss_target_name);
574		ctx->ct_setup.ioc_gss_target_name = USER_ADDR_NULL;
575	}
576	ctx->ct_setup.ioc_gss_target_name = info.ioc_target_name;
577	ctx->ct_setup.ioc_gss_target_size = info.ioc_target_size;
578	ctx->ct_setup.ioc_gss_target_nt = info.ioc_target_nt;
579	info.ioc_target_name = USER_ADDR_NULL;
580
581done:
582	if (ctx->ct_setup.ioc_gss_client_name && ctx->ct_setup.ioc_gss_target_name) {
583		smb_log_info("%s: Client principal name '%s' Server principal name '%s'",
584					 ASL_LEVEL_DEBUG, __FUNCTION__,
585					 (char *)(uintptr_t)ctx->ct_setup.ioc_gss_client_name,
586					 (char *)(uintptr_t)ctx->ct_setup.ioc_gss_target_name);
587	}
588	if (info.ioc_client_name != USER_ADDR_NULL) {
589		free((void *)(uintptr_t)info.ioc_client_name);
590	}
591	if (info.ioc_target_name != USER_ADDR_NULL) {
592		free((void *)(uintptr_t)info.ioc_target_name);
593	}
594}
595
596/*
597 * This routine actually does the whole connection. So if the ctx has a connection
598 * this routine will break it and start the whole connection process over.
599 */
600static int findMatchingVC(struct smb_ctx *ctx, CFMutableArrayRef addressArray)
601{
602	struct smbioc_negotiate	rq;
603	CFMutableDataRef addressData;
604	struct connectAddress *conn;
605	CFIndex ii;
606	int	error =  smb_ctx_gethandle(ctx);
607
608	if (error)
609		return (error);
610
611	error = ENOTCONN;
612	for (ii=0; ii < CFArrayGetCount(addressArray); ii++) {
613		addressData = (CFMutableDataRef)CFArrayGetValueAtIndex(addressArray, ii);
614		if (!addressData)
615			continue;
616		conn = (struct connectAddress *)((void *)CFDataGetMutableBytePtr(addressData));
617		if (!conn)
618			continue;
619
620		bzero(&rq, sizeof(rq));
621		rq.ioc_version = SMB_IOC_STRUCT_VERSION;
622		rq.ioc_saddr_len = conn->addr.sa_len;
623		rq.ioc_saddr = &conn->addr;
624		bcopy(&ctx->ct_ssn, &rq.ioc_ssn, sizeof(struct smbioc_ossn));
625		/* ct_setup.ioc_user and rq.ioc_user must be the same size */
626		bcopy(&ctx->ct_setup.ioc_user, &rq.ioc_user, sizeof(rq.ioc_user));
627		rq.ioc_negotiate_token = CAST_USER_ADDR_T(calloc(SMB_IOC_SPI_INIT_SIZE, 1));
628		if (rq.ioc_negotiate_token != USER_ADDR_NULL) {
629			rq.ioc_negotiate_token_len = SMB_IOC_SPI_INIT_SIZE;
630		}
631
632		/* Call the kernel to see if we already have a vc */
633		if (smb_ioctl_call(ctx->ct_fd, SMBIOC_FIND_VC, &rq) == -1)
634			error = errno;	/* Some internal error happen? */
635		else
636			error = rq.ioc_errno;	/* The real error */
637		if (error) {
638			if (rq.ioc_negotiate_token) {
639				free((void *)((uintptr_t)(rq.ioc_negotiate_token)));
640			}
641			continue;
642		}
643		if (rq.ioc_negotiate_token_len > SMB_IOC_SPI_INIT_SIZE)  {
644			/* Just log it and then pretend that we didn't get any mech info */
645			smb_log_info("%s: %s mech info too large %d", ASL_LEVEL_DEBUG,
646						 __FUNCTION__, ctx->serverName, rq.ioc_negotiate_token_len);
647			rq.ioc_negotiate_token_len = 0;
648			if (rq.ioc_negotiate_token) {
649				free((void *)((uintptr_t)(rq.ioc_negotiate_token)));
650                rq.ioc_negotiate_token = USER_ADDR_NULL;
651			}
652		}
653
654		if (ctx->mechDict) {
655			CFRelease(ctx->mechDict);
656			ctx->mechDict = NULL;
657		}
658
659		/* Server return a negotiate token, get the mech dictionary */
660		if (rq.ioc_negotiate_token_len) {
661			CFDataRef NegotiateToken = CFDataCreate(kCFAllocatorDefault, (const UInt8 *)(uintptr_t)rq.ioc_negotiate_token, rq.ioc_negotiate_token_len);
662			if (NegotiateToken) {
663				ctx->mechDict = KRBDecodeNegTokenInit(kCFAllocatorDefault, NegotiateToken);
664				CFRelease(NegotiateToken);
665			}
666		}
667
668		if ((ctx->mechDict == NULL) && (ctx->ct_vc_caps & SMB_CAP_EXT_SECURITY)) {
669			/*
670			 * The server does extended security, but they didn't return any mech
671			 * types. Then create a default RAW NTLMSSP mech dictionary.
672			 */
673			ctx->mechDict = KRBCreateNegTokenLegacyNTLM(kCFAllocatorDefault);
674			ctx->ct_flags |= SMBCF_RAW_NTLMSSP;
675		}
676
677		/* Free it if we have one */
678		if (ctx->ct_saddr)
679			free(ctx->ct_saddr);
680		/* We have a good sockaddr make a copy */
681		ctx->ct_saddr = malloc(conn->addr.sa_len);
682		if (ctx->ct_saddr)
683			memcpy(ctx->ct_saddr, &conn->addr, conn->addr.sa_len);
684		/* Get the server's capablilities */
685		ctx->ct_vc_caps = rq.ioc_ret_caps;
686		/* Get the virtual circuit flags */
687		ctx->ct_vc_flags = rq.ioc_ret_vc_flags;
688		if ((rq.ioc_extra_flags & SMB_SHARING_VC) && (ctx->ct_vc_flags & SMBV_AUTH_DONE)) {
689			ctx->ct_flags |= SMBCF_AUTHORIZED | SMBCF_CONNECTED;
690			getAuthInfo(ctx,  rq.ioc_max_client_size, rq.ioc_max_target_size);
691			ctx->ct_vc_shared = TRUE;
692		} else {
693			ctx->ct_vc_shared = FALSE;
694		}
695
696		/* If we have no username and the kernel does then use the name in the kernel */
697		if ((ctx->ct_setup.ioc_user[0] == 0) && rq.ioc_user[0]) {
698			strlcpy(ctx->ct_setup.ioc_user, rq.ioc_user, sizeof(ctx->ct_setup.ioc_user));
699		}
700
701        if (rq.ioc_negotiate_token != USER_ADDR_NULL) {
702            free((void *)((uintptr_t)(rq.ioc_negotiate_token)));
703        }
704
705		break; /* We found one so we are done */
706	}
707
708	if (error) {
709		close(ctx->ct_fd);
710		ctx->ct_fd = -1;
711	}
712	return (error);
713}
714
715/*
716 * This routine actually does the whole connection. So if the ctx has a connection
717 * this routine will break it and start the whole connection process over.
718 */
719static int smb_negotiate(struct smb_ctx *ctx, struct sockaddr *raddr,
720						 struct sockaddr *laddr, int forceNewSession)
721{
722	struct smbioc_negotiate	rq;
723	int	error = 0;
724    struct timespec timeout;
725
726	error = smb_ctx_gethandle(ctx);
727	if (error)
728		return (error);
729
730	ctx->ct_flags &= ~SMBCF_RAW_NTLMSSP;
731	bzero(&rq, sizeof(rq));
732	rq.ioc_version = SMB_IOC_STRUCT_VERSION;
733	if (raddr) {
734		rq.ioc_saddr_len = raddr->sa_len;
735		rq.ioc_saddr = raddr;
736	}
737	if (laddr) {
738		rq.ioc_laddr = laddr;
739		rq.ioc_laddr_len = laddr->sa_len;
740	}
741	bcopy(&ctx->ct_ssn, &rq.ioc_ssn, sizeof(struct smbioc_ossn));
742	/* ct_setup.ioc_user and rq.ioc_user must be the same size */
743	bcopy(&ctx->ct_setup.ioc_user, &rq.ioc_user, sizeof(rq.ioc_user));
744	/* Pass the user request auth types and other user settable flags. */
745	rq.ioc_userflags = ctx->ct_setup.ioc_userflags;
746	rq.ioc_negotiate_token = CAST_USER_ADDR_T(calloc(SMB_IOC_SPI_INIT_SIZE, 1));
747	if (rq.ioc_negotiate_token == USER_ADDR_NULL) {
748		error = ENOMEM;
749		goto out;
750	}
751	/* They want a new virtual circuit to the server */
752	if (forceNewSession) {
753		rq.ioc_extra_flags |= SMB_FORCE_NEW_SESSION;
754	}
755
756    if (ctx->prefs.smb_negotiate != 0) {
757        switch (ctx->prefs.smb_negotiate) {
758            case 1:
759                rq.ioc_extra_flags |= SMB_SMB1_ONLY;
760                break;
761
762            case 2:
763                rq.ioc_extra_flags |= SMB_SMB2_ONLY;
764                break;
765
766            case 3:
767                rq.ioc_extra_flags |= SMB_SMB3_ONLY;
768                break;
769
770            default:
771                /* ignore any other values */
772                break;
773        }
774    }
775    if (ctx->prefs.signing_required) {
776        rq.ioc_extra_flags |= SMB_SIGNING_REQUIRED;
777
778    }
779    /*
780     * If we are NOT doing SMB 1/2/3 only, then see if "cifs://" was
781     * specified. Specifying "cifs://" forces us to only try SMB 1
782     */
783    if (!(rq.ioc_extra_flags & (SMB_SMB1_ONLY | SMB_SMB2_ONLY | SMB_SMB3_ONLY))) {
784        CFStringRef scheme = CFURLCopyScheme (ctx->ct_url);
785
786        if (scheme != NULL) {
787            if (kCFCompareEqualTo == CFStringCompare (scheme, CFSTR("cifs"),
788                                                      kCFCompareCaseInsensitive)) {
789                smb_log_info("%s: cifs specified so force using SMB 1",
790                             ASL_LEVEL_DEBUG, __FUNCTION__);
791
792                /* Clear SMB 2 and 3 only flags in case they are set */
793                rq.ioc_extra_flags &= ~(SMB_SMB2_ONLY | SMB_SMB3_ONLY);
794
795                /* Set SMB 1 only flag */
796                rq.ioc_extra_flags |= SMB_SMB1_ONLY;
797            }
798
799            CFRelease(scheme);
800        }
801    }
802
803    /* Pass in the max_resp_timeout */
804    rq.ioc_max_resp_timeout = ctx->prefs.max_resp_timeout;
805
806    rq.ioc_negotiate_token_len = SMB_IOC_SPI_INIT_SIZE;
807
808    /* Need Client Guid for SMB 2/3 Neg request */
809    timeout.tv_nsec = 0;
810    timeout.tv_sec = 180;   /* 3 mins should be plenty of time to get uuid */
811
812    /* prefill with 1's in case we fail to get the host uuid */
813    memset(rq.ioc_client_guid, 1, sizeof(rq.ioc_client_guid));
814
815    error = gethostuuid(rq.ioc_client_guid, &timeout);
816    if (error) {
817		smb_log_info("gethostuuid failed %d", ASL_LEVEL_ERR, errno);
818    }
819
820    /* Call the kernel to make the negotiate call */
821	if (smb_ioctl_call(ctx->ct_fd, SMBIOC_NEGOTIATE, &rq) == -1)
822		error = errno;	/* Some internal error happen? */
823	else
824		error = rq.ioc_errno;	/* The real error */
825
826	if (error) {
827		smb_log_info("%s: negotiate ioctl failed, syserr = %s",
828					 ASL_LEVEL_DEBUG, __FUNCTION__, strerror(error));
829		goto out;
830	}
831
832	if (rq.ioc_negotiate_token_len > SMB_IOC_SPI_INIT_SIZE)  {
833		/* Just log it and then pretend that we didn't get any mech info */
834		smb_log_info("%s: %s mech info too large %d", ASL_LEVEL_DEBUG,
835					 __FUNCTION__, ctx->serverName, rq.ioc_negotiate_token_len);
836		rq.ioc_negotiate_token_len = 0;
837	}
838
839	/* Get the server's capablilities */
840	ctx->ct_vc_caps = rq.ioc_ret_caps;
841	/* Get the virtual circuit flags */
842	ctx->ct_vc_flags = rq.ioc_ret_vc_flags;
843	if ((rq.ioc_extra_flags & SMB_SHARING_VC) && (ctx->ct_vc_flags & SMBV_AUTH_DONE)) {
844		ctx->ct_flags |= SMBCF_AUTHORIZED;
845		getAuthInfo(ctx,  rq.ioc_max_client_size, rq.ioc_max_target_size);
846		ctx->ct_vc_shared = TRUE;
847	} else {
848		ctx->ct_vc_shared = FALSE;
849	}
850
851	/* If we have no username and the kernel does then use the name in the kernel */
852	if ((ctx->ct_setup.ioc_user[0] == 0) && rq.ioc_user[0]) {
853		strlcpy(ctx->ct_setup.ioc_user, rq.ioc_user, sizeof(ctx->ct_setup.ioc_user));
854		smb_log_info("%s: ctx->ct_setup.ioc_user = %s", ASL_LEVEL_DEBUG,
855					 __FUNCTION__, ctx->ct_setup.ioc_user);
856	}
857
858	if (ctx->mechDict) {
859		CFRelease(ctx->mechDict);
860		ctx->mechDict = NULL;
861	}
862
863	/* Server return a negotiate token, get the mech dictionary */
864	if (rq.ioc_negotiate_token_len) {
865		CFDataRef NegotiateToken = CFDataCreate(kCFAllocatorDefault, (const UInt8 *)(uintptr_t)rq.ioc_negotiate_token, rq.ioc_negotiate_token_len);
866		if (NegotiateToken) {
867			ctx->mechDict = KRBDecodeNegTokenInit(kCFAllocatorDefault, NegotiateToken);
868			CFRelease(NegotiateToken);
869		}
870	}
871
872    /* Remove this when <12991970> is fixed */
873    if ((ctx->mechDict) && (ctx->prefs.altflags & SMBFS_MNT_KERBEROS_OFF)) {
874        if (CFDictionaryGetValue(ctx->mechDict, KSPNEGOSupportsLKDC)) {
875            smb_log_info("%s: Leaving LKDC on", ASL_LEVEL_DEBUG, __FUNCTION__);
876        }
877        else {
878            smb_log_info("%s: Kerberos turned off in preferences", ASL_LEVEL_ERR, __FUNCTION__);
879            CFRelease(ctx->mechDict);
880            ctx->mechDict = NULL;
881        }
882    }
883
884    if ((ctx->mechDict == NULL) && (ctx->ct_vc_caps & SMB_CAP_EXT_SECURITY)) {
885		/*
886		 * The server does extended security, but they didn't return any mech
887		 * types. Then create a default RAW NTLMSSP mech dictionary.
888		 */
889		ctx->mechDict = KRBCreateNegTokenLegacyNTLM(kCFAllocatorDefault);
890		ctx->ct_flags |= SMBCF_RAW_NTLMSSP;
891	}
892
893out:
894	if (error) {
895		/*
896		 * If we have an EINTR error then the user canceled the connection. Never
897		 * log that as an error.
898		 */
899		if (error == EINTR)
900			error = ECANCELED;
901		/* We got an error and its not a cancel error log it */
902		if (error && (error != ECANCELED))
903			smb_log_info("%s: negotiate phase failed %s, syserr = %s", ASL_LEVEL_DEBUG, __FUNCTION__,
904						 (ctx->serverName) ? ctx->serverName : "", strerror(error));
905		close(ctx->ct_fd);
906		ctx->ct_fd = -1;
907	}
908	if (rq.ioc_negotiate_token) {
909		free((void *)((uintptr_t)(rq.ioc_negotiate_token)));
910	}
911	return (error);
912}
913
914/*
915 * Do a tree disconnect with the last tree we connected on.
916 */
917int smb_share_disconnect(struct smb_ctx *ctx)
918{
919	int error = 0;
920
921	if ((ctx->ct_fd < 0) || ((ctx->ct_flags & SMBCF_SHARE_CONN) != SMBCF_SHARE_CONN))
922		return 0;	/* Nothing to do here */
923
924	ctx->ct_sh.ioc_version = SMB_IOC_STRUCT_VERSION;
925	if (smb_ioctl_call(ctx->ct_fd, SMBIOC_TDIS, &ctx->ct_sh) == -1) {
926		error = errno;
927		if (error != ENOTCONN) {
928			smb_log_info("%s: tree disconnect failed, syserr = %s",
929						 ASL_LEVEL_DEBUG, __FUNCTION__, strerror(error));
930		}
931	}
932
933	if (!error)
934		ctx->ct_flags &= ~SMBCF_SHARE_CONN;
935
936	return error;
937}
938
939/*
940 * Do a tree connect.
941 */
942int smb_share_connect(struct smb_ctx *ctx)
943{
944	int error = 0;
945
946	if ((ctx->ct_flags & SMBCF_AUTHORIZED) == 0)
947		return EAUTH;
948
949	/* Make a tree disconnect if we have a tree connect. */
950	error = smb_share_disconnect(ctx);
951	if (error == 0) {
952		ctx->ct_sh.ioc_version = SMB_IOC_STRUCT_VERSION;
953		ctx->ct_sh.ioc_optionalSupport = 0;
954		ctx->ct_sh.ioc_fstype = 0;
955		if (smb_ioctl_call(ctx->ct_fd, SMBIOC_TCON, &ctx->ct_sh) == -1)
956			error = errno;
957	}
958	if (error == 0)
959		ctx->ct_flags |= SMBCF_SHARE_CONN;
960
961	return (error);
962}
963
964/*
965 * Return the tree connect optional support flags
966 */
967uint16_t smb_tree_conn_optional_support_flags(struct smb_ctx *ctx)
968{
969	if (ctx->ct_flags & SMBCF_SHARE_CONN)
970		return ctx->ct_sh.ioc_optionalSupport;
971	return 0;
972}
973
974/*
975 * Return the tree connect optional support flags
976 */
977uint32_t
978smb_tree_conn_fstype(struct smb_ctx *ctx)
979{
980	if (ctx->ct_flags & SMBCF_SHARE_CONN)
981		return ctx->ct_sh.ioc_fstype;
982	return 0;
983}
984
985/*
986 * Update the vc properties, calling routine should make sure we have a connection.
987 */
988void smb_get_vc_properties(struct smb_ctx *ctx)
989{
990	struct smbioc_vc_properties properties;
991
992	memset(&properties, 0, sizeof(properties));
993	properties.ioc_version = SMB_IOC_STRUCT_VERSION;
994	if (smb_ioctl_call(ctx->ct_fd, SMBIOC_VC_PROPERTIES, &properties) == -1)
995		smb_log_info("%s: Getting the vc properties failed, syserr = %s",
996					 ASL_LEVEL_ERR, __FUNCTION__, strerror(errno));
997	else {
998        size_t model_len = 0;
999
1000		ctx->ct_vc_uid = properties.uid;
1001        ctx->ct_vc_caps = properties.smb1_caps;
1002        ctx->ct_vc_smb2_caps = properties.smb2_caps;
1003		ctx->ct_vc_flags = properties.flags;
1004        ctx->ct_vc_misc_flags = properties.misc_flags;
1005        ctx->ct_vc_hflags = properties.hflags;
1006        ctx->ct_vc_hflags2 = properties.hflags2;
1007		ctx->ct_vc_txmax = properties.txmax;
1008		ctx->ct_vc_rxmax = properties.rxmax;
1009        ctx->ct_vc_wxmax = properties.wxmax;
1010        /* if we are mac to mac and SMB 2/3 then we have model info, so update it */
1011        if (ctx->model_info) {
1012            free(ctx->model_info);
1013            ctx->model_info = NULL;
1014        }
1015        model_len = strlen(properties.model_info);
1016        if ((properties.misc_flags & SMBV_OSX_SERVER) && (model_len > 0)) {
1017            /* SMBV_OSX_SERVER and we have the model information from VC */
1018            ctx->model_info = malloc(model_len);
1019            if (ctx->model_info) {
1020                memset(ctx->model_info, 0, model_len);
1021                memcpy(ctx->model_info, properties.model_info, model_len);
1022            }
1023        }
1024	}
1025}
1026
1027static void smb_session_reset_security(struct smb_ctx *ctx)
1028{
1029	ctx->ct_setup.ioc_version = SMB_IOC_STRUCT_VERSION;
1030	/* Remove any previous client name */
1031	if (ctx->ct_setup.ioc_gss_client_name) {
1032		free((void *)(uintptr_t)ctx->ct_setup.ioc_gss_client_name);
1033		ctx->ct_setup.ioc_gss_client_name = USER_ADDR_NULL;
1034	}
1035	ctx->ct_setup.ioc_gss_client_size = 0;
1036	ctx->ct_setup.ioc_gss_client_nt = GSSD_STRING_NAME;
1037
1038	/* Remove any previous target name */
1039	if (ctx->ct_setup.ioc_gss_target_name) {
1040		free((void *)(uintptr_t)ctx->ct_setup.ioc_gss_target_name);
1041		ctx->ct_setup.ioc_gss_target_name = USER_ADDR_NULL;
1042	}
1043	ctx->ct_setup.ioc_gss_target_size = 0;
1044	ctx->ct_setup.ioc_gss_target_nt = GSSD_STRING_NAME;
1045}
1046
1047static int MakeTarget(struct smb_ctx *ctx, const char *serverPrincipal, gssd_nametype serverNameType)
1048{
1049	if (serverPrincipal) {
1050		ctx->ct_setup.ioc_gss_target_size = (uint32_t)strlen(serverPrincipal) + 1;
1051		ctx->ct_setup.ioc_gss_target_name = CAST_USER_ADDR_T(malloc(ctx->ct_setup.ioc_gss_target_size));
1052		if (ctx->ct_setup.ioc_gss_target_name) {
1053			memcpy((char *)(uintptr_t)ctx->ct_setup.ioc_gss_target_name, serverPrincipal,
1054			       ctx->ct_setup.ioc_gss_target_size);
1055		}
1056	} else {
1057		GetTargetNameUsingHostName(ctx);
1058	}
1059	/* Set the target name type */
1060	ctx->ct_setup.ioc_gss_target_nt = serverNameType;
1061
1062	/* Couldn't get a target name, nothing more we can do here */
1063	if (ctx->ct_setup.ioc_gss_target_name == USER_ADDR_NULL) {
1064		smb_log_info("%s: Couldn't create server name!", ASL_LEVEL_ERR, __FUNCTION__);
1065		ctx->ct_setup.ioc_gss_target_size = 0;
1066		return ENOMEM;
1067	}
1068	return 0;
1069}
1070
1071static int smb_session_send_auth(struct smb_ctx *ctx)
1072{
1073	int btmmAddress = isBTMMAddress(ctx->serverNameRef);
1074	int error = 0;
1075
1076	/* Doing extended security, but they don't support SPEGNO NTLMSSP */
1077	if (ctx->ct_flags & SMBCF_RAW_NTLMSSP) {
1078		/* We are doing RAW NTLMSSP */
1079		ctx->ct_setup.ioc_userflags |= SMBV_RAW_NTLMSSP;
1080	}
1081
1082	if (smb_ioctl_call(ctx->ct_fd, SMBIOC_SSNSETUP, &ctx->ct_setup) == -1)
1083		error = errno;
1084
1085	if (error == 0) {
1086
1087		ctx->ct_flags |= SMBCF_AUTHORIZED;
1088		smb_get_vc_properties(ctx);	/* Update the the vc flags in case they have changed */
1089
1090		if (btmmAddress) {
1091			LogToMessageTracer(SMB_BTMM_DOMAIN, "success", "success", NULL,
1092					   "authentication succeeded");
1093		}
1094	} else {
1095		if (btmmAddress) {
1096			LogToMessageTracer(SMB_BTMM_DOMAIN, "failure to authenticate",
1097					   "failure", NULL, "authentication failed %d",
1098					   error);
1099		}
1100	}
1101
1102	return (error);
1103}
1104
1105
1106/*
1107 * It seems that the NTLM client principal name can have two different formats:
1108 *	1. domain\username - Traditional NTLM style
1109 *	2. username@domain - Kerberos style
1110 *
1111 * Seems Heimdal always displays and uses the Kerberos style so lets always
1112 * create that style. If the domain contians a server name then it should begin
1113 * with a backslash.
1114 */
1115static void
1116smb_session_set_user(struct smb_ctx *ctx, const char *clientpn)
1117{
1118	char *k_component;
1119
1120	if (ctx->ct_setup.ioc_user[0]) {
1121		return; /* We already have a user name nothing to do here */
1122	}
1123	strlcpy(ctx->ct_setup.ioc_user, clientpn, SMB_MAXUSERNAMELEN + 1);
1124	k_component = strchr(ctx->ct_setup.ioc_user, KERBEROS_INSTANCE_DELIMITER);
1125	if (k_component == NULL) {
1126		k_component = strchr(ctx->ct_setup.ioc_user, KERBEROS_REALM_DELIMITER);
1127	}
1128	if (k_component) {
1129		*k_component = '\0';
1130	}
1131}
1132
1133static gssd_nametype get_nt_from_oid(gss_OID mech)
1134{
1135	if (gss_oid_equal(GSS_KRB5_MECHANISM, mech))
1136		return GSSD_KRB5_PRINCIPAL;
1137	else if (gss_oid_equal(GSS_NTLM_MECHANISM, mech))
1138		return GSSD_NTLM_PRINCIPAL;
1139	else {
1140		return GSSD_NTLM_PRINCIPAL;	/* Not sure what we should return as a default */
1141	}
1142}
1143
1144static int smb_session_set_client(struct smb_ctx *ctx, const char *clientpn, gssd_nametype nt)
1145{
1146	if (ctx->ct_setup.ioc_gss_client_name) {
1147		free((void *)(uintptr_t)ctx->ct_setup.ioc_gss_client_name);
1148		ctx->ct_setup.ioc_gss_client_name = USER_ADDR_NULL;
1149	}
1150
1151	ctx->ct_setup.ioc_gss_client_size = (uint32_t)strlen(clientpn) + 1;
1152	ctx->ct_setup.ioc_gss_client_name = CAST_USER_ADDR_T(malloc(ctx->ct_setup.ioc_gss_client_size));
1153	if (ctx->ct_setup.ioc_gss_client_name) {
1154		memcpy((char *)(uintptr_t)ctx->ct_setup.ioc_gss_client_name, clientpn,
1155		       ctx->ct_setup.ioc_gss_client_size);
1156		ctx->ct_setup.ioc_gss_client_nt = nt;
1157	} else {
1158		smb_log_info("Creating client name '%s' failed!", ASL_LEVEL_DEBUG, clientpn);
1159		ctx->ct_setup.ioc_gss_client_size = 0;
1160		return (ENOMEM);
1161	}
1162	return (0);
1163}
1164
1165/*
1166 * It seems that the NTLM client principal name can have two different formats:
1167 *	1. domain\username - Traditional NTLM style
1168 *	2. username@domain - Kerberos style
1169 *
1170 * Seems Heimdal always displays and uses the Kerberos style so lets always
1171 * create that style. If the domain contians a server name then it should begin
1172 * with a backslash.
1173 */
1174static int
1175smb_session_set_ntlm_name(struct smb_ctx *ctx, const char *name, const char *domain)
1176{
1177	char ntlm_name[SMB_MAX_NTLM_NAME + 1];
1178
1179	strlcpy(ntlm_name, name, sizeof(ntlm_name));
1180	strlcat(ntlm_name, "@", sizeof(ntlm_name));
1181	strlcat(ntlm_name, domain, sizeof(ntlm_name));
1182	return (smb_session_set_client(ctx, ntlm_name, GSSD_NTLM_PRINCIPAL));
1183}
1184
1185static int
1186smb_session_cache(struct smb_ctx *ctx, gss_OID mech, const char *name,
1187				  const char *domain)
1188{
1189	struct smb_gss_cred_list *cl;
1190	struct smb_gss_cred_list_entry *ep, *tmp;
1191	int error;
1192
1193	error = smb_gss_get_cred_list(&cl, mech);
1194	if (error)
1195		return (error);
1196
1197	if (TAILQ_EMPTY(cl)) { // No credentials nothing to try
1198		error = ENOENT;
1199		goto out;
1200	}
1201
1202	if (name && *name) {
1203		TAILQ_FOREACH_SAFE(ep, cl, next, tmp) {
1204			if (smb_gss_match_cred_entry(ep, mech, name, domain)) {
1205				error = smb_session_set_client(ctx, ep->principal, get_nt_from_oid(mech));
1206				if (error)
1207					goto out;
1208				error = smb_session_send_auth(ctx);
1209				if (error == 0)
1210					goto out;
1211				TAILQ_REMOVE(cl, ep, next);
1212				smb_gss_free_cred_entry(&ep);
1213			}
1214		}
1215		/* They gave us a user name and we didn't find a match, return EAUTH */
1216		return (EAUTH);
1217
1218	}
1219
1220	/* Found no creds that match the user name, try the others now */
1221	TAILQ_FOREACH(ep, cl, next) {
1222		error = smb_session_set_client(ctx, ep->principal, get_nt_from_oid(mech));
1223		if (error)
1224			goto out;
1225		/* Set the user name so the kernel will have a copy */
1226		smb_session_set_user(ctx, ep->principal);
1227		error = smb_session_send_auth(ctx);
1228		if (error) {
1229			/* We failed clear out the user name */
1230			smb_ctx_setuser(ctx, "");
1231		} else {
1232			goto out;
1233		}
1234	}
1235out:
1236
1237	smb_gss_free_cred_list(&cl);
1238	return (error);
1239}
1240
1241/*
1242 * Given a authentication dictionary use the information provided to authenticate
1243 * the connection.
1244 */
1245static int AuthenticateWithDictionary(struct smb_ctx *ctx, CFDictionaryRef authInfoDict)
1246{
1247	int error = 0;
1248	CFStringRef clientPrincipalRef = CFDictionaryGetValue(authInfoDict, kNAHClientPrincipal);
1249	CFStringRef serverPrincipalRef = CFDictionaryGetValue(authInfoDict, kNAHServerPrincipal);
1250	CFNumberRef clientNameTypeRef = CFDictionaryGetValue (authInfoDict, kNAHClientNameTypeGSSD);
1251	CFNumberRef serverNameTypeRef = CFDictionaryGetValue (authInfoDict, kNAHServerNameTypeGSSD);
1252	CFStringRef inferedUserNameRef = CFDictionaryGetValue(authInfoDict, kNAHInferredLabel);
1253
1254	char clientPrincipal[SMB_MAX_KERB_PN] = {0};
1255	char serverPrincipal[SMB_MAX_KERB_PN] = {0};
1256	char userName[SMB_MAX_KERB_PN] = {0};
1257	int32_t clientNameType = 0, serverNameType = 0;
1258
1259	/* We require the dictionary to contain all the information needed */
1260	if (!clientPrincipalRef || !serverPrincipalRef || !clientNameTypeRef || !serverNameTypeRef) {
1261		smb_log_info("%s: authorization dictionary is incomplete, syserr = %s",
1262					 ASL_LEVEL_ERR, __FUNCTION__, strerror(EINVAL));
1263		return EINVAL;	/* Should we return EAUTH here? */
1264	}
1265	CFStringGetCString(clientPrincipalRef, clientPrincipal, SMB_MAX_KERB_PN, kCFStringEncodingUTF8);
1266    if (inferedUserNameRef) {
1267        CFStringGetCString(inferedUserNameRef, userName, SMB_MAX_KERB_PN, kCFStringEncodingUTF8);
1268        smb_log_info("%s: infered User Name = %s", ASL_LEVEL_DEBUG, __FUNCTION__, userName);
1269    } else {
1270        CFStringGetCString(clientPrincipalRef, userName, SMB_MAX_KERB_PN, kCFStringEncodingUTF8);
1271    }
1272	CFStringGetCString(serverPrincipalRef, serverPrincipal, SMB_MAX_KERB_PN, kCFStringEncodingUTF8);
1273	CFNumberGetValue(clientNameTypeRef, kCFNumberSInt32Type, &clientNameType);
1274	CFNumberGetValue(serverNameTypeRef, kCFNumberSInt32Type, &serverNameType);
1275
1276	smb_session_reset_security(ctx);
1277
1278	/*
1279	 * LHA reqested we do it this way, may want to change this in the future. The
1280	 * serverIsDomainController flag means we were passed in an AD Domain name but
1281	 * we connected to one of domain controllers. We are being passed down the
1282	 * AD domain name, but if we used that name kerberos may fail. If we put
1283	 * the domain controller name into to targe name, then this should always
1284	 * work. In the future I think we should pass the domain controllers up to
1285	 * and let Helimdal use it.
1286	 */
1287	if (ctx->serverIsDomainController) {
1288		error = MakeTarget(ctx, NULL, GSSD_HOSTBASED);
1289
1290	} else {
1291		error = MakeTarget(ctx, serverPrincipal, serverNameType);
1292
1293	}
1294
1295	/* We always create a target name if we are doing extended security. */
1296	if (error)
1297		return (error);
1298
1299	/* Now see if we have a client name */
1300	smb_session_set_user(ctx, userName);
1301	error = smb_session_set_client(ctx, clientPrincipal, clientNameType);
1302	if (error)
1303		return (error);
1304
1305	error = smb_session_send_auth(ctx);
1306	/* Will need to reset this if these names ever become binary */
1307	smb_log_info("%s: Authentication %s, client principal %s - %d server principal %s - %d gss client principal %s - %d gss server principal %s - %d for %s",
1308				 ASL_LEVEL_DEBUG, __FUNCTION__,
1309				 (error) ? "FAILED" : "SUCCEED",
1310				 clientPrincipal, clientNameType,
1311				 serverPrincipal, serverNameType,
1312				 (char *)(uintptr_t)ctx->ct_setup.ioc_gss_client_name,
1313				 ctx->ct_setup.ioc_gss_client_nt,
1314				 (char *)(uintptr_t)ctx->ct_setup.ioc_gss_target_name,
1315				 ctx->ct_setup.ioc_gss_target_nt,
1316				 ctx->serverName);
1317	return (error);
1318}
1319
1320/*
1321 * Use the information obtain from the URL to authenticate. This may include
1322 * looking into the cache.
1323 */
1324static int AuthenticateWithURL(struct smb_ctx *ctx, Boolean useNTLM)
1325{
1326	int foundInCache = FALSE;
1327	const char *name = ctx->ct_setup.ioc_user;
1328	const char *domain = NULL;
1329	gss_OID mech = (useNTLM) ? GSS_NTLM_MECHANISM : GSS_KRB5_MECHANISM;
1330	const char *passwd = NULL;
1331	int error = 0;
1332	char serverDomain[SMB_MAX_NTLM_NAME + 1];
1333
1334	smb_session_reset_security(ctx);
1335
1336	/* We always create a target name if we are doing extended security. */
1337	error = MakeTarget(ctx, NULL, GSSD_HOSTBASED);
1338	if (error) {
1339		goto done;
1340	}
1341	/* The NTLM creds are store in user@domain or user@server */
1342	if (useNTLM) {
1343		if (ctx->ct_setup.ioc_domain[0]) {
1344			/* We have a domain use it */
1345			domain = ctx->ct_setup.ioc_domain;
1346		} else {
1347			/*
1348			 * No domain use the server name as the domain, but make sure it
1349			 * starts with a backslash so Hemidal can tell that its a server name
1350			 */
1351			strlcpy(serverDomain, "\\", sizeof(serverDomain));
1352			strlcat(serverDomain, ctx->serverName, sizeof(serverDomain));
1353			domain = serverDomain;
1354		}
1355	}
1356	/*
1357	 * Try and acquire credentials if we were given a name, domain and/or password,
1358	 * then use those credentials. If we can't acquire credentials or send
1359	 * auth fails, return EAUTH, since we can't do what the user wanted and
1360	 * it is likely this name and password are really for this type of authentication.
1361	 */
1362	if (*name && (ctx->ct_flags & SMBCF_EXPLICITPWD)) {
1363		void *gssCreds = NULL;
1364
1365		passwd = ctx->ct_setup.ioc_password;
1366		if (useNTLM) {
1367			error = smb_session_set_ntlm_name(ctx, name, domain);
1368			if (error) {
1369				goto done;
1370			}
1371
1372			if (!smb_acquire_ntlm_cred(name, domain, passwd, &gssCreds)) {
1373				error = EAUTH;
1374				goto done;
1375			}
1376		} else {
1377			char *principal;
1378
1379			if (!smb_acquire_krb5_cred(name, NULL, passwd, &gssCreds)) {
1380				error = EAUTH;
1381				goto done;
1382			}
1383
1384			principal = smb_gss_principal_from_cred(gssCreds);
1385			if (principal) {
1386				error = smb_session_set_client(ctx, principal, GSSD_KRB5_PRINCIPAL);
1387				free(principal);
1388			} else {
1389				error = EAUTH;
1390			}
1391
1392			if (error) {
1393				smb_release_gss_cred(gssCreds, error);
1394				goto done;
1395			}
1396		}
1397		error = smb_session_send_auth(ctx);
1398		smb_release_gss_cred(gssCreds, error);
1399	} else {
1400		/* Otherwise try the credentials in the cache */
1401		error = smb_session_cache(ctx, mech, name, domain);
1402		if (error == ENOENT) {
1403			error = EAUTH;
1404		}
1405		foundInCache = TRUE;
1406	}
1407done:
1408	/* Will need to reset this if these names ever become binary */
1409	smb_log_info("%s: Authentication %s%s, user name %s %s '%s' gss client principal %s - %d gss server principal %s - %d %s for %s",
1410				 ASL_LEVEL_DEBUG, __FUNCTION__,
1411				 (error) ? "FAILED" : "SUCCEED",
1412				 (foundInCache) ? " in cache" : "",
1413				 ctx->ct_setup.ioc_user,
1414				 (useNTLM) ? "domain" : "using",
1415				 (useNTLM) ? ctx->ct_setup.ioc_domain : "KERBEROS",
1416				 (char *)(uintptr_t)ctx->ct_setup.ioc_gss_client_name,
1417				 ctx->ct_setup.ioc_gss_client_nt,
1418				 (char *)(uintptr_t)ctx->ct_setup.ioc_gss_target_name,
1419				 ctx->ct_setup.ioc_gss_target_nt,
1420				 (passwd) ? "" : "obtain from cache",
1421				 ctx->serverName);
1422	return (error);
1423}
1424
1425
1426/*
1427 * Guest authentication, always NTLMSSP.
1428 */
1429static int AuthenticateWithGuest(struct smb_ctx *ctx)
1430{
1431	int foundInCache = FALSE;
1432	int error = 0;
1433	void *gssCreds = NULL;
1434
1435	smb_session_reset_security(ctx);
1436
1437	/* We always create a target name if we are doing extended security. */
1438	error = MakeTarget(ctx, NULL, GSSD_HOSTBASED);
1439	if (error) {
1440		goto done;
1441	}
1442
1443	/* We should search the cache first to see if we already have guest creds */
1444	error = smb_session_cache(ctx, GSS_NTLM_MECHANISM, ctx->ct_setup.ioc_user, NULL);
1445	if (! error) {
1446		foundInCache = TRUE;
1447		goto done;
1448	}
1449	/* We never have a domain name in the guest case */
1450	error = smb_session_set_ntlm_name(ctx, ctx->ct_setup.ioc_user, "");
1451	if (error) {
1452		goto done;
1453	}
1454	/* We never have a domain or password  in the guest case */
1455	if (!smb_acquire_ntlm_cred(ctx->ct_setup.ioc_user, "",  "", &gssCreds)) {
1456		error = EAUTH;
1457		goto done;
1458	}
1459	error = smb_session_send_auth(ctx);
1460	smb_release_gss_cred(gssCreds, error);
1461
1462done:
1463	/* Will need to reset this if these names ever become binary */
1464	smb_log_info("%s: Authentication %s%s, user name %s gss client principal %s - %d gss server principal %s - %d for %s",
1465				 ASL_LEVEL_DEBUG, __FUNCTION__,
1466				 (error) ? "FAILED" : "SUCCEED",
1467				 (foundInCache) ? " in cache" : "",
1468				 ctx->ct_setup.ioc_user,
1469				 (char *)(uintptr_t)ctx->ct_setup.ioc_gss_client_name,
1470				 ctx->ct_setup.ioc_gss_client_nt,
1471				 (char *)(uintptr_t)ctx->ct_setup.ioc_gss_target_name,
1472				 ctx->ct_setup.ioc_gss_target_nt,
1473				 ctx->serverName);
1474	return (error);
1475}
1476
1477/*
1478 * Anonymous authentication.
1479 */
1480static int AuthenticateWithAnonymous(struct smb_ctx *ctx)
1481{
1482	int error;
1483
1484	smb_session_reset_security(ctx);
1485	ctx->ct_setup.ioc_gss_client_nt = GSSD_ANONYMOUS;
1486	error = MakeTarget(ctx, NULL, GSSD_HOSTBASED);
1487	if (error)
1488		return (error);
1489	error = smb_session_send_auth(ctx);
1490	return (error);
1491}
1492
1493/*
1494 * Non Extended security, need to depricated this authentication method. Since
1495 * this is the only way to do clear text and share level security removing it
1496 * will break some NetApp sites.
1497 */
1498static int AuthenticateWithNonExtSecurity(struct smb_ctx *ctx)
1499{
1500	int error;
1501
1502	smb_session_reset_security(ctx);
1503	error = smb_session_send_auth(ctx);
1504	smb_log_info("%s: Authentication %s, user name %s domain %s for %s",
1505				 ASL_LEVEL_DEBUG, __FUNCTION__, (error) ? "FAILED" : "SUCCEED",
1506				 ctx->ct_setup.ioc_user, ctx->ct_setup.ioc_domain,
1507				 ctx->serverName);
1508	return error;
1509}
1510
1511static int smb_session_security(struct smb_ctx *ctx, CFDictionaryRef authInfoDict)
1512{
1513	Boolean serverSupportsKerb = serverSupportsKerberos(ctx->mechDict);
1514
1515	ctx->ct_flags &= ~SMBCF_AUTHORIZED;
1516	if ((ctx->ct_flags & SMBCF_CONNECTED) != SMBCF_CONNECTED) {
1517		return EPIPE;
1518	}
1519
1520	/* Allow anonymous and guest to skip any min auth requirements */
1521	if ((ctx->ct_setup.ioc_userflags & SMBV_ANONYMOUS_ACCESS) ||
1522		(ctx->ct_setup.ioc_userflags & SMBV_GUEST_ACCESS)) {
1523		goto skip_minauth_check;
1524	}
1525
1526	if (!serverSupportsKerb && (ctx->prefs.minAuthAllowed == SMB_MINAUTH_KERBEROS)) {
1527		smb_log_info("%s: Server doesn't support Kerberos and its required!", ASL_LEVEL_ERR, __FUNCTION__);
1528		return ENETFSNOAUTHMECHSUPP;
1529	}
1530
1531	/* Server requires clear text password, but we are not doing clear text passwords. */
1532	if (((ctx->ct_vc_flags & SMBV_ENCRYPT_PASSWORD) != SMBV_ENCRYPT_PASSWORD) &&
1533		(ctx->prefs.minAuthAllowed != SMB_MINAUTH)) {
1534		smb_log_info("%s: Clear text passwords are not allowed!", ASL_LEVEL_ERR, __FUNCTION__);
1535		return ENETFSNOAUTHMECHSUPP;
1536	}
1537
1538    /*
1539     * If non extended security, we do NTLMv2 and if that fails, try NTLMv1.
1540     * For extended security, GSS only allows a min of NTLM v2.
1541     *
1542     * The new default min auth is NTLMv2.
1543     */
1544	if (((ctx->ct_vc_caps & SMB_CAP_EXT_SECURITY) != SMB_CAP_EXT_SECURITY) &&
1545		(ctx->prefs.minAuthAllowed >= SMB_MINAUTH_NTLMV2)) {
1546        ctx->ct_setup.ioc_userflags |= SMBV_NO_NTLMV1;
1547    }
1548
1549	/* They want to use Kerberos, but the server doesn't support it */
1550	if ((ctx->ct_setup.ioc_userflags & SMBV_KERBEROS_ACCESS) && !serverSupportsKerb) {
1551		smb_log_info("%s: Server doesn't support Kerberos, syserr = %s",
1552					 ASL_LEVEL_ERR, __FUNCTION__, strerror(EAUTH));
1553		return ENETFSNOAUTHMECHSUPP;
1554	}
1555
1556skip_minauth_check:
1557
1558	/*
1559	 * Not doing extended security no more processing needed just send the
1560	 * authenication message.
1561	 */
1562	if ((ctx->ct_vc_caps & SMB_CAP_EXT_SECURITY) != SMB_CAP_EXT_SECURITY) {
1563		netfs_log_message_tracer_auth_type((char *)"non-extended");
1564		return AuthenticateWithNonExtSecurity(ctx);
1565	}
1566	netfs_log_message_tracer_auth_type((char *)"extended");
1567
1568	/*
1569	 * They gave us a authentication dictionary then use it.
1570	 */
1571	if (authInfoDict) {
1572		return AuthenticateWithDictionary(ctx, authInfoDict);
1573	}
1574
1575	/* They want us to use anonymous */
1576	if (ctx->ct_setup.ioc_userflags & SMBV_ANONYMOUS_ACCESS) {
1577		return AuthenticateWithAnonymous(ctx);
1578	}
1579
1580	/* They want us to use guest */
1581	if (ctx->ct_setup.ioc_userflags & SMBV_GUEST_ACCESS) {
1582		return AuthenticateWithGuest(ctx);
1583	}
1584
1585	/*
1586	 * If the server support kerberos then we try it first and if that fails
1587	 * then try NTLM.
1588	 */
1589	if (serverSupportsKerb) {
1590		int error;
1591
1592		ctx->ct_setup.ioc_userflags |= SMBV_KERBEROS_ACCESS;
1593		error = AuthenticateWithURL(ctx, FALSE);
1594		if (!error || (ctx->prefs.minAuthAllowed == SMB_MINAUTH_KERBEROS)) {
1595			if (!error)
1596			return error;
1597		}
1598		/* Only fall through if the min level allows it */
1599		/* Kerberos failed, clear the flag */
1600		ctx->ct_setup.ioc_userflags &= ~SMBV_KERBEROS_ACCESS;
1601	}
1602	/* Ok try NTLM */
1603	return AuthenticateWithURL(ctx, TRUE);
1604}
1605
1606/*
1607 * Resolve the name using NetBIOS.
1608 */
1609static int
1610smb_resolve_netbios_name(struct smb_ctx *ctx, CFMutableArrayRef *outAddressArray,
1611						 Boolean loopBackAllowed)
1612{
1613	char * netbios_name = NULL;
1614	int error = 0;
1615	struct smb_prefs *prefs = &ctx->prefs;
1616
1617	/*
1618	 * We uppercase and convert the server name given in the URL to Windows Code
1619	 * Page. We assume the server name is a a UTF8 name, if not this could fail,
1620	 * but it only fails on port 139.
1621	 */
1622	if (ctx->serverName)
1623		netbios_name = convert_utf8_to_wincs(ctx->serverName, prefs->WinCodePage, TRUE);
1624
1625	/* Must have a NetBIOS name if we are going to resovle it using NetBIOS */
1626	if ((netbios_name == NULL) || (strlen(netbios_name) > SMB_MAXNetBIOSNAMELEN)) {
1627		error = EHOSTUNREACH;
1628		goto done;
1629	}
1630	/*
1631	 * If we have a "NetBIOSDNSName" then the configuration file contained the
1632	 * DNS name or DOT IP Notification address that we should be using to connect
1633	 * to the server.
1634	 */
1635	if (ctx->prefs.NetBIOSDNSName) {
1636		char NetBIOSDNSName[SMB_MAX_DNS_SRVNAMELEN+1];
1637
1638		CFStringGetCString(ctx->prefs.NetBIOSDNSName, NetBIOSDNSName,
1639						   sizeof(NetBIOSDNSName), kCFStringEncodingUTF8);
1640		error = resolvehost(NetBIOSDNSName, outAddressArray, netbios_name,
1641							prefs->tcp_port, loopBackAllowed, prefs->tryBothPorts);
1642	} else {
1643		error = nbns_resolvename(&ctx->ct_nb, prefs, netbios_name, NBT_SERVER,
1644								 outAddressArray, prefs->tcp_port,  loopBackAllowed,
1645								 prefs->tryBothPorts, &ctx->ct_cancel);
1646	}
1647
1648	if (error) {
1649		/*
1650		 * We have something that looked like a NetBIOS name, but we couldn't
1651		 * resolve it try DNS.
1652		 */
1653		goto done;
1654	}
1655	/*
1656	 * We now have a list of address and we have a NetBIOS name. Use the NetBIOS
1657	 * name as the server name. This should always work for the tree connect.
1658	 */
1659	strlcpy(ctx->ct_ssn.ioc_srvname, netbios_name, sizeof(ctx->ct_ssn.ioc_srvname));
1660
1661done:
1662	/* If we get an ELOOP then we found the address, just happens to be the local address */
1663	if ((error == 0) || (error == ELOOP))
1664		ctx->ct_flags |= SMBCF_RESOLVED; /* We are done no more resolving needed */
1665	/* Done with the name, so free it */
1666	if (netbios_name)
1667		free(netbios_name);
1668
1669	return error;
1670}
1671
1672/*
1673 * Resolve the name using Bonjour.
1674 */
1675static int
1676smb_resolve_bonjour_name(struct smb_ctx *ctx, CFMutableArrayRef *outAddressArray,
1677						 Boolean loopBackAllowed)
1678{
1679	CFNetServiceRef theService = _CFNetServiceCreateFromURL(NULL, ctx->ct_url);
1680	CFStringRef serviceNameType;
1681	CFStringRef displayServiceName;
1682	CFStreamError debug_error = {(CFStreamErrorDomain)0, 0};
1683	CFArrayRef retAddresses = NULL;
1684	CFIndex numAddresses = 0;
1685	CFIndex ii;
1686	CFMutableArrayRef addressArray = NULL;
1687	CFMutableDataRef addressData;
1688	int error = 0;
1689	int localAddressFound = FALSE;
1690
1691	/* Not a Bonjour Service Name, need to resolve with some other method */
1692	if (theService == NULL)
1693		return EHOSTUNREACH;
1694
1695	 /* Error or no error we are done no more resolving needed */
1696	ctx->ct_flags |= SMBCF_RESOLVED;
1697	/* Bonjour service name lookup, never fallback. */
1698	ctx->prefs.tryBothPorts = FALSE;
1699
1700	addressArray = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks);
1701	if (!addressArray) {
1702		error = ENOMEM;
1703		goto done;
1704	}
1705
1706	serviceNameType = CFNetServiceGetType(theService);
1707	displayServiceName = CFNetServiceGetName(theService);
1708
1709	/* Should we be doing CFNetServiceResolveWithTimeout async? */
1710	if (serviceNameType && (CFStringCompare(serviceNameType, CFSTR(SMB_BonjourServiceNameType), kCFCompareCaseInsensitive) != kCFCompareEqualTo)) {
1711		const char *serviceNameTypeCString = CFStringGetCStringPtr(serviceNameType, kCFStringEncodingUTF8);
1712
1713		smb_log_info("Wrong service type for smb, should be %s got %s", ASL_LEVEL_ERR,
1714					 SMB_BonjourServiceNameType, (serviceNameTypeCString) ? serviceNameTypeCString : "NULL");
1715	} else if (CFNetServiceResolveWithTimeout(theService, 30, &debug_error) == TRUE) {
1716		retAddresses = CFNetServiceGetAddressing(theService);
1717		numAddresses = (retAddresses) ? CFArrayGetCount(retAddresses) : 0;
1718		smb_log_info("Bonjour lookup found %ld address entries.",
1719					 ASL_LEVEL_DEBUG, numAddresses);
1720	}
1721	else
1722		smb_log_info("Looking up Bonjour service name timeouted? error %ld:%d",
1723					 ASL_LEVEL_DEBUG, debug_error.domain, (int)debug_error.error);
1724
1725	for (ii=0; ii<numAddresses; ii++) {
1726		struct sockaddr *sockAddr = (struct sockaddr*) CFDataGetBytePtr ((CFDataRef) CFArrayGetValueAtIndex (retAddresses, ii));
1727		if (sockAddr == NULL) {
1728			smb_log_info("We have a NULL sock address pointer, shouldn't happed, syserr = %s",
1729						 ASL_LEVEL_ERR, strerror(errno));
1730			continue;
1731		} else if ((sockAddr->sa_family != AF_INET) && (sockAddr->sa_family != AF_INET6)) {
1732			smb_log_info("Unknown sa_family = %d sa_len = %d", ASL_LEVEL_DEBUG,
1733						 sockAddr->sa_family, sockAddr->sa_len);
1734			continue;
1735		} else {
1736			struct connectAddress conn;
1737			in_port_t port;
1738
1739			memset(&conn, 0, sizeof(conn));
1740			memcpy(&conn.addr, sockAddr, sockAddr->sa_len);
1741			conn.so = -1;	/* Default to socket create failed */
1742			port = (conn.addr.sa_family == AF_INET) ? conn.in4.sin_port : conn.in6.sin6_port;
1743			/* We don't support port 139 with Bonjour */
1744			if (port == htons(NBSS_TCP_PORT_139)) {
1745				smb_log_info("Port 139 is not support with Bonjour skipping sa_family %d",
1746							 ASL_LEVEL_DEBUG, sockAddr->sa_family);
1747				continue;
1748			}
1749			/* Check to see if we have a loopback bonjour name */
1750			if (isLocalIPAddress(sockAddr, port, loopBackAllowed)) {
1751				smb_log_info("The address for `%s' is a loopback address, not allowed!",
1752							 ASL_LEVEL_DEBUG, ctx->serverName);
1753				localAddressFound = TRUE;
1754				continue;
1755			}
1756
1757
1758			addressData = CFDataCreateMutable(NULL, 0);
1759			if (addressData) {
1760				CFDataAppendBytes(addressData, (const UInt8 *)&conn, (CFIndex)sizeof(conn));
1761				CFArrayAppendValue(addressArray, addressData);
1762				CFRelease(addressData);
1763			}
1764			smb_log_info("Resolve for Bonjour[%ld] found family %d sin_port = %d",
1765						 ASL_LEVEL_DEBUG, ii, sockAddr->sa_family, port);
1766		}
1767	}
1768
1769	if (CFArrayGetCount(addressArray) == 0) {
1770		/*
1771		 * If the only address we found was a loopback address, then return the
1772		 * ELOOP (AFP error) otherwise return not reachable.
1773		 */
1774		if (localAddressFound) {
1775			error = ELOOP;
1776		} else {
1777			error = EHOSTUNREACH;
1778		}
1779		goto done;
1780	}
1781
1782	if (displayServiceName) {
1783		if (ctx->serverNameRef)
1784			CFRelease(ctx->serverNameRef);
1785		ctx->serverNameRef = CFStringCreateCopy(kCFAllocatorDefault, displayServiceName);
1786	}
1787
1788	/*
1789	 * We now have a list of address and we have a Bonjour name. Use the Bonjour
1790	 * name as the server name. This should always work for the tree connect.
1791	 */
1792	strlcpy(ctx->ct_ssn.ioc_srvname, ctx->serverName, sizeof(ctx->ct_ssn.ioc_srvname));
1793
1794done:
1795	if (theService)
1796		CFRelease(theService);
1797
1798	if (error) {
1799		if (addressArray)
1800			CFRelease(addressArray);
1801		addressArray = NULL;
1802	}
1803	*outAddressArray = addressArray;
1804	return error;
1805}
1806
1807/*
1808 * Resolve the name using DNS.
1809 * By the time we get here, we have already tried resolving it as a Bonjour
1810 * name, then tried as a NetBios name and its not either of those.
1811 */
1812static int smb_resolve_dns_name(struct smb_ctx *ctx, CFMutableArrayRef *outAddressArray,
1813								Boolean loopBackAllowed)
1814{
1815	int error;
1816    size_t len;
1817    char *temp_name = NULL;
1818    char *scope = NULL;
1819
1820	error = resolvehost(ctx->serverName, outAddressArray, NULL, ctx->prefs.tcp_port,
1821						loopBackAllowed, ctx->prefs.tryBothPorts);
1822	if (error == 0) {
1823        ctx->ct_flags |= SMBCF_RESOLVED;
1824
1825        /*
1826         * Note: getaddrinfo() and inet_pton() both will give errors if its
1827         * an IPv6 address enclosed by brackets. I cant find a way to detect
1828         * if the address is IPv6 or not if the brackets are present. Thus, the
1829         * check for '[' at the start and ']' at the end of the string.
1830         */
1831
1832        /* Check to see if its IPv6 and if it is IPv6 with brackets */
1833        len = strnlen(ctx->serverName, 1024);  /* assume hostname < 1024 */
1834        if ((len > 3) && (ctx->serverName[0] == '[') && (ctx->serverName[len - 1] == ']')) {
1835            /* Seems to be IPv6 with brackets */
1836            temp_name = malloc(len);
1837
1838            if (temp_name != NULL) {
1839                /*
1840                 * Copy string and skip beginning '[' (&hostname[1]) and
1841                 * ending ']' (len - 1)
1842                 */
1843                strlcpy(temp_name, &ctx->serverName[1], len - 1);
1844
1845                /*
1846                 * Strip off the scope if one is found as our own server does
1847                 * not like the %en0 in the Tree Connect
1848                 */
1849                scope = strrchr(temp_name, '%');
1850                if (scope != NULL) {
1851                    /* Found a scope, so lop it off */
1852                    *scope = NULL;
1853                }
1854
1855                strlcpy(ctx->ct_ssn.ioc_srvname, temp_name, sizeof(ctx->ct_ssn.ioc_srvname));
1856
1857                free(temp_name);
1858            }
1859            else {
1860                error = ENOMEM;
1861            }
1862        }
1863        else {
1864            strlcpy(ctx->ct_ssn.ioc_srvname, ctx->serverName, sizeof(ctx->ct_ssn.ioc_srvname));
1865        }
1866	}
1867
1868	return error;
1869}
1870
1871/*
1872 * We want to attempt to resolve the name using any available method.
1873 *	1.	Bonjour Lookup: We always check to see if the name is a Bonjour
1874 *		service name first. If it's a Bonjour name than we either resolve it or
1875 *		fail the whole connection. If we succeed then we always use the port give
1876 *		to us by Bonjour.
1877 *
1878 *	2.	NetBIOS Lookup: If not a Bonjour name then by default attempt to resolve it
1879 *		using NetBIOS.
1880 *
1881 *	3.	DNS Lookup: If all else fails attempt to look it up with DNS.
1882 */
1883static int
1884smb_resolve(struct smb_ctx *ctx, CFMutableArrayRef *outAddressArray,
1885			Boolean loopBackAllowed)
1886{
1887	int error;
1888
1889	ctx->ct_flags &= ~SMBCF_RESOLVED;
1890	/* We always try Bonjour first and use the port if gave us. */
1891	error = smb_resolve_bonjour_name(ctx, outAddressArray, loopBackAllowed);
1892	/* We are done if Bonjour resolved it otherwise try the other methods. */
1893	if (ctx->ct_flags & SMBCF_RESOLVED)
1894		goto WeAreDone;
1895
1896	/* We default to trying NetBIOS next unless they request us to use some other port */
1897	if ((ctx->prefs.tcp_port == NBSS_TCP_PORT_139) || ctx->prefs.tryBothPorts)
1898		error = smb_resolve_netbios_name(ctx, outAddressArray, loopBackAllowed);
1899	/* We found it with NetBIOS we are done. */
1900	if (ctx->ct_flags & SMBCF_RESOLVED)
1901		goto WeAreDone;
1902
1903	/* Finally try DNS */
1904	error = smb_resolve_dns_name(ctx, outAddressArray, loopBackAllowed);
1905
1906WeAreDone:
1907	if (error) {
1908		ctx->ct_flags &= ~SMBCF_RESOLVED;
1909		smb_log_info("Couldn't resolve %s", ASL_LEVEL_DEBUG, ctx->serverName);
1910	}
1911	return error;
1912}
1913
1914/*
1915 * First we resolve the name, once we have it resolved then we are done.
1916 */
1917static int
1918smb_connect_one(struct smb_ctx *ctx, int forceNewSession, Boolean loopBackAllowed)
1919{
1920	CFMutableArrayRef addressArray = NULL;
1921	struct connectAddress *conn  = NULL;
1922	struct sockaddr *laddr = NULL;
1923	int error = 0;
1924	int btmmAddress = isBTMMAddress(ctx->serverNameRef);
1925
1926	/* We are already connected nothing to do here */
1927	if (ctx->ct_flags & SMBCF_CONNECTED)
1928		return 0;
1929
1930	/* We already found an address use that one */
1931	if ((ctx->ct_flags & SMBCF_RESOLVED) && ctx->ct_saddr)
1932		 goto do_negotiate;
1933
1934	error = smb_resolve(ctx, &addressArray, loopBackAllowed);
1935	if (error) {
1936		if (btmmAddress) {
1937			LogToMessageTracer(SMB_BTMM_DOMAIN, "failure to resolve address",
1938							   "failure", NULL, "smb_resolve failed %d",
1939							   error);
1940		}
1941		return error;
1942	}
1943	/* We have a list of address, if only one address then use it */
1944	 if (CFArrayGetCount(addressArray) == 1) {
1945		 CFMutableDataRef addressData = (CFMutableDataRef)CFArrayGetValueAtIndex(addressArray, 0);
1946		 if (addressData)
1947			 conn = (struct connectAddress *)((void *)CFDataGetMutableBytePtr(addressData));
1948		if (!conn)
1949			error = ENOMEM; /* Should never happen but just in case */
1950	 } else {
1951		 /* Search all the address and see if we are already connected */
1952		 if ((!forceNewSession) && findMatchingVC(ctx, addressArray) == 0)
1953			 goto done;
1954		 /*
1955		  * So we have a list of address, try connecting to all of them async. The
1956		  * first to come back is the one we will use
1957		  */
1958		 error = findReachableAddress(addressArray, &ctx->ct_cancel, &conn);
1959	 }
1960	if (error) {
1961		if (btmmAddress) {
1962			LogToMessageTracer(SMB_BTMM_DOMAIN, "failure to connect to resolved address(es)",
1963						   "failure", NULL, "findReachableAddress failed %d", error);
1964		}
1965		ctx->ct_flags &= ~SMBCF_RESOLVED;
1966		goto done;
1967	}
1968
1969	/* Free the old sockaddr if we have one */
1970	if (ctx->ct_saddr)
1971		free(ctx->ct_saddr);
1972	ctx->ct_saddr = NULL;
1973
1974	/* We found it with DNS, and we are using port 139, we need a NetBIOS socket_addr */
1975	if ((conn->addr.sa_family == AF_INET) && (conn->in4.sin_port == htons(NBSS_TCP_PORT_139))) {
1976		char *netbios_name = (char *)NetBIOS_SMBSERVER;
1977
1978		/* We need a NetBIOS name, if we don't find one use *SMBSERVER. */
1979		if (smb_ctx_getnbname(ctx, &conn->addr) == 0)
1980			netbios_name = ctx->ct_ssn.ioc_srvname; /* Found the NetBIOS name */
1981		nb_sockaddr(&conn->addr, netbios_name, NBT_SERVER, &ctx->ct_saddr);
1982	} else {
1983		/* We already have the sockaddr we will be using for the connection */
1984		ctx->ct_saddr = malloc(conn->addr.sa_len);
1985		/* If smb_negotiate will error out, so deal with it at that time */
1986		if (ctx->ct_saddr)
1987			memcpy(ctx->ct_saddr, &conn->addr, conn->addr.sa_len);
1988	}
1989
1990do_negotiate:
1991	/* We need a local address if connecting with NetBIOS */
1992	if (ctx->ct_saddr && (ctx->ct_saddr->sa_family == AF_NETBIOS))
1993		nb_sockaddr(NULL, ctx->ct_ssn.ioc_localname, NBT_WKSTA, &laddr);
1994
1995	error = smb_negotiate(ctx, ctx->ct_saddr, laddr, forceNewSession);
1996	if (error) {
1997		if (btmmAddress) {
1998			LogToMessageTracer(SMB_BTMM_DOMAIN, "failure to connect to resolved address",
1999						   "failure", NULL, "smb_negotiate failed %d", error);
2000		}
2001		goto done;
2002	}
2003
2004done:
2005	if (error == 0)
2006		ctx->ct_flags |= SMBCF_CONNECTED;
2007
2008	if (laddr)
2009		free(laddr);
2010	if (addressArray)
2011		CFRelease(addressArray);
2012	return error;
2013}
2014
2015/*
2016 * In the future I would like to move the open directory code to its own file,
2017 * but since this is going into a software update lets leave it here for now.
2018 */
2019
2020#include <OpenDirectory/OpenDirectory.h>
2021#include <CoreFoundation/CoreFoundation.h>
2022#include <opendirectory/adtypes.h>
2023#include <SystemConfiguration/SystemConfiguration.h>
2024
2025/*
2026 * Make the call to the AD Plugin to get a list of the domain controllers, if any.
2027 */
2028CF_RETURNS_RETAINED
2029static CFArrayRef
2030smb_dc_lookup(ODNodeRef nodeRef, CFStringRef lookupName)
2031{
2032    CFDataRef name = CFStringCreateExternalRepresentation(kCFAllocatorDefault, lookupName, kCFStringEncodingUTF8, 0);
2033    CFArrayRef result = NULL;
2034    CFDataRef response;
2035
2036	if (!name) {
2037		return NULL;
2038	}
2039
2040	response = ODNodeCustomCall(nodeRef, eODCustomCallActiveDirectoryDCsForName, name, NULL);
2041    if (response) {
2042        CFPropertyListRef plist = CFPropertyListCreateWithData(kCFAllocatorDefault, response, kCFPropertyListImmutable, NULL, NULL);
2043        if (plist != NULL) {
2044            if (CFArrayGetTypeID() == CFGetTypeID(plist)) {
2045                result = CFRetain(plist);
2046            }
2047            CFRelease(plist);
2048        }
2049        CFRelease(response);
2050    }
2051
2052	CFRelease(name);
2053    return result;
2054}
2055
2056/*
2057 * The server name could be an AD Domain Name, in that case we really need to
2058 * connect to one of domain controllers. The old code would just use DNS, but
2059 * not all AD enviroments are configure to have the AD Domain Name resolve to
2060 * the domain controllers. So now lets ask the AD plugin for help. If this is
2061 * an AD Domain Name that we are bound to then the AD plugin will return a list
2062 * of domain controllers for that domain. If not bound or if this name is not
2063 * part of the AD domain then the plugin will return NULL.
2064 */
2065CF_RETURNS_RETAINED
2066CFArrayRef
2067smb_resolve_domain(CFStringRef serverNameRef)
2068{
2069    CFArrayRef result = NULL;
2070	ODNodeRef nodeRef = NULL;
2071	SCDynamicStoreRef store = NULL;
2072	CFDictionaryRef dict = NULL;
2073	CFStringRef nodename = NULL;
2074
2075	/* Just to be safe should never happen */
2076	if (!serverNameRef) {
2077        smb_log_info("%s: No server name", ASL_LEVEL_DEBUG, __FUNCTION__);
2078		return NULL;
2079	}
2080
2081	store = SCDynamicStoreCreate(kCFAllocatorDefault, NULL, NULL, NULL);
2082	if (!store) {
2083        smb_log_info("%s: SCDynamicStoreCreate failed", ASL_LEVEL_DEBUG,
2084                     __FUNCTION__);
2085		return NULL;
2086	}
2087
2088	/* If SCDynamicStoreCopyValue returns null then we are not bound to AD */
2089	dict = SCDynamicStoreCopyValue(store,
2090                                   CFSTR("com.apple.opendirectoryd.ActiveDirectory"));
2091	if (dict != NULL) {
2092		nodename = CFDictionaryGetValue(dict, CFSTR("NodeName"));
2093		if (!nodename) {
2094			smb_log_info("%s: Failed to obtain the AD node?", ASL_LEVEL_DEBUG,
2095                         __FUNCTION__);
2096		}
2097	}
2098    else {
2099		smb_log_info("%s: We are not bound to AD", ASL_LEVEL_DEBUG,
2100                     __FUNCTION__);
2101	}
2102
2103	if (nodename) {
2104		/* Open the the AD Plugin Node */
2105		nodeRef = ODNodeCreateWithName(kCFAllocatorDefault, kODSessionDefault,
2106                                       nodename, NULL);
2107		if (nodeRef) {
2108			/* attempt to get the list of domain controllers */
2109			result = smb_dc_lookup(nodeRef, serverNameRef);
2110            if (!result) {
2111                smb_log_info("%s: smb_dc_lookup failed", ASL_LEVEL_DEBUG,
2112                             __FUNCTION__);
2113            }
2114
2115		}
2116	}
2117
2118	if (nodeRef) {
2119		CFRelease(nodeRef);
2120	}
2121
2122	if (dict) {
2123		CFRelease(dict);
2124	}
2125
2126	if (store) {
2127		CFRelease(store);
2128	}
2129
2130	return result;
2131}
2132
2133/*
2134 * Helper routine that will set the server name field to the domain controller
2135 * name, but doesn't replace ctx->serverNameRef. The ctx->serverNameRef is the
2136 * name the user typed in and should never be replaced.
2137 */
2138static int
2139smb_set_server_name_to_dc(struct smb_ctx *ctx, CFStringRef dcNameRef)
2140{
2141	CFIndex maxlen;
2142
2143	maxlen = CFStringGetMaximumSizeForEncoding(CFStringGetLength(dcNameRef), kCFStringEncodingUTF8) + 1;
2144	if (ctx->serverName)
2145		free(ctx->serverName);
2146	ctx->serverName = malloc(maxlen);
2147	if (!ctx->serverName) {
2148		return ENOMEM;
2149	}
2150	CFStringGetCString(dcNameRef, ctx->serverName, maxlen, kCFStringEncodingUTF8);
2151	smb_log_info("%s: Setting serverName to %s", ASL_LEVEL_DEBUG, __FUNCTION__, ctx->serverName);
2152	return 0;
2153}
2154
2155static int
2156smb_connect(struct smb_ctx *ctx, int forceNewSession, Boolean loopBackAllowed)
2157{
2158	int error = EHOSTUNREACH;
2159	CFArrayRef dcArrayRef = smb_resolve_domain(ctx->serverNameRef);
2160	CFIndex numItems = (dcArrayRef) ? CFArrayGetCount(dcArrayRef) : 0;
2161
2162	ctx->serverIsDomainController = FALSE;
2163
2164	/*
2165	 * Did we get a list of domain controllers, then attempt to connect to one
2166	 * of them, otherwise just use the server name passed in.
2167	 */
2168	if (dcArrayRef && numItems) {
2169		CFIndex ii;
2170		char *hold_serverName = ctx->serverName;
2171
2172		smb_log_info("%s: AD return %ld domain controllers.", ASL_LEVEL_DEBUG, __FUNCTION__, numItems);
2173		ctx->serverName = NULL;
2174
2175		for (ii = 0; ii < numItems && error; ii++) {
2176			CFStringRef dc = CFArrayGetValueAtIndex(dcArrayRef, ii);
2177
2178			if (dc) {
2179				error = smb_set_server_name_to_dc(ctx, dc);
2180				if (!error) {
2181					ctx->ct_flags &= ~(SMBCF_CONNECTED | SMBCF_RESOLVED);
2182					error = smb_connect_one(ctx, forceNewSession, loopBackAllowed);
2183					smb_log_info("%s: Connecting to domain controller %s %s",
2184								 ASL_LEVEL_DEBUG, __FUNCTION__, ctx->serverName,
2185								 (error) ? "FAILED" : "SUCCEED");
2186				}
2187			} else {
2188				smb_log_info("%s: We have a NULL domain controller entry?", ASL_LEVEL_ERR, __FUNCTION__);
2189				error = EHOSTUNREACH;
2190			}
2191		}
2192
2193		if (error) {
2194            /* Could not connect to any of the domain controllers */
2195			if (ctx->serverName) {
2196				free(ctx->serverName);
2197			}
2198
2199            /* Fall back to server name that was passed in */
2200			ctx->serverName = hold_serverName;
2201
2202			ctx->ct_flags &= ~(SMBCF_CONNECTED | SMBCF_RESOLVED);
2203			error = smb_connect_one(ctx, forceNewSession, loopBackAllowed);
2204			smb_log_info("%s: Connecting to all the domain controller failed! Connecting to %s %s",
2205						 ASL_LEVEL_DEBUG, __FUNCTION__, ctx->serverName,
2206						 (error) ? "FAILED" : "SUCCEED");
2207		}
2208        else {
2209			ctx->serverIsDomainController = TRUE;
2210			free(hold_serverName);
2211		}
2212	}
2213    else {
2214        /*
2215         * No list of domain controllers, just try to connect using the name
2216         * passed in.
2217         */
2218		error = smb_connect_one(ctx, forceNewSession, loopBackAllowed);
2219	}
2220
2221	if (dcArrayRef) {
2222		CFRelease(dcArrayRef);
2223	}
2224
2225	return error;
2226}
2227
2228/*
2229 * Common code used by both  smb_get_server_info and smb_open_session.
2230 */
2231static void smb_get_sessioninfo(struct smb_ctx *ctx, CFMutableDictionaryRef mutableDict, const char * func)
2232{
2233	if (ctx->ct_vc_flags & (SMBV_GUEST_ACCESS | SMBV_SFS_ACCESS)) {
2234		CFDictionarySetValue (mutableDict, kNetFSMountedByGuestKey, kCFBooleanTrue);
2235		smb_log_info("%s: Session shared as Guest", ASL_LEVEL_DEBUG, func);
2236	} else {
2237		CFStringRef userNameRef = NULL;
2238
2239		/* Authenticated mount, set the key */
2240		CFDictionarySetValue (mutableDict, kNetFSMountedWithAuthenticationInfoKey, kCFBooleanTrue);
2241		if (ctx->ct_setup.ioc_user[0]) {
2242			userNameRef = CFStringCreateWithCString (NULL, ctx->ct_setup.ioc_user, kCFStringEncodingUTF8);
2243			smb_log_info("%s: Session shared as %s", ASL_LEVEL_DEBUG, func, ctx->ct_setup.ioc_user);
2244		}
2245
2246		if (userNameRef) {
2247			CFDictionarySetValue (mutableDict, kNetFSMountedByUserKey, userNameRef);
2248			CFRelease (userNameRef);
2249		}
2250	}
2251	/* if the server is OSX server, get the model string if VC has it */
2252	smb_get_vc_properties(ctx);
2253	if (ctx->model_info) {
2254		CFStringRef modelInfoRef = NULL;
2255		modelInfoRef = CFStringCreateWithCString(NULL, ctx->model_info, kCFStringEncodingUTF8);
2256		if (modelInfoRef) {
2257			smb_log_info("%s: kNetFSMachineTypeKey model_info = %s",
2258						 ASL_LEVEL_DEBUG,__FUNCTION__, ctx->model_info);
2259			CFDictionarySetValue(mutableDict, kNetFSMachineTypeKey, modelInfoRef);
2260			CFRelease(modelInfoRef);
2261		}
2262		else {
2263			smb_log_info("%s: CFStringCreateWithCString() failed for model_info = %s",
2264						 ASL_LEVEL_DEBUG,__FUNCTION__, ctx->model_info);
2265		}
2266	}
2267
2268}
2269
2270/*
2271 * smb_get_server_info
2272 *
2273 * Every call to this routine will force a new connection to happen. So if this
2274 * session already has a connection that connection will be broken and a new
2275 * connection will be start.
2276 */
2277int smb_get_server_info(struct smb_ctx *ctx, CFURLRef url, CFDictionaryRef OpenOptions, CFDictionaryRef *ServerParams)
2278{
2279	int  error = 0;
2280	CFMutableDictionaryRef mutableDict = NULL;
2281	Boolean loopBackAllowed = SMBGetDictBooleanValue(OpenOptions, kNetFSAllowLoopbackKey, FALSE);
2282	Boolean noUserPrefs = SMBGetDictBooleanValue(OpenOptions, kNetFSNoUserPreferencesKey, FALSE);
2283
2284	*ServerParams = NULL;
2285
2286	/*
2287	 * We are already connected, if this is the same URL then just use the previous
2288	 * connection. If its a different URL force a new lookup and connection.
2289	 * NOTE: NetAuthAgent will call us twice sometimes with the same URL. Save
2290	 * the performace hit and just reuse the connection. Also isUrlStringEqual
2291	 * deals with the case of null URLs
2292	 */
2293	if ((ctx->ct_flags & SMBCF_CONNECTED) && (! isUrlStringEqual(ctx->ct_url, url))) {
2294		ctx->ct_flags &= ~(SMBCF_CONNECTED | SMBCF_RESOLVED);
2295	}
2296	if (url) {
2297		/* Now deal with the URL */
2298		if (ctx->ct_url)
2299			CFRelease(ctx->ct_url);
2300		ctx->ct_url =  CFURLCopyAbsoluteURL(url);
2301	}
2302	if (ctx->ct_url) {
2303		error = ParseSMBURL(ctx);
2304	} else {
2305		error = ENOMEM;
2306	}
2307	if (error)
2308		return error;
2309
2310	/* Tell the kernel that we shouldn't touch the home directory, ever on this VC */
2311	if (noUserPrefs) {
2312		ctx->ct_setup.ioc_userflags &= ~SMBV_HOME_ACCESS_OK;
2313	}
2314
2315	/* Only read the preference files once */
2316	if ((ctx->ct_flags & SMBCF_READ_PREFS) != SMBCF_READ_PREFS) {
2317		char *shareName = (ctx->ct_sh.ioc_share[0]) ? ctx->ct_sh.ioc_share : NULL;
2318		readPreferences(&ctx->prefs, ctx->serverName, shareName,  noUserPrefs, FALSE);
2319	}
2320	error = smb_connect(ctx, FALSE, loopBackAllowed);
2321	if (error)
2322		return error;
2323	/* Now return what we know about the server */
2324	mutableDict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2325	if (mutableDict == NULL) {
2326		smb_log_info("%s: CFDictionaryCreateMutable failed, syserr = %s",
2327					 ASL_LEVEL_ERR, __FUNCTION__, strerror(errno));
2328		return errno;
2329	}
2330	/* Handle the case we know about here for sure */
2331	/* All modern servers support change password, but the client doesn't so for now the answer is no! */
2332	CFDictionarySetValue(mutableDict, kNetFSSupportsChangePasswordKey, kCFBooleanFalse);
2333	/* Most servers support guest, but not sure how we can tell if it is turned on yet */
2334	CFDictionarySetValue(mutableDict, kNetFSSupportsGuestKey, kCFBooleanTrue);
2335	CFDictionarySetValue(mutableDict, kNetFSGuestOnlyKey, kCFBooleanFalse);
2336	/* We have a Mech Dictionary add it to the info */
2337	if (ctx->mechDict) {
2338		CFDictionarySetValue(mutableDict, kNetFSMechTypesSupportedKey, ctx->mechDict);
2339	}
2340
2341	/*
2342	 * Need to return the server display name. We always have serverNameRef,
2343	 * unless we ran out of memory.
2344	 *
2345	 * %%% In the future we should handle the case of not enough memory when
2346	 * creating the serverNameRef. Until then just fallback to the server name that
2347	 * came from the URL.
2348	 */
2349	if (ctx->serverNameRef) {
2350		CFDictionarySetValue(mutableDict, kNetFSServerDisplayNameKey, ctx->serverNameRef);
2351	} else if (ctx->serverName != NULL) {
2352		CFStringRef Server = CFStringCreateWithCString(NULL, ctx->serverName, kCFStringEncodingUTF8);
2353		if (Server != NULL) {
2354			CFDictionarySetValue(mutableDict, kNetFSServerDisplayNameKey, Server);
2355			CFRelease (Server);
2356		}
2357	}
2358	smb_get_os_lanman(ctx, mutableDict);
2359
2360	if (ctx->ct_vc_shared)
2361		smb_get_sessioninfo(ctx, mutableDict, __FUNCTION__);
2362
2363	*ServerParams = mutableDict;
2364	return error;
2365}
2366
2367int smb_open_session(struct smb_ctx *ctx, CFURLRef url, CFDictionaryRef OpenOptions, CFDictionaryRef *sessionInfo)
2368{
2369	int  error = 0;
2370	Boolean loopBackAllowed = SMBGetDictBooleanValue(OpenOptions, kNetFSAllowLoopbackKey, FALSE);
2371	Boolean forceNewSession = SMBGetDictBooleanValue(OpenOptions, kNetFSForceNewSessionKey, FALSE);
2372	Boolean	UseAuthentication = SMBGetDictBooleanValue(OpenOptions, kNetFSUseAuthenticationInfoKey, FALSE);
2373	Boolean	UseGuest = SMBGetDictBooleanValue(OpenOptions, kNetFSUseGuestKey, FALSE);
2374	Boolean	UseAnonymous = SMBGetDictBooleanValue(OpenOptions, kNetFSUseAnonymousKey, FALSE);
2375	Boolean	ChangePassword = SMBGetDictBooleanValue(OpenOptions, kNetFSChangePasswordKey, FALSE);
2376	Boolean noUserPrefs = SMBGetDictBooleanValue(OpenOptions, kNetFSNoUserPreferencesKey, FALSE);
2377	CFDictionaryRef authInfoDict = NULL;
2378	char *tmscheme = GetTraceMessageScheme(OpenOptions);
2379
2380	/* Remove any previously auth request flags */
2381	ctx->ct_setup.ioc_userflags &= ~(SMBV_KERBEROS_ACCESS | SMBV_ANONYMOUS_ACCESS |
2382									 SMBV_PRIV_GUEST_ACCESS | SMBV_GUEST_ACCESS);
2383
2384	/* They are trying to mix security options, not allowed */
2385	if ((UseAuthentication && UseGuest) || (UseAuthentication && UseAnonymous) ||
2386		(UseGuest && UseAnonymous)) {
2387		error = EINVAL;
2388		NetFSLogToMessageTracer(tmscheme, "mixing authentication options in SMB_OpenSession", error);
2389		goto done;
2390	}
2391	/* We currently do not support change password maybe someday? */
2392	if (ChangePassword) {
2393		error = ENOTSUP;
2394		NetFSLogToMessageTracer(tmscheme, "change password in SMB_OpenSession", error);
2395		goto done;
2396	}
2397	/* Tell the kernel that we shouldn't touch the home directory, ever on this VC */
2398	if (noUserPrefs) {
2399		ctx->ct_setup.ioc_userflags &= ~SMBV_HOME_ACCESS_OK;
2400
2401        /*
2402         * Kerberos is not allowed to access home dir either. autofs and
2403         * home dir mounting *should* be setting the noUserPrefs flags. No
2404         * access to mount flags which would tell us if its automounter, so
2405         * have to rely upon this flag to be set correctly.
2406         */
2407        krb5_set_home_dir_access(NULL, false);
2408	}
2409
2410	/*  If they pass a URL then use it otherwise use the one we already have */
2411	if (url) {
2412		if (ctx->ct_url)
2413			CFRelease(ctx->ct_url);
2414		ctx->ct_url =  CFURLCopyAbsoluteURL(url);
2415	}
2416	/* Remember that parsing the url can set the SMBV_GUEST_ACCESS */
2417	if (ctx->ct_url) {
2418		error = ParseSMBURL(ctx);
2419	} else {
2420		error = ENOMEM;
2421		NetFSLogToMessageTracer(tmscheme, "checking url in SMB_OpenSession", error);
2422		goto done;
2423	}
2424
2425	/*
2426	 * Check to see what authentication method they wish for us to use in the
2427	 * connection process. Remove any other values that may have been set when
2428	 * we parsed the URL.
2429	 */
2430	if (UseAuthentication) {
2431		authInfoDict = CFDictionaryGetValue(OpenOptions, kNetFSAuthenticationInfoKey);
2432
2433		if (authInfoDict) {
2434			CFStringRef mechanismRef = CFDictionaryGetValue(authInfoDict, kNAHMechanism);
2435
2436			/* Parsing the URL could have set the guest flag remove it */
2437			ctx->ct_setup.ioc_userflags &= ~SMBV_GUEST_ACCESS;
2438
2439			/* Used for debugging in the future want to remove */
2440			if (mechanismRef) {
2441				char mechanismStr[256];
2442				CFStringGetCString(mechanismRef, mechanismStr, sizeof(mechanismStr), kCFStringEncodingUTF8);
2443				smb_log_info("%s: Connecting to %s Authentication Info kNAHMechanism %s",
2444							 ASL_LEVEL_DEBUG, __FUNCTION__, ctx->serverName,
2445							 mechanismStr);
2446			} else {
2447				smb_log_info("%s: Connecting to %s using authentication Info, but has no kNAHMechanism",
2448							 ASL_LEVEL_DEBUG, __FUNCTION__, ctx->serverName);
2449			}
2450
2451			/* Should we look for other Kerberos Mech here, do we need this anymore? */
2452			if (mechanismRef && (CFStringCompare(mechanismRef,  kGSSAPIMechKerberos, 0) == kCFCompareEqualTo)) {
2453				ctx->ct_setup.ioc_userflags |= SMBV_KERBEROS_ACCESS;
2454			} else if (ctx->prefs.minAuthAllowed == SMB_MINAUTH_KERBEROS) {
2455				/* They don't want to use Kerberos, but the min auth level requires it */
2456				smb_log_info("%s: Kerberos required!", ASL_LEVEL_ERR, __FUNCTION__);
2457				error = ENETFSNOAUTHMECHSUPP;
2458				NetFSLogToMessageTracer(tmscheme, "Kerberos required in SMB_OpenSession", error);
2459				goto done;
2460			}
2461		} else {
2462			smb_log_info("%s: Connecting to %s using authentication, but has no Authentication Info",
2463						 ASL_LEVEL_DEBUG, __FUNCTION__, ctx->serverName);
2464		}
2465	} else if (UseAnonymous) {
2466		/*
2467		 * If the URL contian the username guest then remove it because they
2468		 * told us to use anonymous.
2469		 */
2470		ctx->ct_setup.ioc_userflags &= ~SMBV_GUEST_ACCESS;
2471		ctx->ct_setup.ioc_userflags |= SMBV_ANONYMOUS_ACCESS;
2472		smb_ctx_setuser(ctx, "");
2473		smb_ctx_setpassword(ctx, "", FALSE);
2474		smb_ctx_setdomain(ctx, "");
2475		smb_log_info("%s: Connecting to %s using anonymous", ASL_LEVEL_DEBUG,
2476					 __FUNCTION__, ctx->serverName);
2477	} else if (UseGuest) {
2478		ctx->ct_setup.ioc_userflags |= SMBV_GUEST_ACCESS;
2479		smb_ctx_setuser(ctx, kGuestAccountName);
2480		smb_ctx_setpassword(ctx, kGuestPassword, FALSE);
2481		smb_log_info("%s: Connecting to %s using guest", ASL_LEVEL_DEBUG,
2482					 __FUNCTION__, ctx->serverName);
2483	} else if (ctx->ct_setup.ioc_userflags & SMBV_GUEST_ACCESS) {
2484		/* Must have been set from the URL, mark it as private */
2485		ctx->ct_setup.ioc_userflags |= SMBV_PRIV_GUEST_ACCESS;
2486		smb_ctx_setpassword(ctx, kGuestPassword, TRUE);
2487		smb_log_info("%s: Connecting to %s using URL guest", ASL_LEVEL_DEBUG,
2488					 __FUNCTION__, ctx->serverName);
2489	}
2490
2491	/*
2492	 * Force a new session, seems automount always sets it. Doesn't make any
2493	 * difference because we never have a connection in the automounter case.
2494	 * XXX - See <rdar://problem/7633097>
2495	 */
2496	if ((ctx->ct_vc_shared) && forceNewSession) {
2497		ctx->ct_flags &= ~SMBCF_CONNECTED;
2498	} else {
2499		/* We should remove this once <rdar://problem/7633097> is completed */
2500		forceNewSession = FALSE;
2501	}
2502
2503
2504	/* We haven't connect yet or we need to start over in either case read the preference again and do the connect */
2505	if ((ctx->ct_flags & SMBCF_CONNECTED) != SMBCF_CONNECTED) {
2506	    	/* Only read the preference files once */
2507		if ((ctx->ct_flags & SMBCF_READ_PREFS) != SMBCF_READ_PREFS) {
2508			char *shareName = (ctx->ct_sh.ioc_share[0]) ? ctx->ct_sh.ioc_share : NULL;
2509			readPreferences(&ctx->prefs, ctx->serverName, shareName, noUserPrefs, FALSE);
2510		}
2511		error = smb_connect(ctx, forceNewSession, loopBackAllowed);
2512		if (error) {
2513			NetFSLogToMessageTracer(tmscheme, "connection in SMB_OpenSession", error);
2514			goto done;
2515		}
2516	}
2517
2518	/* If we are not sharing the session we need to authenticate */
2519	if (!ctx->ct_vc_shared) {
2520		error = smb_session_security(ctx, authInfoDict);
2521	}
2522
2523	/*
2524	 * The connection went down for some reason. Attempt to connect again
2525	 * and retry the authentication.
2526	 */
2527	if (DISCONNECT_ERROR(error)) {
2528		smb_log_info("%s: The connection to %s went down retry authentication",
2529					 ASL_LEVEL_DEBUG, __FUNCTION__, ctx->serverName);
2530
2531		ctx->ct_flags &= ~SMBCF_CONNECTED;
2532		error = smb_connect(ctx, TRUE, loopBackAllowed);
2533		if (!error) {
2534			error = smb_session_security(ctx, authInfoDict);
2535		}
2536		if (DISCONNECT_ERROR(error)) {
2537			/* Connection failed again, mark that we are not connect */
2538			ctx->ct_flags &= ~SMBCF_CONNECTED;
2539		}
2540	}
2541	/*
2542	 * We failed, so clear out any local security settings. We need to make
2543	 * sure we do not reuse these values in the next open session. If these
2544	 * values were set by the URL then will get reset on the next open call.
2545	 */
2546	if (error) {
2547		smb_ctx_setuser(ctx, "");
2548		smb_ctx_setpassword(ctx, "", FALSE);
2549		smb_ctx_setdomain(ctx, "");
2550		NetFSLogToMessageTracer(tmscheme, "authentication in SMB_OpenSession", error);
2551	}
2552
2553	if ((error == 0) && sessionInfo) {
2554		/* create and return session info dictionary */
2555		CFMutableDictionaryRef mutableDict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
2556												&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2557        if (mutableDict) {
2558            smb_get_sessioninfo(ctx, mutableDict, __FUNCTION__);
2559        }
2560        else {
2561            smb_log_info("%s: CFDictionaryCreateMutable() failed",  ASL_LEVEL_DEBUG, __FUNCTION__);
2562        }
2563        *sessionInfo = mutableDict;
2564	}
2565
2566done:
2567	if (tmscheme) {
2568		free(tmscheme);
2569	}
2570	return error;
2571}
2572
2573int smb_mount(struct smb_ctx *ctx, CFStringRef mpoint,  CFDictionaryRef mOptions, CFDictionaryRef *mInfo,
2574			  void (*callout)(void  *, void *), void *args)
2575{
2576	CFMutableDictionaryRef mdict = NULL;
2577	struct UniqueSMBShareID req;
2578	Boolean ForceNewSession = SMBGetDictBooleanValue(mOptions, kNetFSForceNewSessionKey, FALSE);
2579	struct smb_mount_args mdata;
2580	int error = 0;
2581	char mount_point[MAXPATHLEN];
2582	struct stat st;
2583	int mntflags;
2584	CFNumberRef numRef;
2585	struct smb_ctx *dfs_ctx = ctx;
2586	char *tmscheme = GetTraceMessageScheme(mOptions);
2587
2588	/*
2589	 * Need to be connected before we can mount the volume.
2590	 */
2591	if ((ctx->ct_flags & SMBCF_CONNECTED) != SMBCF_CONNECTED) {
2592		error = ENOTCONN;
2593		smb_log_info("%s: syserr = %s",  ASL_LEVEL_DEBUG, __FUNCTION__, strerror(error));
2594		NetFSLogToMessageTracer(tmscheme, "connection in SMB_Mount", error);
2595		goto done;
2596	}
2597	/*  Initialize the mount arguments  */
2598	mdata.version = SMB_IOC_STRUCT_VERSION;
2599	mdata.altflags = ctx->prefs.altflags; /* Contains flags that were read from preference */
2600	mdata.path_len = 0;
2601	mdata.path[0] = 0;
2602	mdata.volume_name[0] = 0;
2603
2604	/* Get the alternative mount flags from the mount options */
2605	if (SMBGetDictBooleanValue(mOptions, kNetFSSoftMountKey, FALSE))
2606		mdata.altflags |= SMBFS_MNT_SOFT;
2607	if (SMBGetDictBooleanValue(mOptions, kNotifyOffMountKey, FALSE))
2608		mdata.altflags |= SMBFS_MNT_NOTIFY_OFF;
2609	/* Only want the value if it exist */
2610	if (SMBGetDictBooleanValue(mOptions, kStreamstMountKey, FALSE))
2611		mdata.altflags |= SMBFS_MNT_STREAMS_ON;
2612	else if (! SMBGetDictBooleanValue(mOptions, kStreamstMountKey, TRUE))
2613		mdata.altflags &= ~SMBFS_MNT_STREAMS_ON;
2614
2615	if (SMBGetDictBooleanValue(mOptions, kTimeMachineMountKey, FALSE)) {
2616		mdata.altflags |= SMBFS_MNT_TIME_MACHINE;
2617	}
2618
2619	/* Get the mount flags, just in case there are no flags or something is wrong we start with them set to zero. */
2620	mntflags = 0;
2621	if (mOptions) {
2622		numRef = (CFNumberRef)CFDictionaryGetValue (mOptions, kNetFSMountFlagsKey);
2623		if (numRef)
2624			(void)CFNumberGetValue(numRef, kCFNumberSInt32Type, &mntflags);
2625
2626	}
2627
2628	if (mntflags & MNT_AUTOMOUNTED) {
2629        /* Kerberos is not allowed to access home dir for automounts */
2630        krb5_set_home_dir_access(NULL, false);
2631    }
2632
2633    /* Create the dictionary used to return mount information in. */
2634	mdict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2635	if (!mdict) {
2636		error = ENOMEM;
2637		smb_log_info("%s: allocation of dictionary failed, syserr = %s",
2638					 ASL_LEVEL_DEBUG, __FUNCTION__, strerror(error));
2639		NetFSLogToMessageTracer(tmscheme, "mount information dictionary in SMB_Mount", error);
2640		goto done;
2641	}
2642
2643    /*
2644     * The old code would do a tree connect here, we now do that in checkForDfsReferral
2645     * since the tree connect can fail because we are going to a domain instead
2646     * of a server.
2647     */
2648	error = checkForDfsReferral(ctx, &dfs_ctx, tmscheme, NULL);
2649    if (error) {
2650        /* Connection went down, make sure we return the correct error */
2651  		if ((error == ENOTCONN) || (smb_ctx_connstate(ctx) == ENOTCONN)) {
2652			ctx->ct_flags &= ~SMBCF_CONNECT_STATE;
2653			error =  EPIPE;
2654		}
2655        dfs_ctx = ctx;  /* Clean up code requires this to be set */
2656        goto WeAreDone;
2657    }
2658
2659	if (dfs_ctx == NULL) {
2660        dfs_ctx = ctx;  /* Clean up code requires this to be set */
2661        /*
2662		 * We have a mount path and we are not doing Dfs so see if we should be
2663		 * doing submounts. Remember if the submount key is not set then we
2664		 * do submounts. They have to tell us not to do submounts.
2665		 */
2666		if ((ctx->mountPath) && (!SMBGetDictBooleanValue(mOptions, kNetFSAllowSubMountsKey, TRUE))) {
2667			CFMutableDictionaryRef urlParmsRef = NULL;
2668			CFURLRef urlRef = NULL;
2669			int error2;
2670
2671			/*
2672			 * Not a submount, so remove the path from ctx->ct_url.
2673			 * First, convert URLRef to a dictionary
2674			 */
2675			error2 = smb_url_to_dictionary(ctx->ct_url, (CFDictionaryRef *) &urlParmsRef);
2676			if (error2 || (urlParmsRef == NULL)) {
2677				smb_log_info("Failed parsing URL, syserr = %s", ASL_LEVEL_DEBUG, strerror(error2));
2678				NetFSLogToMessageTracer(tmscheme, "smb_url_to_dictionary in SMB_Mount", error2);
2679				goto WeAreDone;
2680			}
2681
2682			/* Remove kNetFSPathKey from the dictionary */
2683			CFDictionaryRemoveValue(urlParmsRef, kNetFSPathKey);
2684
2685			/* Convert dictionary back into URLRef */
2686			error2 = smb_dictionary_to_url(urlParmsRef, &urlRef);
2687			CFRelease(urlParmsRef);
2688			if (error || (urlRef == NULL)) {
2689				smb_log_info("Failed creating URL, syserr = %s", ASL_LEVEL_DEBUG, strerror(error2));
2690				NetFSLogToMessageTracer(tmscheme, "smb_url_to_dictionary in SMB_Mount", error2);
2691				goto WeAreDone;
2692			}
2693
2694			/* Replace the previous URLRef with the new one */
2695			CFRelease(ctx->ct_url);
2696			ctx->ct_url = urlRef;
2697
2698			/* Turn off submounts */
2699			CFRelease(ctx->mountPath);
2700			ctx->mountPath = NULL;
2701		}
2702	}
2703
2704	/*
2705	 * If they have MOPT_AUTOMOUNTED options set then ignore the fact  that its
2706	 * already mounted. If the ForceNewSession is set then ignore those also,
2707	 * this is the same as AFP. If its is already mounted return EEXIST and the
2708	 * mount information.
2709	 */
2710	if (!ForceNewSession && ((mntflags & MNT_AUTOMOUNTED) == 0)) {
2711		int fs_cnt = 0;
2712		struct statfs *fs = smb_getfsstat(&fs_cnt);	/* Get the list of  mounted volumes */
2713
2714		error = already_mounted(dfs_ctx, dfs_ctx->ct_sh.ioc_share, fs, fs_cnt, mdict, mntflags);
2715		if (fs)	/* Done with free it */
2716			free(fs);
2717		/* It already exist return the mount point */
2718		if (error == EEXIST) /* Only error already_mounted returns */ {
2719			if (mInfo) {
2720				*mInfo = mdict;
2721				mdict = NULL;
2722			}
2723			goto WeAreDone;
2724		}
2725	}
2726
2727	/*
2728	 * We have a mount path make sure it exist and that the last part of the
2729	 * path is a directory.
2730	 */
2731	if (dfs_ctx->mountPath) {
2732		char *newpath = CStringCreateWithCFString(dfs_ctx->mountPath);
2733
2734		if (newpath) {
2735			uint32_t ntError;
2736
2737			error = smb2io_check_directory(dfs_ctx, newpath, 0, &ntError);
2738			if (error)
2739				smb_log_info("%s Check path on %s return ntstatus = 0x%x, syserr = %s",
2740							 ASL_LEVEL_DEBUG, __FUNCTION__, newpath, ntError, strerror(error));
2741
2742			/*
2743			 * NOTE: With SMB we only get the STATUS_NOT_A_DIRECTORY error,
2744			 * when the last component is not a directory. We will get a bad
2745			 * path if there is some other issue with the path. If the last
2746			 * component is not a directory then remove it from the path. We just
2747			 * return any other error to the calling process.
2748			 */
2749			if (error && (ntError == STATUS_NOT_A_DIRECTORY)) {
2750				CFArrayRef userArray = CFStringCreateArrayBySeparatingStrings(NULL, dfs_ctx->mountPath, CFSTR("/"));
2751				CFRelease(dfs_ctx->mountPath);
2752				dfs_ctx->mountPath = NULL;
2753				if (userArray) {
2754					CFIndex cnt = CFArrayGetCount(userArray);
2755
2756					if (cnt > 1) {
2757						CFMutableArrayRef userArrayM = CFArrayCreateMutableCopy(NULL, cnt, userArray);
2758
2759						if (userArrayM) {
2760							CFArrayRemoveValueAtIndex(userArrayM, cnt-1);
2761							dfs_ctx->mountPath = CFStringCreateByCombiningStrings(NULL, userArrayM, CFSTR("/"));
2762							CFRelease(userArrayM);
2763						}
2764					}
2765					CFRelease(userArray);
2766				}
2767				error = 0;
2768			}
2769			free(newpath);
2770		} else {
2771			error = ENOMEM;
2772		}
2773		if (error) {
2774			NetFSLogToMessageTracer(tmscheme, "checking path in SMB_Mount", error);
2775			goto WeAreDone;
2776		}
2777	}
2778
2779	CFStringGetCString(mpoint, mount_point, sizeof(mount_point), kCFStringEncodingUTF8);
2780	if (stat(mount_point, &st) == -1)
2781		 error = errno;
2782	else if (!S_ISDIR(st.st_mode))
2783		 error = ENOTDIR;
2784
2785	 if (error) {
2786		 smb_log_info("%s: bad mount point, syserr = %s", ASL_LEVEL_DEBUG,
2787					  __FUNCTION__, strerror(error));
2788		 NetFSLogToMessageTracer(tmscheme, "checking mount point in SMB_Mount", error);
2789		 goto WeAreDone;
2790	 }
2791
2792	/* now create the unique_id, using tcp address + port + uppercase share */
2793	if (ctx->ct_saddr)
2794		create_unique_id(ctx, ctx->ct_sh.ioc_share, mdata.unique_id, &mdata.unique_id_len);
2795	else
2796		smb_log_info("%s: ioc_saddr is NULL how did that happen?", ASL_LEVEL_DEBUG, __FUNCTION__);
2797	mdata.uid = geteuid();
2798	mdata.gid = getegid();;
2799	/*
2800	 * Really would like a better way of doing this, but until we can get the real file/directory access
2801	 * use this method. The old code base the access on the mount point, we no longer do it that way. If
2802	 * the mount option dictionary has file or directory modes use them otherwise always set it to 0700
2803	 */
2804	if (mOptions)
2805		numRef = (CFNumberRef)CFDictionaryGetValue(mOptions, kdirModeKey);
2806	else
2807		numRef = NULL;
2808
2809	if (numRef && (CFNumberGetValue(numRef, kCFNumberSInt16Type, &mdata.dir_mode)))
2810		mdata.dir_mode &= (S_IRWXU | S_IRWXG | S_IRWXO); /* We were passed in the modes to use */
2811	else if (ctx->ct_setup.ioc_userflags & SMBV_GUEST_ACCESS)
2812		mdata.dir_mode = S_IRWXU | S_IRWXG | S_IRWXO;	/* Guest access open it up */
2813	else
2814		mdata.dir_mode = S_IRWXU;						/* User access limit access to the user that mounted it */
2815
2816	if (mOptions)
2817		numRef = (CFNumberRef)CFDictionaryGetValue(mOptions, kfileModeKey);
2818	else
2819		numRef = NULL;
2820
2821	/* See if we were passed a file mode */
2822	if (numRef && (CFNumberGetValue(numRef, kCFNumberSInt16Type, &mdata.file_mode)))
2823		mdata.file_mode &= (S_IRWXU | S_IRWXG | S_IRWXO); /* We were passed in the modes to use */
2824	else if ((ctx->ct_setup.ioc_userflags & SMBV_GUEST_ACCESS) || (ctx->ct_vc_flags & SMBV_SFS_ACCESS))
2825		mdata.file_mode = S_IRWXU | S_IRWXG | S_IRWXO;	/* Guest access open it up */
2826	else
2827		mdata.file_mode = S_IRWXU;						/* User access limit access to the user that mounted it */
2828
2829	/* Just make sure they didn't do something stupid */
2830	if (mdata.dir_mode & S_IRUSR)
2831		mdata.dir_mode |= S_IXUSR;
2832	if (mdata.dir_mode & S_IRGRP)
2833		mdata.dir_mode |= S_IXGRP;
2834	if (mdata.dir_mode & S_IROTH)
2835		mdata.dir_mode |= S_IXOTH;
2836
2837	mdata.KernelLogLevel = ctx->prefs.KernelLogLevel;
2838    mdata.max_resp_timeout = ctx->prefs.max_resp_timeout;
2839
2840	mdata.dev = dfs_ctx->ct_fd;
2841
2842	CreateSMBFromName(ctx, mdata.url_fromname, MAXPATHLEN);
2843
2844	if (dfs_ctx->mountPath) {
2845		CFStringGetCString(dfs_ctx->mountPath, mdata.path, MAXPATHLEN, kCFStringEncodingUTF8);
2846		mdata.path_len = (uint32_t)strlen(mdata.path);	/* Path length does not include the null byte */
2847	}
2848	/* The URL had a starting path send it to the kernel */
2849	if (ctx->mountPath) {
2850		CFStringRef	volname;
2851		CFArrayRef pathArray;
2852
2853		/* Get the Volume Name from the path.
2854		 *
2855		 * Remember we already removed any extra slashes in the path. So it will
2856		 * be in one of the following formats:
2857		 *		"/"
2858		 *		"path"
2859		 *		"path1/path2/path3"
2860		 *
2861		 * We can have a share that is only a slash, but we can never have a path
2862		 * that is only a slash. Just to be safe we do test for that case. A slash
2863		 * in the path will cause us to have two empty array elements.
2864		 */
2865
2866		pathArray = CFStringCreateArrayBySeparatingStrings(NULL, ctx->mountPath, CFSTR("/"));
2867		if (pathArray) {
2868			CFIndex indexCnt = CFArrayGetCount(pathArray);
2869
2870			/* Make sure we don't have a path with just a slash or only one element */
2871			if ((indexCnt < 1) ||
2872				((indexCnt == 2) && (CFStringGetLength((CFStringRef)CFArrayGetValueAtIndex(pathArray, 0)) == 0)))
2873				volname = ctx->mountPath;
2874			else
2875				volname = (CFStringRef)CFArrayGetValueAtIndex(pathArray, indexCnt -1);
2876		} else
2877			volname = ctx->mountPath;
2878
2879		CFStringGetCString(volname, mdata.volume_name, MAXPATHLEN, kCFStringEncodingUTF8);
2880		if (pathArray)
2881			CFRelease(pathArray);
2882	} else if (ctx->ct_origshare) /* Just to be safe, should never happen */
2883		strlcpy(mdata.volume_name, ctx->ct_origshare, sizeof(mdata.volume_name));
2884
2885	/* We have a callback and they aren't calling us from a callback */
2886	if (callout && !ctx->inCallback) {
2887		ctx->inCallback = TRUE;
2888		callout(dfs_ctx, args);
2889		ctx->inCallback = FALSE;
2890	}
2891	/*
2892	 * Either we found it with Dfs or its a Dfs share, either case tell the
2893	 * kernel this is a Dfs mount
2894	 */
2895	if ((dfs_ctx != ctx) ||
2896		(smb_tree_conn_optional_support_flags(ctx) & SMB_SHARE_IS_IN_DFS)) {
2897	    mdata.altflags |= SMBFS_MNT_DFS_SHARE;
2898	}
2899	smb_log_info("%s: Volume name = %s mntflags = 0x%x altflags = 0x%x",
2900				 ASL_LEVEL_DEBUG, __FUNCTION__, mdata.volume_name,
2901				 mntflags, mdata.altflags);
2902	error = mount(SMBFS_VFSNAME, mount_point, mntflags, (void*)&mdata);
2903	if (error || (mInfo == NULL)) {
2904		if (error == -1)
2905			error = errno; /* Make sure we return a real error number */
2906		NetFSLogToMessageTracer(tmscheme, "mount in SMB_Mount", error);
2907		goto WeAreDone;
2908	}
2909	/* Now  get the mount information. */
2910	bzero(&req, sizeof(req));
2911	bcopy(mdata.unique_id, req.unique_id, sizeof(req.unique_id));
2912	req.unique_id_len = mdata.unique_id_len;
2913	if (get_share_mount_info(mount_point, mdict, &req) == EEXIST) {
2914		*mInfo = mdict;
2915		mdict = NULL;
2916	} else {
2917		smb_log_info("%s: Getting mount information failed !", ASL_LEVEL_ERR, __FUNCTION__);
2918		NetFSLogToMessageTracer(tmscheme, "getting mount information in SMB_Mount", error);
2919	}
2920
2921WeAreDone:
2922	if (error) {
2923		char * log_server = (ctx->serverName) ? ctx->serverName : (char *)"";
2924		char * log_share = (ctx->ct_origshare) ? ctx->ct_origshare : (char *)"";
2925		smb_log_info("%s: mount failed to %s/%s, syserr = %s", ASL_LEVEL_ERR,
2926					 __FUNCTION__, log_server, log_share, strerror(error));
2927		/* If we have an error send a tree disconnect */
2928		if (ctx->ct_flags & SMBCF_SHARE_CONN)
2929			(void)smb_share_disconnect(ctx);
2930	}
2931	if (mdict)
2932		 CFRelease(mdict);
2933	ctx->ct_flags &= ~SMBCF_SHARE_CONN;
2934	if (dfs_ctx != ctx)
2935		smb_ctx_done(dfs_ctx);
2936
2937done:
2938	if (tmscheme) {
2939		free(tmscheme);
2940	}
2941	return error;
2942}
2943
2944CFMutableDictionaryRef
2945CreateAuthDictionary(struct smb_ctx *ctx, uint32_t authFlags,
2946					 const char * clientPrincipal, uint32_t clientNameType)
2947{
2948	uint32_t serverNameType = GSSD_HOSTBASED;
2949	CFStringRef serverPrincipalRef = TargetNameCreateWithHostName(ctx);
2950	CFNumberRef serverNameTypeRef;
2951	CFStringRef clientPrincipalRef;
2952	CFNumberRef clientNameTypeRef;
2953	CFMutableDictionaryRef authInfoDict;
2954
2955	serverNameTypeRef = CFNumberCreate( kCFAllocatorDefault, kCFNumberIntType,
2956									   &serverNameType);
2957	clientPrincipalRef = CFStringCreateWithCString(kCFAllocatorDefault,
2958												   clientPrincipal,
2959												   kCFStringEncodingUTF8);
2960	clientNameTypeRef = CFNumberCreate( kCFAllocatorDefault, kCFNumberIntType,
2961									   &clientNameType);
2962	authInfoDict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
2963											 &kCFTypeDictionaryKeyCallBacks,
2964											 &kCFTypeDictionaryValueCallBacks);
2965
2966	/* Something went wrong bail out */
2967	if (!authInfoDict || !clientPrincipalRef || !serverPrincipalRef ||
2968		!clientNameTypeRef || !serverNameTypeRef) {
2969		if (authInfoDict) {
2970			CFRelease(authInfoDict);
2971		}
2972		authInfoDict = NULL;	/* Make sure we return null */
2973		goto done;
2974	}
2975	/* Set the mechanism type, currently we only know about two */
2976	if (authFlags & SMBV_KERBEROS_ACCESS) {
2977		CFDictionarySetValue(authInfoDict, kNAHMechanism, kGSSAPIMechKerberos);
2978	} else {
2979		CFDictionarySetValue(authInfoDict, kNAHMechanism, kGSSAPIMechNTLM);
2980	}
2981	/* Add the client principal */
2982	CFDictionarySetValue(authInfoDict, kNAHClientPrincipal, clientPrincipalRef);
2983	CFDictionarySetValue (authInfoDict, kNAHClientNameTypeGSSD, clientNameTypeRef);
2984	/* Add the server principal */
2985	CFDictionarySetValue(authInfoDict, kNAHServerPrincipal, serverPrincipalRef);
2986	CFDictionarySetValue (authInfoDict, kNAHServerNameTypeGSSD, serverNameTypeRef);
2987done:
2988	if (clientPrincipalRef) {
2989		CFRelease(clientPrincipalRef);
2990	}
2991	if (serverPrincipalRef) {
2992		CFRelease(serverPrincipalRef);
2993	}
2994	if (clientNameTypeRef) {
2995		CFRelease(clientNameTypeRef);
2996	}
2997	if (serverNameTypeRef) {
2998		CFRelease(serverNameTypeRef);
2999	}
3000	return authInfoDict;
3001}
3002
3003/*
3004 * smb_ctx_clone
3005 *
3006 * Clone any security, and local info we can fron the old ctx
3007 * into the new ctx.
3008 */
3009int smb_ctx_clone(struct smb_ctx *new_ctx, struct smb_ctx *old_ctx,
3010						   CFMutableDictionaryRef openOptions)
3011{
3012	CFMutableDictionaryRef authInfoDict;
3013	const char *clientName, *oldName;
3014	char *newName;
3015	size_t	size;
3016
3017	if (openOptions == NULL) {
3018		return ENOTSUP;
3019	}
3020
3021	new_ctx->ct_setup = old_ctx->ct_setup;
3022
3023	/* Make a copy of any pointer values in case we are sharing this VC. */
3024	new_ctx->ct_setup.ioc_gss_client_name = USER_ADDR_NULL;
3025	new_ctx->ct_setup.ioc_gss_client_size = 0;
3026	new_ctx->ct_setup.ioc_gss_client_nt = GSSD_STRING_NAME;
3027	if (old_ctx->ct_setup.ioc_gss_client_name != USER_ADDR_NULL) {
3028		size = old_ctx->ct_setup.ioc_gss_client_size;
3029		newName = calloc(size, 1);
3030		if (newName) {
3031			oldName = (char *)(uintptr_t)old_ctx->ct_setup.ioc_gss_client_name;
3032			memcpy(newName,  oldName,  size);
3033			new_ctx->ct_setup.ioc_gss_client_name = CAST_USER_ADDR_T(newName);
3034			new_ctx->ct_setup.ioc_gss_client_size = (uint32_t)size;
3035			new_ctx->ct_setup.ioc_gss_client_nt = old_ctx->ct_setup.ioc_gss_client_nt;
3036		}
3037	}
3038
3039	/* Make a copy of any pointer values in case we are sharing this VC. */
3040	new_ctx->ct_setup.ioc_gss_target_name = USER_ADDR_NULL;
3041	new_ctx->ct_setup.ioc_gss_target_size = 0;
3042	new_ctx->ct_setup.ioc_gss_target_nt = GSSD_STRING_NAME;
3043	if (old_ctx->ct_setup.ioc_gss_target_name != USER_ADDR_NULL) {
3044		size = old_ctx->ct_setup.ioc_gss_target_size;
3045		newName = calloc(size, 1);
3046		if (newName) {
3047			oldName = (char *)(uintptr_t)old_ctx->ct_setup.ioc_gss_target_name;
3048			memcpy(newName,  oldName,  size);
3049			new_ctx->ct_setup.ioc_gss_target_name = CAST_USER_ADDR_T(newName);
3050			new_ctx->ct_setup.ioc_gss_target_size = (uint32_t)size;
3051			new_ctx->ct_setup.ioc_gss_target_nt = old_ctx->ct_setup.ioc_gss_target_nt;
3052		}
3053	}
3054
3055	/*
3056	 * The previous server was connected using kerberos and this server doesn't
3057	 * support Kerberos then nothing we can do try the cache and if that fails
3058	 * give up. Some day we should call Kerberous Helper?
3059	 */
3060	if ((old_ctx->ct_setup.ioc_userflags & SMBV_KERBEROS_ACCESS) &&
3061		(!serverSupportsKerberos(new_ctx->mechDict))) {
3062		goto done;
3063	}
3064	clientName = (char *)(uintptr_t)old_ctx->ct_setup.ioc_gss_client_name;
3065	/* We have no client name then just try the cache and get out */
3066	if (!clientName) {
3067		goto done;
3068	}
3069
3070	authInfoDict = CreateAuthDictionary(new_ctx, old_ctx->ct_vc_flags, clientName,
3071										old_ctx->ct_setup.ioc_gss_client_nt);
3072	if (!authInfoDict) {
3073		goto done;
3074	}
3075
3076	CFDictionarySetValue(openOptions, kNetFSUseAuthenticationInfoKey, kCFBooleanTrue);
3077	CFDictionarySetValue(openOptions, kNetFSAuthenticationInfoKey, authInfoDict);
3078	CFRelease(authInfoDict);
3079
3080done:
3081	return 0;
3082}
3083
3084
3085int findMountPointVC(void *inRef, const char *mntPoint)
3086{
3087	struct connectAddress conn;
3088	CFMutableArrayRef addressArray = NULL;
3089	CFMutableDataRef addressData = NULL;
3090	int error;
3091
3092	memset(&conn, 0, sizeof(conn));
3093	if (fsctl(mntPoint, (unsigned int)smbfsGetVCSockaddrFSCTL, &conn.storage, 0 ) != 0) {
3094		return errno;
3095	}
3096	addressArray = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks);
3097	if (!addressArray) {
3098		return ENOMEM;
3099	}
3100	addressData = CFDataCreateMutable(NULL, 0);
3101	if (addressData) {
3102		CFDataAppendBytes(addressData, (const UInt8 *)&conn, (CFIndex)sizeof(conn));
3103		CFArrayAppendValue(addressArray, addressData);
3104		CFRelease(addressData);
3105		error = findMatchingVC(inRef, addressArray);
3106	} else {
3107		error = ENOMEM;
3108	}
3109
3110	CFRelease(addressArray);
3111	return error;
3112
3113}
3114
3115/*
3116 * Create the smb library context and fill in the default values.
3117 */
3118void *create_smb_ctx(void)
3119{
3120	struct smb_ctx *ctx = NULL;
3121
3122	ctx = malloc(sizeof(struct smb_ctx));
3123	if (ctx == NULL)
3124		return NULL;
3125
3126	/* Clear out everything out to start with */
3127	bzero(ctx, sizeof(*ctx));
3128
3129	if (pthread_mutex_init(&ctx->ctx_mutex, NULL) == -1) {
3130		smb_log_info("%s: pthread_mutex_init failed, syserr = %s",
3131					 ASL_LEVEL_DEBUG, __FUNCTION__, strerror(errno));
3132		free(ctx);
3133		return NULL;
3134	}
3135
3136	ctx->ct_fd = -1;
3137
3138	/* We default to allowing them to touch the home directory */
3139	ctx->ct_setup.ioc_userflags = SMBV_HOME_ACCESS_OK;
3140	ctx->ct_ssn.ioc_owner = geteuid();
3141	ctx->ct_ssn.ioc_reconnect_wait_time = SMBM_RECONNECT_WAIT_TIME;
3142	getDefaultPreferences(&ctx->prefs);
3143	/*
3144	 * We need a local NetBIOS name when connecting on port 139 or when doing
3145	 * in kernel NTLMSSP. Once we remove the NTLMSSP kernel code we should
3146	 * relook at this code. See <rdar://problem/7016849>
3147	 */
3148	if (ctx->prefs.LocalNetBIOSName) {
3149		CFStringGetCString(ctx->prefs.LocalNetBIOSName, ctx->ct_ssn.ioc_localname,
3150					   sizeof(ctx->ct_ssn.ioc_localname), kCFStringEncodingUTF8);
3151	} else {
3152		smb_log_info("%s: Couldn't obtain the Local NetBIOS Name", ASL_LEVEL_DEBUG, __FUNCTION__);
3153	}
3154
3155	return ctx;
3156}
3157
3158/*
3159 * Create the smb library context and fill in the default values. Use the url
3160 * string to create a CFURL that should be used.
3161 */
3162int create_smb_ctx_with_url(struct smb_ctx **out_ctx, const char *url)
3163{
3164	int  error = EINVAL;
3165	struct smb_ctx *ctx = NULL;
3166
3167	if (!url) {
3168		error = EINVAL;
3169		goto failed;
3170	}
3171	/* Create the structure and fill in the default values */
3172	ctx = create_smb_ctx();
3173	if (ctx == NULL) {
3174		error = ENOMEM;
3175		goto failed;
3176	}
3177	/* Create the CFURL, from the c-style url string */
3178	ctx->ct_url = CreateSMBURL(url);
3179	if (ctx->ct_url)
3180		error = ParseSMBURL(ctx); /* Now verify the URL */
3181	if (error)
3182		goto failed;
3183
3184	*out_ctx = ctx;
3185	return 0;
3186
3187failed:
3188	smb_ctx_done(ctx);
3189	return error;
3190}
3191
3192/*
3193 * We are done with the smb library context remove it and all its pointers.
3194 */
3195void smb_ctx_done(void *inRef)
3196{
3197	struct smb_ctx *ctx = (struct smb_ctx *)inRef;
3198
3199	if (ctx == NULL)
3200		return; /* Nothing to do here */
3201
3202	if (pthread_mutex_trylock(&ctx->ctx_mutex) != 0) {
3203		smb_log_info("%s: Canceling connection", ASL_LEVEL_DEBUG, __FUNCTION__);
3204		smb_ctx_cancel_connection(ctx);
3205		pthread_mutex_lock(&ctx->ctx_mutex);
3206	}
3207	/* If we have a tree connect, disconnect */
3208	smb_share_disconnect(ctx);
3209	releasePreferenceInfo(&ctx->prefs);
3210	if (ctx->ct_fd != -1)
3211		close(ctx->ct_fd);
3212	if (ctx->ct_url)
3213		CFRelease(ctx->ct_url);
3214	if (ctx->serverName)
3215		free(ctx->serverName);
3216	if (ctx->serverNameRef)
3217		CFRelease(ctx->serverNameRef);
3218	if (ctx->ct_saddr)
3219		free(ctx->ct_saddr);
3220	if (ctx->ct_origshare)
3221		free(ctx->ct_origshare);
3222	if (ctx->mountPath)
3223		CFRelease(ctx->mountPath);
3224	if (ctx->ct_setup.ioc_gss_client_name)
3225		free((void *)((uintptr_t)(ctx->ct_setup.ioc_gss_client_name)));
3226	if (ctx->ct_setup.ioc_gss_target_name)
3227		free((void *)((uintptr_t)(ctx->ct_setup.ioc_gss_target_name)));
3228	if (ctx->mechDict) {
3229		CFRelease(ctx->mechDict);
3230		ctx->mechDict = NULL;
3231	}
3232	pthread_mutex_unlock(&ctx->ctx_mutex);
3233	pthread_mutex_destroy(&ctx->ctx_mutex);
3234	free(ctx);
3235}
3236
3237