/* * Copyright (c) 2010-2013 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #include #include #include #include #include #include #include #include #include #include #include #include "msdfs.h" #include #include #include "gss.h" #include "remount.h" /* * SysctlByFSID * * utility routine to call sysctl by fsid */ static int SysctlByFSID(int op, fsid_t fsid, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ctlname[CTL_MAXNAME+2]; size_t ctllen; const char *sysstr = "vfs.generic.ctlbyfsid"; struct vfsidctl vc; ctllen = CTL_MAXNAME+2; if (sysctlnametomib(sysstr, ctlname, &ctllen) == -1) { smb_log_info("%s: sysctlnametomib(%s)", ASL_LEVEL_ERR, __FUNCTION__, sysstr); return -1; }; ctlname[ctllen] = op; bzero(&vc, sizeof(vc)); vc.vc_vers = VFS_CTL_VERS1; vc.vc_fsid = fsid; vc.vc_ptr = newp; vc.vc_len = newlen; return sysctl(ctlname, (u_int)(ctllen + 1), oldp, oldlenp, &vc, sizeof(vc)); } /* * SysctlRemountInfo * * Calls into sysctl with a fsid to retrieve the remount information, may be * removed in the future. We could just have autofsd pass this info up with * the fsid. Need to decide if there any secuity concerns with doing it that * way. Until autofs work is complete we need this method to get the information. */ static int SysctlRemountInfo(fsid_t fsid, struct smb_remount_info *info) { size_t size; size = sizeof(*info); if (SysctlByFSID(SMBFS_SYSCTL_REMOUNT_INFO, fsid, info, &size, NULL, 0) == -1) { smb_log_info("%s: failed for 0x%x:0x%x - %s", ASL_LEVEL_ERR, __FUNCTION__, fsid.val[0], fsid.val[1], strerror(errno)); return errno; } #ifdef SMB_DEBUG smb_ctx_hexdump(__FUNCTION__, "Remount Info =", (u_char *)info, sizeof(info)); #endif // SMB_DEBUG return 0; } /* * SysctlRemountFS * * Calls into sysctl with a fsid to trigger the remount. The devId is just a * file descriptor to the device that holds the share. The kernel will * retrieve the share from the file descriptor and replace share on the mount * point with the new share. */ static int SysctlRemountFS(fsid_t fsid, int devId) { if (SysctlByFSID(SMBFS_SYSCTL_REMOUNT, fsid, NULL, NULL, &devId, sizeof(devId)) == -1) { smb_log_info("%s: failed for 0x%x:0x%x - %s", ASL_LEVEL_ERR, __FUNCTION__, fsid.val[0], fsid.val[1], strerror(errno)); return errno; } return 0; } static int GetRootShareConnection(struct smb_ctx *ctx, const char *url, uint32_t authFlags, const char * clientPrincipal, uint32_t clientNameType, uint32_t maxTimer) { CFDictionaryRef serverParams = NULL; CFDictionaryRef sessionInfo = NULL; CFMutableDictionaryRef openOptions; int error = 0; time_t start_time = time(NULL); openOptions = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); if (!openOptions) { smb_log_info("%s: Couldn't create open options for %s", ASL_LEVEL_ERR, __FUNCTION__, url); error = ENOMEM; goto done; } #ifdef SMBDEBUG_REMOUNT /* This is only needed for testing and should be remove once we have autofs hooked up */ CFDictionarySetValue(openOptions, kNetFSForceNewSessionKey, kCFBooleanTrue); #endif // SMBDEBUG_REMOUNT /* Never touch the user home directory */ CFDictionarySetValue(openOptions, kNetFSNoUserPreferencesKey, kCFBooleanTrue); /* * If they have a loopback in the referral we always allow it, no way for * us to decided what is correct at this point. */ CFDictionarySetValue(openOptions, kNetFSAllowLoopbackKey, kCFBooleanTrue); /* * Do a get server info call first to determine the if the server supports * the security we need. Also needed it to make sure we have the correct * server principal name. */ while (difftime(time(NULL), start_time) < maxTimer ) { error = smb_get_server_info(ctx, NULL, openOptions, &serverParams); if (!error) { break; } smb_log_info("%s: get server info failed %d, sleeping one second, have %d seconds left.", ASL_LEVEL_DEBUG, __FUNCTION__, error, maxTimer - (int)difftime(time(NULL), start_time)); sleep(1); /* Wait one second before trying again */ } if (error) { smb_log_info("%s: get server info failed from %s with %d", ASL_LEVEL_ERR, __FUNCTION__, url, error); goto done; } /* * We should check the server params and make sure this server supports * the same auth method as the old server. Doesn't really make any difference * we should fail in the open if they don't support the correct auth. */ /* * Set up the authorization using the same auth method that was use in the * original mount. */ if (authFlags & (SMBV_GUEST_ACCESS | SMBV_SFS_ACCESS | SMBV_PRIV_GUEST_ACCESS)) { CFDictionarySetValue( openOptions, kNetFSUseGuestKey, kCFBooleanTrue); } else { CFMutableDictionaryRef authInfoDict; authInfoDict = CreateAuthDictionary(ctx, authFlags, clientPrincipal, clientNameType); if (!authInfoDict) { smb_log_info("%s: Creating authorization dictionary failed for %s", ASL_LEVEL_ERR, __FUNCTION__, url); error = ENOMEM; goto done; } CFDictionarySetValue( openOptions, kNetFSUseAuthenticationInfoKey, kCFBooleanTrue); CFDictionarySetValue(openOptions, kNetFSAuthenticationInfoKey, authInfoDict); CFRelease(authInfoDict); } error = smb_open_session(ctx, NULL, openOptions, &sessionInfo); if (error) { smb_log_info("%s: open session failed from url %s with %d", ASL_LEVEL_ERR, __FUNCTION__, url, error); goto done; } error = smb_share_connect(ctx); if (error) { smb_log_info("%s: share connect failed from url %s with %d", ASL_LEVEL_ERR, __FUNCTION__, url, error); goto done; } done: if (sessionInfo) { CFRelease(sessionInfo); } if (serverParams) { CFRelease(serverParams); } if (openOptions) { CFRelease(openOptions); } return error; } /* * Would prefer to do all of this as the user that mounted the volume, but it * seems the sysctlbyfsid requires you to be root. So do the sysctlbyfsid as * root and everything else as the user. */ int smb_remount_with_fsid(fsid_t fsid) { int error, error2; struct smb_ctx *ctx = NULL; struct smb_remount_info remountInfo; uid_t rootUID = geteuid(); /* Get the remount information need for the remount */ memset(&remountInfo, 0, sizeof(remountInfo)); error = SysctlRemountInfo(fsid, &remountInfo); if (error) { goto done; } smb_log_info("%s: mount url smb:%s", ASL_LEVEL_DEBUG, __FUNCTION__, remountInfo.mntURL); smb_log_info("%s: client principal name %s", ASL_LEVEL_DEBUG, __FUNCTION__, remountInfo.mntClientPrincipalName); smb_log_info("%s: client principal name type %d", ASL_LEVEL_DEBUG, __FUNCTION__, remountInfo.mntClientPrincipalNameType); smb_log_info("%s: authorization flags 0x%x", ASL_LEVEL_DEBUG, __FUNCTION__, remountInfo.mntAuthFlags); smb_log_info("%s: owner of mount point %d", ASL_LEVEL_DEBUG, __FUNCTION__, remountInfo.mntOwner); /* Switch to the user that owns the mount */ error2 = seteuid(remountInfo.mntOwner); if (error2) { smb_log_info("%s: seteuid failed %d for mntOwner", ASL_LEVEL_ERR, __FUNCTION__, error2); goto done; } error = create_smb_ctx_with_url(&ctx, remountInfo.mntURL); if (error) { smb_log_info("%s: Could create ctx from url smb:%s", ASL_LEVEL_ERR, __FUNCTION__, remountInfo.mntURL); error2 = seteuid(rootUID); if (error2) { smb_log_info("%s: seteuid failed %d for rootUID", ASL_LEVEL_ERR, __FUNCTION__, error2); } goto done; } error = GetRootShareConnection(ctx, remountInfo.mntURL, remountInfo.mntAuthFlags, remountInfo.mntClientPrincipalName, remountInfo.mntClientPrincipalNameType, remountInfo.mntDeadTimer); if (!error) { struct smb_ctx *dfs_ctx = NULL; /* * XXX - Need to handle the DfsRoot remount case, we should check to * see if this is just a DfsRoot, if so then continue with the remount * because we found a different domain control to access. */ error = checkForDfsReferral(ctx, &dfs_ctx, NULL, NULL); if (error || (ctx == dfs_ctx) || (dfs_ctx == NULL)) { error2 = seteuid(rootUID); if (error2) { smb_log_info("%s: seteuid failed %d for rootUID", ASL_LEVEL_ERR, __FUNCTION__, error2); } goto done; } smb_ctx_done(ctx); ctx = dfs_ctx; } error2 = seteuid(rootUID); if (error2) { smb_log_info("%s: seteuid failed %d for rootUID", ASL_LEVEL_ERR, __FUNCTION__, error2); goto done; } if (error) { goto done; } if (smb_tree_conn_fstype(ctx) == SMB_FS_FAT) { smb_log_info("%s: Could remount url smb:%s found a fat file system", ASL_LEVEL_ERR, __FUNCTION__, remountInfo.mntURL); error = ENOTSUP; goto done; } if (!error) { error = SysctlRemountFS(fsid, ctx->ct_fd); } done: if (ctx) { error2 = seteuid(remountInfo.mntOwner); if (error2) { smb_log_info("%s: seteuid failed %d for mntOwner", ASL_LEVEL_ERR, __FUNCTION__, error2); } smb_ctx_done(ctx); error2 = seteuid(rootUID); if (error2) { smb_log_info("%s: seteuid failed %d for rootUID", ASL_LEVEL_ERR, __FUNCTION__, error2); } } if (error) { smb_log_info("%s: remount failed for url %s with error = %d", ASL_LEVEL_ERR, __FUNCTION__, remountInfo.mntURL, error); } return error; }