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