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