1/*
2 * Copyright (c) 2009 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 <security/pam_appl.h>
25#include <security/pam_modules.h>
26#include <security/openpam.h>
27
28#include <sys/mount.h>
29#include <sys/param.h>
30#include <sys/stat.h>
31#include <stdio.h>
32#include <unistd.h>
33#include <limits.h>
34#include <string.h>
35#include <pwd.h>
36#include <utmpx.h>
37
38#include <CoreFoundation/CoreFoundation.h>
39#include <OpenDirectory/OpenDirectory.h>
40#include <NetFS/URLMount.h>
41#include <DiskImages/DIHLFileVaultInterface.h>
42#include <DiskImages/DIFrameworkUtilities.h>
43
44#include "Common.h"
45
46#define PM_DISPLAY_NAME "mount"
47
48PAM_EXTERN int
49pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv)
50{
51	static const char password_prompt[] = "Password:";
52	char *authenticator = NULL;
53
54	if (PAM_SUCCESS != pam_get_authtok(pamh, PAM_AUTHTOK, (void *)&authenticator, password_prompt)) {
55		openpam_log(PAM_LOG_DEBUG, "Unable to obtain the authtok.");
56		return PAM_IGNORE;
57	}
58	if (PAM_SUCCESS != pam_setenv(pamh, "mount_authenticator", authenticator, 1)) {
59		openpam_log(PAM_LOG_DEBUG, "Unable to set the authtok in the environment.");
60		return PAM_IGNORE;
61	}
62
63	return PAM_SUCCESS;
64}
65
66
67PAM_EXTERN int
68pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv)
69{
70	return PAM_SUCCESS;
71}
72
73
74PAM_EXTERN int
75pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char **argv)
76{
77	int retval = PAM_SUCCESS;
78	char *server_URL = NULL;
79	char *path = NULL;
80	char *homedir = NULL;
81	int mountdirlen = PATH_MAX;
82	char *mountdir = NULL;
83	const char *username = NULL;
84	const char *authenticator = NULL;
85	struct passwd *pwd = NULL;
86	struct passwd pwdbuf;
87	char pwbuffer[2 * PATH_MAX];
88	unsigned int was_remounted = 0;
89
90	/* get the username */
91	if (PAM_SUCCESS != (retval = pam_get_user(pamh, &username, NULL))) {
92		openpam_log(PAM_LOG_ERROR, "Unable to get the username: %s", pam_strerror(pamh, retval));
93		goto fin;
94	}
95	if (username == NULL || *username == '\0') {
96		openpam_log(PAM_LOG_ERROR, "Username is invalid.");
97		retval = PAM_PERM_DENIED;
98		goto fin;
99	}
100
101	/* get the uid */
102	if (0 != getpwnam_r(username, &pwdbuf, pwbuffer, sizeof(pwbuffer), &pwd) || NULL == pwd) {
103		openpam_log(PAM_LOG_ERROR, "Unknown user \"%s\".", username);
104		retval = PAM_SYSTEM_ERR;
105		goto fin;
106	}
107
108	/* get the authenticator */
109	if (NULL == (authenticator = pam_getenv(pamh, "mount_authenticator"))) {
110		openpam_log(PAM_LOG_DEBUG, "Unable to retrieve the authenticator.");
111		retval = PAM_IGNORE;
112		goto fin;
113	}
114
115	/* get the server_URL, path and homedir from OD */
116	if (PAM_SUCCESS != (retval = od_extract_home(pamh, username, &server_URL, &path, &homedir))) {
117		openpam_log(PAM_LOG_ERROR, "Error retrieve data from OpenDirectory: %s", pam_strerror(pamh, retval));
118		goto fin;
119	}
120
121	openpam_log(PAM_LOG_DEBUG, "           UID: %d", pwd->pw_uid);
122	openpam_log(PAM_LOG_DEBUG, "    server_URL: %s", server_URL);
123	openpam_log(PAM_LOG_DEBUG, "          path: %s", path);
124	openpam_log(PAM_LOG_DEBUG, "       homedir: %s", homedir);
125	openpam_log(PAM_LOG_DEBUG, "      username: %s", username);
126	//openpam_log(PAM_LOG_DEBUG, " authenticator: %s", authenticator);  // We don't want to log user's passwords.
127
128	/* determine if we need to mount the home folder */
129	// this triggers the automounting for nfs
130	if (0 == access(homedir, F_OK) || EACCES == errno) {
131		openpam_log(PAM_LOG_DEBUG, "The home folder share is already mounted.");
132	}
133	if (NULL == (mountdir = malloc(mountdirlen+1))) {
134		openpam_log(PAM_LOG_DEBUG, "Failed to malloc the mountdir.");
135		retval = PAM_IGNORE;
136		goto fin;
137	}
138
139	/* mount the home folder */
140	if (NULL != server_URL && retval == PAM_SUCCESS) {
141		// for an afp or smb home folder
142		if (NULL != path) {
143			if (0 != NetFSMountHomeDirectoryWithAuthentication(server_URL,
144									   homedir,
145									   path,
146									   pwd->pw_uid,
147									   mountdirlen,
148									   mountdir,
149									   username,
150									   authenticator,
151									   kNetFSAllowKerberos,
152									   &was_remounted)) {
153				openpam_log(PAM_LOG_DEBUG, "Unable to mount the home folder.");
154				retval = PAM_SESSION_ERR;
155				goto fin;
156			}
157			else {
158				if (0 != was_remounted) {
159					openpam_log(PAM_LOG_DEBUG, "Remounted home folder.");
160				}
161				else {
162					openpam_log(PAM_LOG_DEBUG, "Mounted home folder.");
163				}
164				/* cache the homedir and path for close_session */
165				pam_set_data(pamh, "homedir", homedir, openpam_free_data);
166				pam_set_data(pamh, "path", path, openpam_free_data);
167				homedir = NULL;
168				path = NULL;
169			}
170		}
171		// for a FileVault home folder
172		if (NULL == path && NULL != homedir) {
173			CFStringRef password = CFStringCreateWithCString(NULL, authenticator, kCFStringEncodingUTF8);
174			CFStringRef url = CFStringCreateWithCString(NULL, server_URL, kCFStringEncodingUTF8);
175			CFURLRef dmgin = CFURLCreateWithString(NULL, url, NULL);
176			CFStringRef mountpoint = CFStringCreateWithCString(NULL, homedir, kCFStringEncodingUTF8);
177			CFStringRef devicepath = NULL;
178			CFStringRef mountpath = NULL;
179
180			if (0 != DIHLFVMount(dmgin, kDIHLFVCredUserPasswordType, password, mountpoint, NULL, NULL, NULL, &mountpath, &devicepath)) {
181				openpam_log(PAM_LOG_ERROR, "Unable to mount the FileVault home folder.");
182				retval = PAM_SESSION_ERR;
183			}
184			else {
185				openpam_log(PAM_LOG_DEBUG, "Mounted FileVault home folder.");
186				pam_set_data(pamh, "devicepath", (void *)devicepath, pam_cf_cleanup);
187			}
188			CFRelease(url);
189			CFRelease(password);
190			CFRelease(dmgin);
191			CFRelease(mountpoint);
192		}
193	}
194	else {
195		// skip unmount for local homes
196		pam_set_data(pamh, "path", strdup(""), openpam_free_data);
197	}
198
199
200fin:
201	pam_unsetenv(pamh, "mount_authenticator");
202	free(homedir);
203	free(mountdir);
204	free(server_URL);
205	free(path);
206
207	return retval;
208}
209
210
211PAM_EXTERN int
212pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, const char **argv)
213{
214	int retval = PAM_SUCCESS;
215	const char *username;
216	char *server_URL = NULL;
217	char *path = NULL;
218	char *homedir = NULL;
219	CFStringRef devicepath = NULL;
220	struct passwd *pwd = NULL;
221	struct passwd pwdbuf;
222	char pwbuffer[2 * PATH_MAX];
223	CFStringRef cfhomedir = NULL;
224	CFURLRef home_url = NULL;
225	DASessionRef dasession = NULL;
226	DADiskRef da = NULL;
227	const char *devnode = NULL;
228
229	/* get the username */
230	retval = pam_get_user(pamh, &username, NULL);
231	if (retval != PAM_SUCCESS) {
232		openpam_log(PAM_LOG_ERROR, "Unable to get the username: %s", pam_strerror(pamh, retval));
233		return retval;
234	}
235	if (username == NULL || *username == '\0') {
236		openpam_log(PAM_LOG_ERROR, "Username is invalid.");
237		return PAM_PERM_DENIED;
238	}
239
240	/* determine if we need to unmount the home folder */
241	struct utmpx *x;
242	setutxent();
243	while (NULL != (x = getutxent())) {
244		if (USER_PROCESS == x->ut_type && 0 == strcmp(x->ut_user, username) && x->ut_pid != getpid()) {
245			openpam_log(PAM_LOG_DEBUG, "User is still logged in elsewhere (%s), skipping home folder unmount.", x->ut_line);
246			return PAM_IGNORE;
247		}
248	}
249	endutxent();
250
251	/* try to retrieve the cached devicepath */
252	if (PAM_SUCCESS != pam_get_data(pamh, "devicepath", (void *)&devicepath)) {
253		openpam_log(PAM_LOG_DEBUG, "No cached devicepath in the PAM context.");
254	}
255	if (NULL != devicepath) {
256		if (NULL == (devicepath = CFStringCreateCopy(kCFAllocatorDefault, devicepath))) {
257			openpam_log(PAM_LOG_ERROR, "Failed to duplicate the devicepath.");
258			retval = PAM_BUF_ERR;
259			goto fin;
260		}
261	}
262
263	/* try to retrieve the cached homedir */
264	if (PAM_SUCCESS != pam_get_data(pamh, "homedir", (void *)&homedir)) {
265		openpam_log(PAM_LOG_DEBUG, "No cached homedir in the PAM context.");
266	}
267	if (NULL != homedir) {
268		if (NULL == (homedir = strdup(homedir))) {
269			openpam_log(PAM_LOG_ERROR, "Failed to duplicate the homedir.");
270			retval = PAM_BUF_ERR;
271			goto fin;
272		}
273	}
274
275	/* try to retrieve the cached path */
276	if (PAM_SUCCESS != pam_get_data(pamh, "path", (void *)&path)) {
277		openpam_log(PAM_LOG_DEBUG, "No cached path in the PAM context.");
278	}
279	if (NULL != path) {
280		if (NULL == (path = strdup(path))) {
281			openpam_log(PAM_LOG_ERROR, "Failed to duplicate the path.");
282			retval = PAM_BUF_ERR;
283			goto fin;
284		}
285	}
286
287	/* skip unmount for local homes */
288	if (NULL != path && 0 == strcmp("", path)) {
289		openpam_log(PAM_LOG_DEBUG, "Skipping unmount.");
290		goto fin;
291	}
292
293	/* get the homedir and path or devicepath if needed */
294	if ((NULL == homedir || NULL == path) && NULL == devicepath) {
295		if (PAM_SUCCESS != (retval = od_extract_home(pamh, username, &server_URL, &path, &homedir))) {
296			openpam_log(PAM_LOG_ERROR, "Error retrieve data from OpenDirectory: %s", pam_strerror(pamh, retval));
297			goto fin;
298		}
299		if (NULL != server_URL && NULL == path && NULL != homedir) {
300			openpam_log(PAM_LOG_DEBUG, "Constructing the FileVault home path.");
301			if ((NULL == (cfhomedir = CFStringCreateWithCString(kCFAllocatorDefault, homedir, kCFStringEncodingUTF8))) ||
302				(NULL == (home_url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, cfhomedir, kCFURLPOSIXPathStyle, true))) ||
303				(NULL == (dasession = DASessionCreate(kCFAllocatorDefault))) ||
304				(NULL == (da = DADiskCreateFromVolumePath(kCFAllocatorDefault, dasession, home_url))) ||
305				(NULL == (devnode = DADiskGetBSDName(da))) ||
306				(NULL == (devicepath = CFStringCreateWithCString(kCFAllocatorDefault, devnode, kCFStringEncodingUTF8)))) {
307				openpam_log(PAM_LOG_ERROR, "Unable to to construct the FileVault home path.");
308				retval = PAM_SYSTEM_ERR;
309				goto fin;
310			}
311		}
312	}
313
314	/* attempt to unmount the home folder */
315	if (NULL == devicepath && NULL != homedir && NULL != path) {
316		// not FileVault
317		if (0 != getpwnam_r(username, &pwdbuf, pwbuffer, sizeof(pwbuffer), &pwd) || NULL == pwd) {
318			openpam_log(PAM_LOG_ERROR, "Unknown user \"%s\".", username);
319			retval = PAM_SYSTEM_ERR;
320			goto fin;
321		}
322		if (0 != NetFSUnmountHomeDirectory(homedir, path, pwd->pw_uid, 0)) {
323			openpam_log(PAM_LOG_DEBUG, "Unable to unmount the home folder: %s.", strerror(errno));
324			retval = PAM_IGNORE;
325		}
326		else {
327			openpam_log(PAM_LOG_DEBUG, "Unmounted home folder.");
328		}
329	}
330	else if (NULL != devicepath && NULL != homedir && NULL == path) {
331		// FileVault
332		if (0 != (retval = DIHLFVUnmount(devicepath, kDIHLFVUnmountNormally, kDIHLFVUnmountNoTimeout))) {
333			openpam_log(PAM_LOG_DEBUG, "Unable to unmount the FileVault home folder: %s.", DIStrError(retval));
334			retval = PAM_IGNORE;
335		}
336		else {
337			openpam_log(PAM_LOG_DEBUG, "Unmounted FileVault home folder.");
338		}
339	}
340	else {
341		// nothing
342		openpam_log(PAM_LOG_DEBUG, "There is nothing to unmount.");
343		retval = PAM_IGNORE;
344	}
345
346fin:
347	free(server_URL);
348	free(path);
349	free(homedir);
350
351	if (home_url)
352		CFRelease(home_url);
353	if (cfhomedir)
354		CFRelease(cfhomedir);
355	if (dasession)
356		CFRelease(dasession);
357	if (da)
358		CFRelease(da);
359	if (devicepath)
360		CFRelease(devicepath);
361
362	return retval;
363}
364