xref: /src/sys/contrib/openzfs/contrib/pam_zfs_key/pam_zfs_key.c (revision 8a62a2a5659d1839d8799b4274c04469d7f17c78)
1 // SPDX-License-Identifier: BSD-3-Clause
2 /*
3  * Redistribution and use in source and binary forms, with or without
4  * modification, are permitted provided that the following conditions are met:
5  *     * Redistributions of source code must retain the above copyright
6  *       notice, this list of conditions and the following disclaimer.
7  *     * Redistributions in binary form must reproduce the above copyright
8  *       notice, this list of conditions and the following disclaimer in the
9  *       documentation and/or other materials provided with the distribution.
10  *     * Neither the name of the <organization> nor the
11  *       names of its contributors may be used to endorse or promote products
12  *       derived from this software without specific prior written permission.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
18  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
21  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
23  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  *
25  * Copyright (c) 2020, Felix Dörre
26  * All rights reserved.
27  */
28 
29 #include <sys/dsl_crypt.h>
30 #include <sys/byteorder.h>
31 #include <libzfs.h>
32 
33 #include <syslog.h>
34 
35 #include <sys/zio_crypt.h>
36 #include <openssl/evp.h>
37 
38 #define	PAM_SM_AUTH
39 #define	PAM_SM_PASSWORD
40 #define	PAM_SM_SESSION
41 #include <security/pam_modules.h>
42 
43 #if	defined(__linux__)
44 #include <security/pam_ext.h>
45 #define	MAP_FLAGS MAP_PRIVATE | MAP_ANONYMOUS
46 #elif	defined(__FreeBSD__)
47 #include <security/pam_appl.h>
48 static void
pam_syslog(pam_handle_t * pamh,int loglevel,const char * fmt,...)49 pam_syslog(pam_handle_t *pamh, int loglevel, const char *fmt, ...)
50 {
51 	(void) pamh;
52 	va_list args;
53 	va_start(args, fmt);
54 	vsyslog(loglevel, fmt, args);
55 	va_end(args);
56 }
57 #define	MAP_FLAGS MAP_PRIVATE | MAP_ANON | MAP_NOCORE
58 #endif
59 
60 #include <string.h>
61 #include <unistd.h>
62 #include <fcntl.h>
63 #include <sys/stat.h>
64 #include <sys/file.h>
65 #include <sys/wait.h>
66 #include <pwd.h>
67 #include <lib/libzfs/libzfs_impl.h>
68 
69 #include <sys/mman.h>
70 
71 static const char PASSWORD_VAR_NAME[] = "pam_zfs_key_authtok";
72 static const char OLD_PASSWORD_VAR_NAME[] = "pam_zfs_key_oldauthtok";
73 
74 static libzfs_handle_t *g_zfs;
75 
76 static void destroy_pw(pam_handle_t *pamh, void *data, int errcode);
77 
78 typedef int (*mlock_func_t) (const void *, size_t);
79 
80 typedef struct {
81 	size_t len;
82 	char *value;
83 } pw_password_t;
84 
85 /*
86  * Try to mlock(2) or munlock(2) addr while handling EAGAIN by retrying ten
87  * times and sleeping 10 milliseconds in between for a total of 0.1
88  * seconds. lock_func must point to either mlock(2) or munlock(2).
89  */
90 static int
try_lock(mlock_func_t lock_func,const void * addr,size_t len)91 try_lock(mlock_func_t lock_func, const void *addr, size_t len)
92 {
93 	int err;
94 	int retries = 10;
95 	useconds_t sleep_dur = 10 * 1000;
96 
97 	if ((err = (*lock_func)(addr, len)) != EAGAIN) {
98 		return (err);
99 	}
100 	for (int i = retries; i > 0; --i) {
101 		(void) usleep(sleep_dur);
102 		if ((err = (*lock_func)(addr, len)) != EAGAIN) {
103 			break;
104 		}
105 	}
106 	return (err);
107 }
108 
109 
110 static pw_password_t *
alloc_pw_size(size_t len)111 alloc_pw_size(size_t len)
112 {
113 	pw_password_t *pw = malloc(sizeof (pw_password_t));
114 	if (!pw) {
115 		return (NULL);
116 	}
117 	pw->len = len;
118 	/*
119 	 * We use mmap(2) rather than malloc(3) since later on we mlock(2) the
120 	 * memory region. Since mlock(2) and munlock(2) operate on whole memory
121 	 * pages we should allocate a whole page here as mmap(2) does. Further
122 	 * this ensures that the addresses passed to mlock(2) an munlock(2) are
123 	 * on a page boundary as suggested by FreeBSD and required by some
124 	 * other implementations. Finally we avoid inadvertently munlocking
125 	 * memory mlocked by an concurrently running instance of us.
126 	 */
127 	pw->value = mmap(NULL, pw->len, PROT_READ | PROT_WRITE, MAP_FLAGS,
128 	    -1, 0);
129 
130 	if (pw->value == MAP_FAILED) {
131 		free(pw);
132 		return (NULL);
133 	}
134 	if (try_lock(mlock, pw->value, pw->len) != 0) {
135 		(void) munmap(pw->value, pw->len);
136 		free(pw);
137 		return (NULL);
138 	}
139 	return (pw);
140 }
141 
142 static pw_password_t *
alloc_pw_string(const char * source)143 alloc_pw_string(const char *source)
144 {
145 	size_t len = strlen(source) + 1;
146 	pw_password_t *pw = alloc_pw_size(len);
147 
148 	if (!pw) {
149 		return (NULL);
150 	}
151 	memcpy(pw->value, source, pw->len);
152 	return (pw);
153 }
154 
155 static void
pw_free(pw_password_t * pw)156 pw_free(pw_password_t *pw)
157 {
158 	memset(pw->value, 0, pw->len);
159 	if (try_lock(munlock, pw->value, pw->len) == 0) {
160 		(void) munmap(pw->value, pw->len);
161 	}
162 	free(pw);
163 }
164 
165 static pw_password_t *
pw_fetch(pam_handle_t * pamh,int tok)166 pw_fetch(pam_handle_t *pamh, int tok)
167 {
168 	const char *token;
169 	if (pam_get_authtok(pamh, tok, &token, NULL) != PAM_SUCCESS) {
170 		pam_syslog(pamh, LOG_ERR,
171 		    "couldn't get password from PAM stack");
172 		return (NULL);
173 	}
174 	if (!token) {
175 		pam_syslog(pamh, LOG_ERR,
176 		    "token from PAM stack is null");
177 		return (NULL);
178 	}
179 	return (alloc_pw_string(token));
180 }
181 
182 static const pw_password_t *
pw_fetch_lazy(pam_handle_t * pamh,int tok,const char * var_name)183 pw_fetch_lazy(pam_handle_t *pamh, int tok, const char *var_name)
184 {
185 	pw_password_t *pw = pw_fetch(pamh, tok);
186 	if (pw == NULL) {
187 		return (NULL);
188 	}
189 	int ret = pam_set_data(pamh, var_name, pw, destroy_pw);
190 	if (ret != PAM_SUCCESS) {
191 		pw_free(pw);
192 		pam_syslog(pamh, LOG_ERR, "pam_set_data failed");
193 		return (NULL);
194 	}
195 	return (pw);
196 }
197 
198 static const pw_password_t *
pw_get(pam_handle_t * pamh,int tok,const char * var_name)199 pw_get(pam_handle_t *pamh, int tok, const char *var_name)
200 {
201 	const pw_password_t *authtok = NULL;
202 	int ret = pam_get_data(pamh, var_name,
203 	    (const void**)(&authtok));
204 	if (ret == PAM_SUCCESS)
205 		return (authtok);
206 	if (ret == PAM_NO_MODULE_DATA)
207 		return (pw_fetch_lazy(pamh, tok, var_name));
208 	pam_syslog(pamh, LOG_ERR, "password not available");
209 	return (NULL);
210 }
211 
212 static int
pw_clear(pam_handle_t * pamh,const char * var_name)213 pw_clear(pam_handle_t *pamh, const char *var_name)
214 {
215 	int ret = pam_set_data(pamh, var_name, NULL, NULL);
216 	if (ret != PAM_SUCCESS) {
217 		pam_syslog(pamh, LOG_ERR, "clearing password failed");
218 		return (-1);
219 	}
220 	return (0);
221 }
222 
223 static void
destroy_pw(pam_handle_t * pamh,void * data,int errcode)224 destroy_pw(pam_handle_t *pamh, void *data, int errcode)
225 {
226 	(void) pamh, (void) errcode;
227 
228 	if (data != NULL) {
229 		pw_free((pw_password_t *)data);
230 	}
231 }
232 
233 static int
pam_zfs_init(pam_handle_t * pamh)234 pam_zfs_init(pam_handle_t *pamh)
235 {
236 	int error = 0;
237 	if ((g_zfs = libzfs_init()) == NULL) {
238 		error = errno;
239 		pam_syslog(pamh, LOG_ERR, "Zfs initialization error: %s",
240 		    libzfs_error_init(error));
241 	}
242 	return (error);
243 }
244 
245 static void
pam_zfs_free(void)246 pam_zfs_free(void)
247 {
248 	libzfs_fini(g_zfs);
249 }
250 
251 static pw_password_t *
prepare_passphrase(pam_handle_t * pamh,zfs_handle_t * ds,const char * passphrase,nvlist_t * nvlist)252 prepare_passphrase(pam_handle_t *pamh, zfs_handle_t *ds,
253     const char *passphrase, nvlist_t *nvlist)
254 {
255 	pw_password_t *key = alloc_pw_size(WRAPPING_KEY_LEN);
256 	if (!key) {
257 		return (NULL);
258 	}
259 	uint64_t salt;
260 	uint64_t iters;
261 	if (nvlist != NULL) {
262 		int fd = open("/dev/urandom", O_RDONLY);
263 		if (fd < 0) {
264 			pw_free(key);
265 			return (NULL);
266 		}
267 		int bytes_read = 0;
268 		char *buf = (char *)&salt;
269 		size_t bytes = sizeof (uint64_t);
270 		while (bytes_read < bytes) {
271 			ssize_t len = read(fd, buf + bytes_read, bytes
272 			    - bytes_read);
273 			if (len < 0) {
274 				close(fd);
275 				pw_free(key);
276 				return (NULL);
277 			}
278 			bytes_read += len;
279 		}
280 		close(fd);
281 
282 		if (nvlist_add_uint64(nvlist,
283 		    zfs_prop_to_name(ZFS_PROP_PBKDF2_SALT), salt)) {
284 			pam_syslog(pamh, LOG_ERR,
285 			    "failed to add salt to nvlist");
286 			pw_free(key);
287 			return (NULL);
288 		}
289 		iters = DEFAULT_PBKDF2_ITERATIONS;
290 		if (nvlist_add_uint64(nvlist, zfs_prop_to_name(
291 		    ZFS_PROP_PBKDF2_ITERS), iters)) {
292 			pam_syslog(pamh, LOG_ERR,
293 			    "failed to add iters to nvlist");
294 			pw_free(key);
295 			return (NULL);
296 		}
297 	} else {
298 		salt = zfs_prop_get_int(ds, ZFS_PROP_PBKDF2_SALT);
299 		iters = zfs_prop_get_int(ds, ZFS_PROP_PBKDF2_ITERS);
300 	}
301 
302 	salt = LE_64(salt);
303 	if (!PKCS5_PBKDF2_HMAC_SHA1((char *)passphrase,
304 	    strlen(passphrase), (uint8_t *)&salt,
305 	    sizeof (uint64_t), iters, WRAPPING_KEY_LEN,
306 	    (uint8_t *)key->value)) {
307 		pam_syslog(pamh, LOG_ERR, "pbkdf failed");
308 		pw_free(key);
309 		return (NULL);
310 	}
311 	return (key);
312 }
313 
314 static int
is_key_loaded(pam_handle_t * pamh,const char * ds_name)315 is_key_loaded(pam_handle_t *pamh, const char *ds_name)
316 {
317 	zfs_handle_t *ds = zfs_open(g_zfs, ds_name, ZFS_TYPE_FILESYSTEM);
318 	if (ds == NULL) {
319 		pam_syslog(pamh, LOG_ERR, "dataset %s not found", ds_name);
320 		return (-1);
321 	}
322 	int keystatus = zfs_prop_get_int(ds, ZFS_PROP_KEYSTATUS);
323 	zfs_close(ds);
324 	return (keystatus != ZFS_KEYSTATUS_UNAVAILABLE);
325 }
326 
327 static int
change_key(pam_handle_t * pamh,const char * ds_name,const char * passphrase)328 change_key(pam_handle_t *pamh, const char *ds_name,
329     const char *passphrase)
330 {
331 	zfs_handle_t *ds = zfs_open(g_zfs, ds_name, ZFS_TYPE_FILESYSTEM);
332 	if (ds == NULL) {
333 		pam_syslog(pamh, LOG_ERR, "dataset %s not found", ds_name);
334 		return (-1);
335 	}
336 	nvlist_t *nvlist = fnvlist_alloc();
337 	pw_password_t *key = prepare_passphrase(pamh, ds, passphrase, nvlist);
338 	if (key == NULL) {
339 		nvlist_free(nvlist);
340 		zfs_close(ds);
341 		return (-1);
342 	}
343 	if (nvlist_add_string(nvlist,
344 	    zfs_prop_to_name(ZFS_PROP_KEYLOCATION),
345 	    "prompt")) {
346 		pam_syslog(pamh, LOG_ERR, "nvlist_add failed for keylocation");
347 		pw_free(key);
348 		nvlist_free(nvlist);
349 		zfs_close(ds);
350 		return (-1);
351 	}
352 	if (nvlist_add_uint64(nvlist,
353 	    zfs_prop_to_name(ZFS_PROP_KEYFORMAT),
354 	    ZFS_KEYFORMAT_PASSPHRASE)) {
355 		pam_syslog(pamh, LOG_ERR, "nvlist_add failed for keyformat");
356 		pw_free(key);
357 		nvlist_free(nvlist);
358 		zfs_close(ds);
359 		return (-1);
360 	}
361 	int ret = lzc_change_key(ds_name, DCP_CMD_NEW_KEY, nvlist,
362 	    (uint8_t *)key->value, WRAPPING_KEY_LEN);
363 	pw_free(key);
364 	if (ret) {
365 		pam_syslog(pamh, LOG_ERR, "change_key failed: %d", ret);
366 		nvlist_free(nvlist);
367 		zfs_close(ds);
368 		return (-1);
369 	}
370 	nvlist_free(nvlist);
371 	zfs_close(ds);
372 	return (0);
373 }
374 
375 typedef struct {
376 	char *homes_prefix;
377 	char *runstatedir;
378 	char *homedir;
379 	char *dsname;
380 	uid_t uid_min;
381 	uid_t uid_max;
382 	uid_t uid;
383 	const char *username;
384 	boolean_t unmount_and_unload;
385 	boolean_t force_unmount;
386 	boolean_t recursive_homes;
387 	boolean_t mount_recursively;
388 } zfs_key_config_t;
389 
390 static int
zfs_key_config_load(pam_handle_t * pamh,zfs_key_config_t * config,int argc,const char ** argv)391 zfs_key_config_load(pam_handle_t *pamh, zfs_key_config_t *config,
392     int argc, const char **argv)
393 {
394 #if	defined(__FreeBSD__)
395 	config->homes_prefix = strdup("zroot/home");
396 #else
397 	config->homes_prefix = strdup("rpool/home");
398 #endif
399 	if (config->homes_prefix == NULL) {
400 		pam_syslog(pamh, LOG_ERR, "strdup failure");
401 		return (PAM_SERVICE_ERR);
402 	}
403 	config->runstatedir = strdup(RUNSTATEDIR "/pam_zfs_key");
404 	if (config->runstatedir == NULL) {
405 		pam_syslog(pamh, LOG_ERR, "strdup failure");
406 		free(config->homes_prefix);
407 		return (PAM_SERVICE_ERR);
408 	}
409 	const char *name;
410 	if (pam_get_user(pamh, &name, NULL) != PAM_SUCCESS) {
411 		pam_syslog(pamh, LOG_ERR,
412 		    "couldn't get username from PAM stack");
413 		free(config->runstatedir);
414 		free(config->homes_prefix);
415 		return (PAM_SERVICE_ERR);
416 	}
417 	struct passwd *entry = getpwnam(name);
418 	if (!entry) {
419 		free(config->runstatedir);
420 		free(config->homes_prefix);
421 		return (PAM_USER_UNKNOWN);
422 	}
423 	config->uid_min = 1000;
424 	config->uid_max = MAXUID;
425 	config->uid = entry->pw_uid;
426 	config->username = name;
427 	config->unmount_and_unload = B_TRUE;
428 	config->force_unmount = B_FALSE;
429 	config->recursive_homes = B_FALSE;
430 	config->mount_recursively = B_FALSE;
431 	config->dsname = NULL;
432 	config->homedir = NULL;
433 	for (int c = 0; c < argc; c++) {
434 		if (strncmp(argv[c], "homes=", 6) == 0) {
435 			free(config->homes_prefix);
436 			config->homes_prefix = strdup(argv[c] + 6);
437 		} else if (strncmp(argv[c], "runstatedir=", 12) == 0) {
438 			free(config->runstatedir);
439 			config->runstatedir = strdup(argv[c] + 12);
440 		} else if (strncmp(argv[c], "uid_min=", 8) == 0) {
441 			sscanf(argv[c] + 8, "%u", &config->uid_min);
442 		} else if (strncmp(argv[c], "uid_max=", 8) == 0) {
443 			sscanf(argv[c] + 8, "%u", &config->uid_max);
444 		} else if (strcmp(argv[c], "nounmount") == 0) {
445 			config->unmount_and_unload = B_FALSE;
446 		} else if (strcmp(argv[c], "forceunmount") == 0) {
447 			config->force_unmount = B_TRUE;
448 		} else if (strcmp(argv[c], "recursive_homes") == 0) {
449 			config->recursive_homes = B_TRUE;
450 		} else if (strcmp(argv[c], "mount_recursively") == 0) {
451 			config->mount_recursively = B_TRUE;
452 		} else if (strcmp(argv[c], "prop_mountpoint") == 0) {
453 			if (config->homedir == NULL)
454 				config->homedir = strdup(entry->pw_dir);
455 		}
456 	}
457 	return (PAM_SUCCESS);
458 }
459 
460 typedef struct {
461 	pam_handle_t *pamh;
462 	zfs_key_config_t *target;
463 } mount_umount_dataset_data_t;
464 
465 static int
mount_dataset(zfs_handle_t * zhp,void * data)466 mount_dataset(zfs_handle_t *zhp, void *data)
467 {
468 	mount_umount_dataset_data_t *mount_umount_dataset_data = data;
469 
470 	zfs_key_config_t *target = mount_umount_dataset_data->target;
471 	pam_handle_t *pamh = mount_umount_dataset_data->pamh;
472 
473 	/* Refresh properties to get the latest key status */
474 	zfs_refresh_properties(zhp);
475 
476 	int ret = 0;
477 
478 	/* Check if dataset type is filesystem */
479 	if (zhp->zfs_type != ZFS_TYPE_FILESYSTEM) {
480 		pam_syslog(pamh, LOG_DEBUG,
481 		    "dataset is not filesystem: %s, skipping.",
482 		    zfs_get_name(zhp));
483 		return (0);
484 	}
485 
486 	/* Check if encryption key is available */
487 	if (zfs_prop_get_int(zhp, ZFS_PROP_KEYSTATUS) ==
488 	    ZFS_KEYSTATUS_UNAVAILABLE) {
489 		pam_syslog(pamh, LOG_WARNING,
490 		    "key unavailable for: %s, skipping",
491 		    zfs_get_name(zhp));
492 		return (0);
493 	}
494 
495 	/* Check if prop canmount is on */
496 	if (zfs_prop_get_int(zhp, ZFS_PROP_CANMOUNT) != ZFS_CANMOUNT_ON) {
497 		pam_syslog(pamh, LOG_INFO,
498 		    "canmount is not on for: %s, skipping",
499 		    zfs_get_name(zhp));
500 		return (0);
501 	}
502 
503 	/* Get mountpoint prop for check */
504 	char mountpoint[ZFS_MAXPROPLEN];
505 	if ((ret = zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT, mountpoint,
506 	    sizeof (mountpoint), NULL, NULL, 0, 1)) != 0) {
507 		pam_syslog(pamh, LOG_ERR,
508 		    "failed to get mountpoint prop: %d", ret);
509 		return (-1);
510 	}
511 
512 	/* Check if mountpoint isn't none or legacy */
513 	if (strcmp(mountpoint, ZFS_MOUNTPOINT_NONE) == 0 ||
514 	    strcmp(mountpoint, ZFS_MOUNTPOINT_LEGACY) == 0) {
515 		pam_syslog(pamh, LOG_INFO,
516 		    "mountpoint is none or legacy for: %s, skipping",
517 		    zfs_get_name(zhp));
518 		return (0);
519 	}
520 
521 	/* Don't mount the dataset if already mounted */
522 	if (zfs_is_mounted(zhp, NULL)) {
523 		pam_syslog(pamh, LOG_INFO, "already mounted: %s",
524 		    zfs_get_name(zhp));
525 		return (0);
526 	}
527 
528 	/* Mount the dataset */
529 	ret = zfs_mount(zhp, NULL, 0);
530 	if (ret) {
531 		pam_syslog(pamh, LOG_ERR,
532 		    "zfs_mount failed for %s with: %d", zfs_get_name(zhp),
533 		    ret);
534 		return (ret);
535 	}
536 
537 	/* Recursively mount children if the recursive flag is set */
538 	if (target->mount_recursively) {
539 		ret = zfs_iter_filesystems_v2(zhp, 0, mount_dataset, data);
540 		if (ret != 0) {
541 			pam_syslog(pamh, LOG_ERR,
542 			    "child iteration failed: %d", ret);
543 			return (-1);
544 		}
545 	}
546 
547 	return (ret);
548 }
549 
550 static int
umount_dataset(zfs_handle_t * zhp,void * data)551 umount_dataset(zfs_handle_t *zhp, void *data)
552 {
553 	mount_umount_dataset_data_t *mount_umount_dataset_data = data;
554 
555 	zfs_key_config_t *target = mount_umount_dataset_data->target;
556 	pam_handle_t *pamh = mount_umount_dataset_data->pamh;
557 
558 	int ret = 0;
559 	/* Recursively umount children if the recursive flag is set */
560 	if (target->mount_recursively) {
561 		ret = zfs_iter_filesystems_v2(zhp, 0, umount_dataset, data);
562 		if (ret != 0) {
563 			pam_syslog(pamh, LOG_ERR,
564 			    "child iteration failed: %d", ret);
565 			return (-1);
566 		}
567 	}
568 
569 	/* Check if dataset type is filesystem */
570 	if (zhp->zfs_type != ZFS_TYPE_FILESYSTEM) {
571 		pam_syslog(pamh, LOG_DEBUG,
572 		    "dataset is not filesystem: %s, skipping",
573 		    zfs_get_name(zhp));
574 		return (0);
575 	}
576 
577 	/* Don't umount the dataset if already unmounted */
578 	if (zfs_is_mounted(zhp, NULL) == 0) {
579 		pam_syslog(pamh, LOG_INFO, "already unmounted: %s",
580 		    zfs_get_name(zhp));
581 		return (0);
582 	}
583 
584 	/* Unmount the dataset */
585 	ret = zfs_unmount(zhp, NULL, target->force_unmount ? MS_FORCE : 0);
586 	if (ret) {
587 		pam_syslog(pamh, LOG_ERR,
588 		    "zfs_unmount failed for %s with: %d", zfs_get_name(zhp),
589 		    ret);
590 		return (ret);
591 	}
592 
593 	return (ret);
594 }
595 
596 static int
decrypt_mount(pam_handle_t * pamh,zfs_key_config_t * config,const char * ds_name,const char * passphrase,boolean_t noop)597 decrypt_mount(pam_handle_t *pamh, zfs_key_config_t *config, const char *ds_name,
598 	const char *passphrase, boolean_t noop)
599 {
600 	zfs_handle_t *ds = zfs_open(g_zfs, ds_name, ZFS_TYPE_FILESYSTEM);
601 	if (ds == NULL) {
602 		pam_syslog(pamh, LOG_ERR, "dataset %s not found", ds_name);
603 		return (-1);
604 	}
605 	pw_password_t *key = prepare_passphrase(pamh, ds, passphrase, NULL);
606 	if (key == NULL) {
607 		zfs_close(ds);
608 		return (-1);
609 	}
610 	int ret = lzc_load_key(ds_name, noop, (uint8_t *)key->value,
611 	    WRAPPING_KEY_LEN);
612 	pw_free(key);
613 	if (ret && ret != EEXIST) {
614 		pam_syslog(pamh, LOG_ERR, "load_key failed: %d", ret);
615 		zfs_close(ds);
616 		return (-1);
617 	}
618 
619 	if (noop) {
620 		zfs_close(ds);
621 		return (0);
622 	}
623 
624 	mount_umount_dataset_data_t data;
625 	data.pamh = pamh;
626 	data.target = config;
627 
628 	ret = mount_dataset(ds, &data);
629 	if (ret != 0) {
630 		pam_syslog(pamh, LOG_ERR, "mount failed: %d", ret);
631 		zfs_close(ds);
632 		return (-1);
633 	}
634 
635 	zfs_close(ds);
636 	return (0);
637 }
638 
639 static int
unmount_unload(pam_handle_t * pamh,const char * ds_name,zfs_key_config_t * target)640 unmount_unload(pam_handle_t *pamh, const char *ds_name,
641     zfs_key_config_t *target)
642 {
643 	zfs_handle_t *ds = zfs_open(g_zfs, ds_name, ZFS_TYPE_FILESYSTEM);
644 	if (ds == NULL) {
645 		pam_syslog(pamh, LOG_ERR, "dataset %s not found", ds_name);
646 		return (-1);
647 	}
648 
649 	mount_umount_dataset_data_t data;
650 	data.pamh = pamh;
651 	data.target = target;
652 
653 	int ret = umount_dataset(ds, &data);
654 	if (ret) {
655 		pam_syslog(pamh, LOG_ERR,
656 		    "unmount_dataset failed with: %d", ret);
657 		zfs_close(ds);
658 		return (-1);
659 	}
660 
661 	ret = lzc_unload_key(ds_name);
662 	if (ret) {
663 		pam_syslog(pamh, LOG_ERR, "unload_key failed with: %d", ret);
664 		zfs_close(ds);
665 		return (-1);
666 	}
667 	zfs_close(ds);
668 	return (0);
669 }
670 
671 static void
zfs_key_config_free(zfs_key_config_t * config)672 zfs_key_config_free(zfs_key_config_t *config)
673 {
674 	free(config->homes_prefix);
675 	free(config->runstatedir);
676 	free(config->homedir);
677 	free(config->dsname);
678 }
679 
680 static int
find_dsname_by_prop_value(zfs_handle_t * zhp,void * data)681 find_dsname_by_prop_value(zfs_handle_t *zhp, void *data)
682 {
683 	zfs_type_t type = zfs_get_type(zhp);
684 	zfs_key_config_t *target = data;
685 	char mountpoint[ZFS_MAXPROPLEN];
686 
687 	/* Skip any datasets whose type does not match */
688 	if ((type & ZFS_TYPE_FILESYSTEM) == 0) {
689 		zfs_close(zhp);
690 		return (0);
691 	}
692 
693 	/* Skip any datasets whose mountpoint does not match */
694 	(void) zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT, mountpoint,
695 	    sizeof (mountpoint), NULL, NULL, 0, B_FALSE);
696 	if (strcmp(target->homedir, mountpoint) != 0) {
697 		if (target->recursive_homes) {
698 			(void) zfs_iter_filesystems_v2(zhp, 0,
699 			    find_dsname_by_prop_value, target);
700 		}
701 		zfs_close(zhp);
702 		return (target->dsname != NULL);
703 	}
704 
705 	target->dsname = strdup(zfs_get_name(zhp));
706 	zfs_close(zhp);
707 	return (1);
708 }
709 
710 static char *
zfs_key_config_get_dataset(pam_handle_t * pamh,zfs_key_config_t * config)711 zfs_key_config_get_dataset(pam_handle_t *pamh, zfs_key_config_t *config)
712 {
713 	if (config->homedir != NULL &&
714 	    config->homes_prefix != NULL) {
715 		if (strcmp(config->homes_prefix, "*") == 0) {
716 			(void) zfs_iter_root(g_zfs,
717 			    find_dsname_by_prop_value, config);
718 		} else {
719 			zfs_handle_t *zhp = zfs_open(g_zfs,
720 			    config->homes_prefix, ZFS_TYPE_FILESYSTEM);
721 			if (zhp == NULL) {
722 				pam_syslog(pamh, LOG_ERR,
723 				    "dataset %s not found",
724 				    config->homes_prefix);
725 				return (NULL);
726 			}
727 
728 			(void) zfs_iter_filesystems_v2(zhp, 0,
729 			    find_dsname_by_prop_value, config);
730 			zfs_close(zhp);
731 		}
732 		char *dsname = config->dsname;
733 		config->dsname = NULL;
734 		return (dsname);
735 	}
736 
737 	if (config->homes_prefix == NULL) {
738 		return (NULL);
739 	}
740 
741 	size_t len = ZFS_MAX_DATASET_NAME_LEN;
742 	size_t total_len = strlen(config->homes_prefix) + 1
743 	    + strlen(config->username);
744 	if (total_len > len) {
745 		return (NULL);
746 	}
747 	char *ret = malloc(len + 1);
748 	if (!ret) {
749 		return (NULL);
750 	}
751 	ret[0] = 0;
752 	(void) snprintf(ret, len + 1, "%s/%s", config->homes_prefix,
753 	    config->username);
754 	return (ret);
755 }
756 
757 /*
758  * Callback type for foreach_dataset.
759  * Returns 0 on success, -1 on failure.
760  */
761 typedef int (*dataset_callback_t)(pam_handle_t *, zfs_key_config_t *,
762     const char *, void *);
763 
764 /*
765  * Iterate over comma-separated homes prefixes and call callback for each
766  * existing dataset. Returns number of successful callbacks, or -1 if none
767  * succeeded.
768  */
769 static int
foreach_dataset(pam_handle_t * pamh,zfs_key_config_t * config,dataset_callback_t callback,void * data)770 foreach_dataset(pam_handle_t *pamh, zfs_key_config_t *config,
771     dataset_callback_t callback, void *data)
772 {
773 	if (config->homes_prefix == NULL)
774 		return (-1);
775 
776 	/* Check if this is a comma-separated list */
777 	if (strchr(config->homes_prefix, ',') == NULL) {
778 		/* Single home - use existing logic */
779 		char *dataset = zfs_key_config_get_dataset(pamh, config);
780 		if (dataset == NULL)
781 			return (-1);
782 		int ret = callback(pamh, config, dataset, data);
783 		free(dataset);
784 		return (ret == 0 ? 1 : -1);
785 	}
786 
787 	/* Multiple homes - parse and iterate */
788 	pam_syslog(pamh, LOG_DEBUG,
789 	    "processing multiple home prefixes: %s", config->homes_prefix);
790 
791 	char *homes_copy = strdup(config->homes_prefix);
792 	if (homes_copy == NULL)
793 		return (-1);
794 
795 	char *saved_prefix = config->homes_prefix;
796 	char *saveptr;
797 	char *token = strtok_r(homes_copy, ",", &saveptr);
798 	int success_count = 0;
799 	boolean_t failed = B_FALSE;
800 
801 	while (token != NULL) {
802 		/* Temporarily set homes_prefix to this single prefix */
803 		config->homes_prefix = token;
804 		char *dataset = zfs_key_config_get_dataset(pamh, config);
805 		if (dataset != NULL) {
806 			pam_syslog(pamh, LOG_DEBUG,
807 			    "processing dataset '%s' for prefix '%s'",
808 			    dataset, token);
809 			if (callback(pamh, config, dataset, data) == 0) {
810 				success_count++;
811 			} else {
812 				failed = B_TRUE;
813 				pam_syslog(pamh, LOG_WARNING,
814 				    "operation failed for dataset '%s'",
815 				    dataset);
816 			}
817 			free(dataset);
818 		} else {
819 			pam_syslog(pamh, LOG_DEBUG,
820 			    "no dataset found for prefix '%s', skip", token);
821 		}
822 		token = strtok_r(NULL, ",", &saveptr);
823 	}
824 
825 	config->homes_prefix = saved_prefix;
826 	free(homes_copy);
827 	pam_syslog(pamh, LOG_DEBUG,
828 	    "processed %d datasets, %s",
829 	    success_count, failed ? "with failures" : "all successful");
830 	return (!failed && success_count > 0 ? success_count : -1);
831 }
832 
833 static int
zfs_key_config_modify_session_counter(pam_handle_t * pamh,zfs_key_config_t * config,int delta)834 zfs_key_config_modify_session_counter(pam_handle_t *pamh,
835     zfs_key_config_t *config, int delta)
836 {
837 	const char *runtime_path = config->runstatedir;
838 	if (mkdir(runtime_path, S_IRWXU) != 0 && errno != EEXIST) {
839 		pam_syslog(pamh, LOG_ERR, "Can't create runtime path: %d",
840 		    errno);
841 		return (-1);
842 	}
843 	if (chown(runtime_path, 0, 0) != 0) {
844 		pam_syslog(pamh, LOG_ERR, "Can't chown runtime path: %d",
845 		    errno);
846 		return (-1);
847 	}
848 	if (chmod(runtime_path, S_IRWXU) != 0) {
849 		pam_syslog(pamh, LOG_ERR, "Can't chmod runtime path: %d",
850 		    errno);
851 		return (-1);
852 	}
853 
854 	char *counter_path;
855 	if (asprintf(&counter_path, "%s/%u", runtime_path, config->uid) == -1)
856 		return (-1);
857 
858 	const int fd = open(counter_path,
859 	    O_RDWR | O_CLOEXEC | O_CREAT | O_NOFOLLOW,
860 	    S_IRUSR | S_IWUSR);
861 	free(counter_path);
862 	if (fd < 0) {
863 		pam_syslog(pamh, LOG_ERR, "Can't open counter file: %d", errno);
864 		return (-1);
865 	}
866 	if (flock(fd, LOCK_EX) != 0) {
867 		pam_syslog(pamh, LOG_ERR, "Can't lock counter file: %d", errno);
868 		close(fd);
869 		return (-1);
870 	}
871 	char counter[20];
872 	char *pos = counter;
873 	int remaining = sizeof (counter) - 1;
874 	int ret;
875 	counter[sizeof (counter) - 1] = 0;
876 	while (remaining > 0 && (ret = read(fd, pos, remaining)) > 0) {
877 		remaining -= ret;
878 		pos += ret;
879 	}
880 	*pos = 0;
881 	long int counter_value = strtol(counter, NULL, 10);
882 	counter_value += delta;
883 	if (counter_value < 0) {
884 		counter_value = 0;
885 	}
886 	lseek(fd, 0, SEEK_SET);
887 	if (ftruncate(fd, 0) != 0) {
888 		pam_syslog(pamh, LOG_ERR, "Can't truncate counter file: %d",
889 		    errno);
890 		close(fd);
891 		return (-1);
892 	}
893 	snprintf(counter, sizeof (counter), "%ld", counter_value);
894 	remaining = strlen(counter);
895 	pos = counter;
896 	while (remaining > 0 && (ret = write(fd, pos, remaining)) > 0) {
897 		remaining -= ret;
898 		pos += ret;
899 	}
900 	close(fd);
901 	return (counter_value);
902 }
903 
904 /* Callback for authentication - verify password works (noop mode) */
905 static int
auth_callback(pam_handle_t * pamh,zfs_key_config_t * config,const char * dataset,void * data)906 auth_callback(pam_handle_t *pamh, zfs_key_config_t *config,
907     const char *dataset, void *data)
908 {
909 	const char *passphrase = data;
910 	return (decrypt_mount(pamh, config, dataset, passphrase, B_TRUE));
911 }
912 
913 __attribute__((visibility("default")))
914 PAM_EXTERN int
pam_sm_authenticate(pam_handle_t * pamh,int flags,int argc,const char ** argv)915 pam_sm_authenticate(pam_handle_t *pamh, int flags,
916     int argc, const char **argv)
917 {
918 	(void) flags;
919 
920 	if (geteuid() != 0) {
921 		pam_syslog(pamh, LOG_ERR,
922 		    "Cannot zfs_mount when not being root.");
923 		return (PAM_SERVICE_ERR);
924 	}
925 	zfs_key_config_t config;
926 	int config_err = zfs_key_config_load(pamh, &config, argc, argv);
927 	if (config_err != PAM_SUCCESS) {
928 		return (config_err);
929 	}
930 	if (config.uid < config.uid_min || config.uid > config.uid_max) {
931 		zfs_key_config_free(&config);
932 		return (PAM_SERVICE_ERR);
933 	}
934 
935 	const pw_password_t *token = pw_fetch_lazy(pamh,
936 	    PAM_AUTHTOK, PASSWORD_VAR_NAME);
937 	if (token == NULL) {
938 		zfs_key_config_free(&config);
939 		return (PAM_AUTH_ERR);
940 	}
941 	if (pam_zfs_init(pamh) != 0) {
942 		zfs_key_config_free(&config);
943 		return (PAM_SERVICE_ERR);
944 	}
945 
946 	int ret = foreach_dataset(pamh, &config, auth_callback,
947 	    (void *)token->value);
948 	pam_zfs_free();
949 	zfs_key_config_free(&config);
950 	if (ret < 0) {
951 		return (PAM_AUTH_ERR);
952 	}
953 	return (PAM_SUCCESS);
954 }
955 
956 __attribute__((visibility("default")))
957 PAM_EXTERN int
pam_sm_setcred(pam_handle_t * pamh,int flags,int argc,const char ** argv)958 pam_sm_setcred(pam_handle_t *pamh, int flags,
959     int argc, const char **argv)
960 {
961 	(void) pamh, (void) flags, (void) argc, (void) argv;
962 	return (PAM_SUCCESS);
963 }
964 
965 /* Context for password change callback */
966 typedef struct {
967 	const char *old_pass;
968 	const char *new_pass;
969 } chauthtok_ctx_t;
970 
971 /* Callback for password change */
972 static int
chauthtok_callback(pam_handle_t * pamh,zfs_key_config_t * config,const char * dataset,void * data)973 chauthtok_callback(pam_handle_t *pamh, zfs_key_config_t *config,
974     const char *dataset, void *data)
975 {
976 	chauthtok_ctx_t *ctx = data;
977 	int was_loaded = is_key_loaded(pamh, dataset);
978 	if (!was_loaded) {
979 		int ret = decrypt_mount(pamh, config, dataset,
980 		    ctx->old_pass, B_FALSE);
981 		if (ret == -1) {
982 			pam_syslog(pamh, LOG_ERR,
983 			    "failed to load key for '%s' during "
984 			    "password change", dataset);
985 			return (-1);
986 		}
987 	}
988 	int ret = change_key(pamh, dataset, ctx->new_pass);
989 	if (ret == -1) {
990 		pam_syslog(pamh, LOG_ERR,
991 		    "failed to change key for dataset '%s'", dataset);
992 	}
993 	if (!was_loaded)
994 		unmount_unload(pamh, dataset, config);
995 	return (ret);
996 }
997 
998 __attribute__((visibility("default")))
999 PAM_EXTERN int
pam_sm_chauthtok(pam_handle_t * pamh,int flags,int argc,const char ** argv)1000 pam_sm_chauthtok(pam_handle_t *pamh, int flags,
1001     int argc, const char **argv)
1002 {
1003 	if (geteuid() != 0) {
1004 		pam_syslog(pamh, LOG_ERR,
1005 		    "Cannot zfs_mount when not being root.");
1006 		return (PAM_PERM_DENIED);
1007 	}
1008 	zfs_key_config_t config;
1009 	if (zfs_key_config_load(pamh, &config, argc, argv) != PAM_SUCCESS) {
1010 		return (PAM_SERVICE_ERR);
1011 	}
1012 	if (config.uid < config.uid_min || config.uid > config.uid_max) {
1013 		zfs_key_config_free(&config);
1014 		return (PAM_SERVICE_ERR);
1015 	}
1016 	const pw_password_t *old_token = pw_get(pamh,
1017 	    PAM_OLDAUTHTOK, OLD_PASSWORD_VAR_NAME);
1018 
1019 	if (!old_token) {
1020 		pam_syslog(pamh, LOG_ERR,
1021 		    "old password from PAM stack is null");
1022 		zfs_key_config_free(&config);
1023 		return (PAM_SERVICE_ERR);
1024 	}
1025 
1026 	if (pam_zfs_init(pamh) != 0) {
1027 		zfs_key_config_free(&config);
1028 		return (PAM_SERVICE_ERR);
1029 	}
1030 
1031 	/* First verify old password works for all datasets */
1032 	int ret = foreach_dataset(pamh, &config, auth_callback,
1033 	    (void *)old_token->value);
1034 	if (ret < 0) {
1035 		pam_syslog(pamh, LOG_ERR, "old token mismatch");
1036 		pam_zfs_free();
1037 		zfs_key_config_free(&config);
1038 		return (PAM_PERM_DENIED);
1039 	}
1040 
1041 	if ((flags & PAM_UPDATE_AUTHTOK) != 0) {
1042 		const pw_password_t *token = pw_get(pamh, PAM_AUTHTOK,
1043 		    PASSWORD_VAR_NAME);
1044 		if (token == NULL) {
1045 			pam_syslog(pamh, LOG_ERR, "new password unavailable");
1046 			pam_zfs_free();
1047 			zfs_key_config_free(&config);
1048 			pw_clear(pamh, OLD_PASSWORD_VAR_NAME);
1049 			return (PAM_SERVICE_ERR);
1050 		}
1051 
1052 		chauthtok_ctx_t ctx = {
1053 			.old_pass = old_token->value,
1054 			.new_pass = token->value
1055 		};
1056 
1057 		ret = foreach_dataset(pamh, &config, chauthtok_callback, &ctx);
1058 		pam_zfs_free();
1059 		zfs_key_config_free(&config);
1060 
1061 		if (ret < 0) {
1062 			pw_clear(pamh, OLD_PASSWORD_VAR_NAME);
1063 			pw_clear(pamh, PASSWORD_VAR_NAME);
1064 			return (PAM_SERVICE_ERR);
1065 		}
1066 
1067 		if (pw_clear(pamh, OLD_PASSWORD_VAR_NAME) == -1 ||
1068 		    pw_clear(pamh, PASSWORD_VAR_NAME) == -1) {
1069 			return (PAM_SERVICE_ERR);
1070 		}
1071 	} else {
1072 		pam_zfs_free();
1073 		zfs_key_config_free(&config);
1074 	}
1075 	return (PAM_SUCCESS);
1076 }
1077 
1078 /* Callback for session open - decrypt and mount */
1079 static int
open_session_callback(pam_handle_t * pamh,zfs_key_config_t * config,const char * dataset,void * data)1080 open_session_callback(pam_handle_t *pamh, zfs_key_config_t *config,
1081     const char *dataset, void *data)
1082 {
1083 	const char *passphrase = data;
1084 	return (decrypt_mount(pamh, config, dataset, passphrase, B_FALSE));
1085 }
1086 
1087 /* Callback for session close - unmount and unload */
1088 static int
close_session_callback(pam_handle_t * pamh,zfs_key_config_t * config,const char * dataset,void * data)1089 close_session_callback(pam_handle_t *pamh, zfs_key_config_t *config,
1090     const char *dataset, void *data)
1091 {
1092 	(void) data;
1093 	return (unmount_unload(pamh, dataset, config));
1094 }
1095 
1096 PAM_EXTERN int
pam_sm_open_session(pam_handle_t * pamh,int flags,int argc,const char ** argv)1097 pam_sm_open_session(pam_handle_t *pamh, int flags,
1098     int argc, const char **argv)
1099 {
1100 	(void) flags;
1101 
1102 	if (geteuid() != 0) {
1103 		pam_syslog(pamh, LOG_ERR,
1104 		    "Cannot zfs_mount when not being root.");
1105 		return (PAM_SUCCESS);
1106 	}
1107 	zfs_key_config_t config;
1108 	if (zfs_key_config_load(pamh, &config, argc, argv) != PAM_SUCCESS) {
1109 		return (PAM_SESSION_ERR);
1110 	}
1111 
1112 	if (config.uid < config.uid_min || config.uid > config.uid_max) {
1113 		zfs_key_config_free(&config);
1114 		return (PAM_SUCCESS);
1115 	}
1116 
1117 	int counter = zfs_key_config_modify_session_counter(pamh, &config, 1);
1118 	if (counter != 1) {
1119 		zfs_key_config_free(&config);
1120 		return (PAM_SUCCESS);
1121 	}
1122 
1123 	const pw_password_t *token = pw_get(pamh,
1124 	    PAM_AUTHTOK, PASSWORD_VAR_NAME);
1125 	if (token == NULL) {
1126 		zfs_key_config_free(&config);
1127 		return (PAM_SESSION_ERR);
1128 	}
1129 	if (pam_zfs_init(pamh) != 0) {
1130 		zfs_key_config_free(&config);
1131 		return (PAM_SERVICE_ERR);
1132 	}
1133 
1134 	int ret = foreach_dataset(pamh, &config, open_session_callback,
1135 	    (void *)token->value);
1136 	pam_zfs_free();
1137 	zfs_key_config_free(&config);
1138 
1139 	if (ret < 0) {
1140 		return (PAM_SERVICE_ERR);
1141 	}
1142 	if (pw_clear(pamh, PASSWORD_VAR_NAME) == -1) {
1143 		return (PAM_SERVICE_ERR);
1144 	}
1145 	return (PAM_SUCCESS);
1146 
1147 }
1148 
1149 __attribute__((visibility("default")))
1150 PAM_EXTERN int
pam_sm_close_session(pam_handle_t * pamh,int flags,int argc,const char ** argv)1151 pam_sm_close_session(pam_handle_t *pamh, int flags,
1152     int argc, const char **argv)
1153 {
1154 	(void) flags;
1155 
1156 	if (geteuid() != 0) {
1157 		pam_syslog(pamh, LOG_ERR,
1158 		    "Cannot zfs_mount when not being root.");
1159 		return (PAM_SUCCESS);
1160 	}
1161 	zfs_key_config_t config;
1162 	if (zfs_key_config_load(pamh, &config, argc, argv) != PAM_SUCCESS) {
1163 		return (PAM_SESSION_ERR);
1164 	}
1165 	if (config.uid < config.uid_min || config.uid > config.uid_max) {
1166 		zfs_key_config_free(&config);
1167 		return (PAM_SUCCESS);
1168 	}
1169 
1170 	int counter = zfs_key_config_modify_session_counter(pamh, &config, -1);
1171 	if (counter != 0) {
1172 		zfs_key_config_free(&config);
1173 		return (PAM_SUCCESS);
1174 	}
1175 
1176 	if (config.unmount_and_unload) {
1177 		if (pam_zfs_init(pamh) != 0) {
1178 			zfs_key_config_free(&config);
1179 			return (PAM_SERVICE_ERR);
1180 		}
1181 
1182 		int ret = foreach_dataset(pamh, &config,
1183 		    close_session_callback, NULL);
1184 		pam_zfs_free();
1185 
1186 		if (ret < 0) {
1187 			zfs_key_config_free(&config);
1188 			return (PAM_SESSION_ERR);
1189 		}
1190 	}
1191 
1192 	zfs_key_config_free(&config);
1193 	return (PAM_SUCCESS);
1194 }
1195