1/* 2 * Copyright (c) 2010-2013 Apple Inc. All rights reserved. 3 * 4 * @APPLE_LICENSE_HEADER_START@ 5 * 6 * This file contains Original Code and/or Modifications of Original Code 7 * as defined in and that are subject to the Apple Public Source License 8 * Version 2.0 (the 'License'). You may not use this file except in 9 * compliance with the License. Please obtain a copy of the License at 10 * http://www.opensource.apple.com/apsl/ and read it before using this 11 * file. 12 * 13 * The Original Code and all software distributed under the License are 14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER 15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, 16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, 17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. 18 * Please see the License for the specific language governing rights and 19 * limitations under the License. 20 * 21 * @APPLE_LICENSE_HEADER_END@ 22 */ 23 24#include <sys/sysctl.h> 25#include <NetFS/NetFS.h> 26#include <KerberosHelper/KerberosHelper.h> 27#include <KerberosHelper/NetworkAuthenticationHelper.h> 28#include <CoreFoundation/CoreFoundation.h> 29 30#include <netsmb/smb_lib.h> 31#include <netsmb/smb_conn.h> 32#include <smbfs/smbfs.h> 33#include <parse_url.h> 34#include <netsmb/upi_mbuf.h> 35#include <sys/mchain.h> 36#include "msdfs.h" 37#include <smbclient/smbclient.h> 38#include <smbclient/smbclient_internal.h> 39#include "gss.h" 40#include "remount.h" 41 42/* 43 * SysctlByFSID 44 * 45 * utility routine to call sysctl by fsid 46 */ 47static int 48SysctlByFSID(int op, fsid_t fsid, void *oldp, size_t *oldlenp, void *newp, 49 size_t newlen) 50{ 51 int ctlname[CTL_MAXNAME+2]; 52 size_t ctllen; 53 const char *sysstr = "vfs.generic.ctlbyfsid"; 54 struct vfsidctl vc; 55 56 ctllen = CTL_MAXNAME+2; 57 if (sysctlnametomib(sysstr, ctlname, &ctllen) == -1) { 58 smb_log_info("%s: sysctlnametomib(%s)", ASL_LEVEL_ERR, __FUNCTION__, 59 sysstr); 60 return -1; 61 }; 62 ctlname[ctllen] = op; 63 64 bzero(&vc, sizeof(vc)); 65 vc.vc_vers = VFS_CTL_VERS1; 66 vc.vc_fsid = fsid; 67 vc.vc_ptr = newp; 68 vc.vc_len = newlen; 69 return sysctl(ctlname, (u_int)(ctllen + 1), oldp, oldlenp, &vc, sizeof(vc)); 70} 71 72 73/* 74 * SysctlRemountInfo 75 * 76 * Calls into sysctl with a fsid to retrieve the remount information, may be 77 * removed in the future. We could just have autofsd pass this info up with 78 * the fsid. Need to decide if there any secuity concerns with doing it that 79 * way. Until autofs work is complete we need this method to get the information. 80 */ 81static int 82SysctlRemountInfo(fsid_t fsid, struct smb_remount_info *info) 83{ 84 size_t size; 85 86 size = sizeof(*info); 87 if (SysctlByFSID(SMBFS_SYSCTL_REMOUNT_INFO, fsid, info, &size, NULL, 0) == -1) { 88 smb_log_info("%s: failed for 0x%x:0x%x - %s", ASL_LEVEL_ERR, 89 __FUNCTION__, fsid.val[0], fsid.val[1], strerror(errno)); 90 return errno; 91 } 92#ifdef SMB_DEBUG 93 smb_ctx_hexdump(__FUNCTION__, "Remount Info =", (u_char *)info, sizeof(info)); 94#endif // SMB_DEBUG 95 return 0; 96} 97 98/* 99 * SysctlRemountFS 100 * 101 * Calls into sysctl with a fsid to trigger the remount. The devId is just a 102 * file descriptor to the device that holds the share. The kernel will 103 * retrieve the share from the file descriptor and replace share on the mount 104 * point with the new share. 105 */ 106static int 107SysctlRemountFS(fsid_t fsid, int devId) 108{ 109 if (SysctlByFSID(SMBFS_SYSCTL_REMOUNT, fsid, NULL, NULL, &devId, 110 sizeof(devId)) == -1) { 111 smb_log_info("%s: failed for 0x%x:0x%x - %s", ASL_LEVEL_ERR, 112 __FUNCTION__, fsid.val[0], fsid.val[1], strerror(errno)); 113 return errno; 114 } 115 return 0; 116} 117 118static int 119GetRootShareConnection(struct smb_ctx *ctx, const char *url, uint32_t authFlags, 120 const char * clientPrincipal, uint32_t clientNameType, 121 uint32_t maxTimer) 122{ 123 CFDictionaryRef serverParams = NULL; 124 CFDictionaryRef sessionInfo = NULL; 125 CFMutableDictionaryRef openOptions; 126 int error = 0; 127 time_t start_time = time(NULL); 128 129 openOptions = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, 130 &kCFTypeDictionaryKeyCallBacks, 131 &kCFTypeDictionaryValueCallBacks); 132 if (!openOptions) { 133 smb_log_info("%s: Couldn't create open options for %s", ASL_LEVEL_ERR, 134 __FUNCTION__, url); 135 error = ENOMEM; 136 goto done; 137 } 138 139#ifdef SMBDEBUG_REMOUNT 140 /* This is only needed for testing and should be remove once we have autofs hooked up */ 141 CFDictionarySetValue(openOptions, kNetFSForceNewSessionKey, kCFBooleanTrue); 142#endif // SMBDEBUG_REMOUNT 143 144 /* Never touch the user home directory */ 145 CFDictionarySetValue(openOptions, kNetFSNoUserPreferencesKey, kCFBooleanTrue); 146 /* 147 * If they have a loopback in the referral we always allow it, no way for 148 * us to decided what is correct at this point. 149 */ 150 CFDictionarySetValue(openOptions, kNetFSAllowLoopbackKey, kCFBooleanTrue); 151 152 /* 153 * Do a get server info call first to determine the if the server supports 154 * the security we need. Also needed it to make sure we have the correct 155 * server principal name. 156 */ 157 while (difftime(time(NULL), start_time) < maxTimer ) { 158 error = smb_get_server_info(ctx, NULL, openOptions, &serverParams); 159 if (!error) { 160 break; 161 } 162 smb_log_info("%s: get server info failed %d, sleeping one second, have %d seconds left.", 163 ASL_LEVEL_DEBUG, __FUNCTION__, error, 164 maxTimer - (int)difftime(time(NULL), start_time)); 165 sleep(1); /* Wait one second before trying again */ 166 } 167 if (error) { 168 smb_log_info("%s: get server info failed from %s with %d", 169 ASL_LEVEL_ERR, __FUNCTION__, url, error); 170 goto done; 171 } 172 173 /* 174 * We should check the server params and make sure this server supports 175 * the same auth method as the old server. Doesn't really make any difference 176 * we should fail in the open if they don't support the correct auth. 177 */ 178 179 /* 180 * Set up the authorization using the same auth method that was use in the 181 * original mount. 182 */ 183 if (authFlags & (SMBV_GUEST_ACCESS | SMBV_SFS_ACCESS | SMBV_PRIV_GUEST_ACCESS)) { 184 CFDictionarySetValue( openOptions, kNetFSUseGuestKey, kCFBooleanTrue); 185 } else { 186 CFMutableDictionaryRef authInfoDict; 187 188 authInfoDict = CreateAuthDictionary(ctx, authFlags, clientPrincipal, clientNameType); 189 if (!authInfoDict) { 190 smb_log_info("%s: Creating authorization dictionary failed for %s", 191 ASL_LEVEL_ERR, __FUNCTION__, url); 192 error = ENOMEM; 193 goto done; 194 } 195 CFDictionarySetValue( openOptions, kNetFSUseAuthenticationInfoKey, kCFBooleanTrue); 196 CFDictionarySetValue(openOptions, kNetFSAuthenticationInfoKey, authInfoDict); 197 CFRelease(authInfoDict); 198 } 199 200 error = smb_open_session(ctx, NULL, openOptions, &sessionInfo); 201 if (error) { 202 smb_log_info("%s: open session failed from url %s with %d", ASL_LEVEL_ERR, 203 __FUNCTION__, url, error); 204 goto done; 205 } 206 error = smb_share_connect(ctx); 207 if (error) { 208 smb_log_info("%s: share connect failed from url %s with %d", ASL_LEVEL_ERR, 209 __FUNCTION__, url, error); 210 goto done; 211 } 212done: 213 if (sessionInfo) { 214 CFRelease(sessionInfo); 215 } 216 if (serverParams) { 217 CFRelease(serverParams); 218 } 219 if (openOptions) { 220 CFRelease(openOptions); 221 } 222 return error; 223} 224 225/* 226 * Would prefer to do all of this as the user that mounted the volume, but it 227 * seems the sysctlbyfsid requires you to be root. So do the sysctlbyfsid as 228 * root and everything else as the user. 229 */ 230int smb_remount_with_fsid(fsid_t fsid) 231{ 232 int error, error2; 233 struct smb_ctx *ctx = NULL; 234 struct smb_remount_info remountInfo; 235 uid_t rootUID = geteuid(); 236 237 /* Get the remount information need for the remount */ 238 memset(&remountInfo, 0, sizeof(remountInfo)); 239 error = SysctlRemountInfo(fsid, &remountInfo); 240 if (error) { 241 goto done; 242 } 243 244 smb_log_info("%s: mount url smb:%s", ASL_LEVEL_DEBUG, __FUNCTION__, 245 remountInfo.mntURL); 246 smb_log_info("%s: client principal name %s", ASL_LEVEL_DEBUG, __FUNCTION__, 247 remountInfo.mntClientPrincipalName); 248 smb_log_info("%s: client principal name type %d", ASL_LEVEL_DEBUG, __FUNCTION__, 249 remountInfo.mntClientPrincipalNameType); 250 smb_log_info("%s: authorization flags 0x%x", ASL_LEVEL_DEBUG, __FUNCTION__, 251 remountInfo.mntAuthFlags); 252 smb_log_info("%s: owner of mount point %d", ASL_LEVEL_DEBUG, __FUNCTION__, 253 remountInfo.mntOwner); 254 255 /* Switch to the user that owns the mount */ 256 error2 = seteuid(remountInfo.mntOwner); 257 if (error2) { 258 smb_log_info("%s: seteuid failed %d for mntOwner", ASL_LEVEL_ERR, 259 __FUNCTION__, error2); 260 goto done; 261 } 262 263 error = create_smb_ctx_with_url(&ctx, remountInfo.mntURL); 264 if (error) { 265 smb_log_info("%s: Could create ctx from url smb:%s", ASL_LEVEL_ERR, 266 __FUNCTION__, remountInfo.mntURL); 267 error2 = seteuid(rootUID); 268 if (error2) { 269 smb_log_info("%s: seteuid failed %d for rootUID", ASL_LEVEL_ERR, 270 __FUNCTION__, error2); 271 } 272 273 goto done; 274 } 275 276 error = GetRootShareConnection(ctx, remountInfo.mntURL, 277 remountInfo.mntAuthFlags, 278 remountInfo.mntClientPrincipalName, 279 remountInfo.mntClientPrincipalNameType, 280 remountInfo.mntDeadTimer); 281 if (!error) { 282 struct smb_ctx *dfs_ctx = NULL; 283 284 /* 285 * XXX - Need to handle the DfsRoot remount case, we should check to 286 * see if this is just a DfsRoot, if so then continue with the remount 287 * because we found a different domain control to access. 288 */ 289 error = checkForDfsReferral(ctx, &dfs_ctx, NULL, NULL); 290 if (error || 291 (ctx == dfs_ctx) || 292 (dfs_ctx == NULL)) { 293 error2 = seteuid(rootUID); 294 if (error2) { 295 smb_log_info("%s: seteuid failed %d for rootUID", ASL_LEVEL_ERR, 296 __FUNCTION__, error2); 297 } 298 299 goto done; 300 } 301 302 smb_ctx_done(ctx); 303 ctx = dfs_ctx; 304 } 305 306 error2 = seteuid(rootUID); 307 if (error2) { 308 smb_log_info("%s: seteuid failed %d for rootUID", ASL_LEVEL_ERR, 309 __FUNCTION__, error2); 310 goto done; 311 } 312 313 if (error) { 314 goto done; 315 } 316 317 if (smb_tree_conn_fstype(ctx) == SMB_FS_FAT) { 318 smb_log_info("%s: Could remount url smb:%s found a fat file system", 319 ASL_LEVEL_ERR, __FUNCTION__, remountInfo.mntURL); 320 error = ENOTSUP; 321 goto done; 322 } 323 324 if (!error) { 325 error = SysctlRemountFS(fsid, ctx->ct_fd); 326 } 327 328done: 329 if (ctx) { 330 error2 = seteuid(remountInfo.mntOwner); 331 if (error2) { 332 smb_log_info("%s: seteuid failed %d for mntOwner", ASL_LEVEL_ERR, 333 __FUNCTION__, error2); 334 } 335 336 smb_ctx_done(ctx); 337 338 error2 = seteuid(rootUID); 339 if (error2) { 340 smb_log_info("%s: seteuid failed %d for rootUID", ASL_LEVEL_ERR, 341 __FUNCTION__, error2); 342 } 343 } 344 345 if (error) { 346 smb_log_info("%s: remount failed for url %s with error = %d", 347 ASL_LEVEL_ERR, __FUNCTION__, remountInfo.mntURL, error); 348 } 349 350 return error; 351} 352