1 /*
2  * Redistribution and use in source and binary forms, with or without
3  * modification, are permitted provided that the following conditions are met:
4  *     * Redistributions of source code must retain the above copyright
5  *       notice, this list of conditions and the following disclaimer.
6  *     * Redistributions in binary form must reproduce the above copyright
7  *       notice, this list of conditions and the following disclaimer in the
8  *       documentation and/or other materials provided with the distribution.
9  *     * Neither the name of the <organization> nor the
10  *       names of its contributors may be used to endorse or promote products
11  *       derived from this software without specific prior written permission.
12  *
13  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16  * ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
17  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23  *
24  * Copyright (c) 2020, Felix Dörre
25  * All rights reserved.
26  */
27 
28 #include <sys/dsl_crypt.h>
29 #include <sys/byteorder.h>
30 #include <libzfs.h>
31 
32 #include <syslog.h>
33 
34 #include <sys/zio_crypt.h>
35 #include <openssl/evp.h>
36 
37 #define	PAM_SM_AUTH
38 #define	PAM_SM_PASSWORD
39 #define	PAM_SM_SESSION
40 #include <security/pam_modules.h>
41 
42 #if	defined(__linux__)
43 #include <security/pam_ext.h>
44 #elif	defined(__FreeBSD__)
45 #include <security/pam_appl.h>
46 static void
47 pam_syslog(pam_handle_t *pamh, int loglevel, const char *fmt, ...)
48 {
49 	va_list args;
50 	va_start(args, fmt);
51 	vsyslog(loglevel, fmt, args);
52 	va_end(args);
53 }
54 #endif
55 
56 #include <string.h>
57 
58 #include <fcntl.h>
59 #include <sys/stat.h>
60 #include <sys/file.h>
61 #include <sys/wait.h>
62 #include <pwd.h>
63 
64 #include <sys/mman.h>
65 
66 static const char PASSWORD_VAR_NAME[] = "pam_zfs_key_authtok";
67 
68 static libzfs_handle_t *g_zfs;
69 
70 static void destroy_pw(pam_handle_t *pamh, void *data, int errcode);
71 
72 typedef struct {
73 	size_t len;
74 	char *value;
75 } pw_password_t;
76 
77 static pw_password_t *
78 alloc_pw_size(size_t len)
79 {
80 	pw_password_t *pw = malloc(sizeof (pw_password_t));
81 	if (!pw) {
82 		return (NULL);
83 	}
84 	pw->len = len;
85 	/*
86 	 * The use of malloc() triggers a spurious gcc 11 -Wmaybe-uninitialized
87 	 * warning in the mlock() function call below, so use calloc().
88 	 */
89 	pw->value = calloc(len, 1);
90 	if (!pw->value) {
91 		free(pw);
92 		return (NULL);
93 	}
94 	mlock(pw->value, pw->len);
95 	return (pw);
96 }
97 
98 static pw_password_t *
99 alloc_pw_string(const char *source)
100 {
101 	pw_password_t *pw = malloc(sizeof (pw_password_t));
102 	if (!pw) {
103 		return (NULL);
104 	}
105 	pw->len = strlen(source) + 1;
106 	/*
107 	 * The use of malloc() triggers a spurious gcc 11 -Wmaybe-uninitialized
108 	 * warning in the mlock() function call below, so use calloc().
109 	 */
110 	pw->value = calloc(pw->len, 1);
111 	if (!pw->value) {
112 		free(pw);
113 		return (NULL);
114 	}
115 	mlock(pw->value, pw->len);
116 	memcpy(pw->value, source, pw->len);
117 	return (pw);
118 }
119 
120 static void
121 pw_free(pw_password_t *pw)
122 {
123 	bzero(pw->value, pw->len);
124 	munlock(pw->value, pw->len);
125 	free(pw->value);
126 	free(pw);
127 }
128 
129 static pw_password_t *
130 pw_fetch(pam_handle_t *pamh)
131 {
132 	const char *token;
133 	if (pam_get_authtok(pamh, PAM_AUTHTOK, &token, NULL) != PAM_SUCCESS) {
134 		pam_syslog(pamh, LOG_ERR,
135 		    "couldn't get password from PAM stack");
136 		return (NULL);
137 	}
138 	if (!token) {
139 		pam_syslog(pamh, LOG_ERR,
140 		    "token from PAM stack is null");
141 		return (NULL);
142 	}
143 	return (alloc_pw_string(token));
144 }
145 
146 static const pw_password_t *
147 pw_fetch_lazy(pam_handle_t *pamh)
148 {
149 	pw_password_t *pw = pw_fetch(pamh);
150 	if (pw == NULL) {
151 		return (NULL);
152 	}
153 	int ret = pam_set_data(pamh, PASSWORD_VAR_NAME, pw, destroy_pw);
154 	if (ret != PAM_SUCCESS) {
155 		pw_free(pw);
156 		pam_syslog(pamh, LOG_ERR, "pam_set_data failed");
157 		return (NULL);
158 	}
159 	return (pw);
160 }
161 
162 static const pw_password_t *
163 pw_get(pam_handle_t *pamh)
164 {
165 	const pw_password_t *authtok = NULL;
166 	int ret = pam_get_data(pamh, PASSWORD_VAR_NAME,
167 	    (const void**)(&authtok));
168 	if (ret == PAM_SUCCESS)
169 		return (authtok);
170 	if (ret == PAM_NO_MODULE_DATA)
171 		return (pw_fetch_lazy(pamh));
172 	pam_syslog(pamh, LOG_ERR, "password not available");
173 	return (NULL);
174 }
175 
176 static int
177 pw_clear(pam_handle_t *pamh)
178 {
179 	int ret = pam_set_data(pamh, PASSWORD_VAR_NAME, NULL, NULL);
180 	if (ret != PAM_SUCCESS) {
181 		pam_syslog(pamh, LOG_ERR, "clearing password failed");
182 		return (-1);
183 	}
184 	return (0);
185 }
186 
187 static void
188 destroy_pw(pam_handle_t *pamh, void *data, int errcode)
189 {
190 	if (data != NULL) {
191 		pw_free((pw_password_t *)data);
192 	}
193 }
194 
195 static int
196 pam_zfs_init(pam_handle_t *pamh)
197 {
198 	int error = 0;
199 	if ((g_zfs = libzfs_init()) == NULL) {
200 		error = errno;
201 		pam_syslog(pamh, LOG_ERR, "Zfs initialization error: %s",
202 		    libzfs_error_init(error));
203 	}
204 	return (error);
205 }
206 
207 static void
208 pam_zfs_free(void)
209 {
210 	libzfs_fini(g_zfs);
211 }
212 
213 static pw_password_t *
214 prepare_passphrase(pam_handle_t *pamh, zfs_handle_t *ds,
215     const char *passphrase, nvlist_t *nvlist)
216 {
217 	pw_password_t *key = alloc_pw_size(WRAPPING_KEY_LEN);
218 	if (!key) {
219 		return (NULL);
220 	}
221 	uint64_t salt;
222 	uint64_t iters;
223 	if (nvlist != NULL) {
224 		int fd = open("/dev/urandom", O_RDONLY);
225 		if (fd < 0) {
226 			pw_free(key);
227 			return (NULL);
228 		}
229 		int bytes_read = 0;
230 		char *buf = (char *)&salt;
231 		size_t bytes = sizeof (uint64_t);
232 		while (bytes_read < bytes) {
233 			ssize_t len = read(fd, buf + bytes_read, bytes
234 			    - bytes_read);
235 			if (len < 0) {
236 				close(fd);
237 				pw_free(key);
238 				return (NULL);
239 			}
240 			bytes_read += len;
241 		}
242 		close(fd);
243 
244 		if (nvlist_add_uint64(nvlist,
245 		    zfs_prop_to_name(ZFS_PROP_PBKDF2_SALT), salt)) {
246 			pam_syslog(pamh, LOG_ERR,
247 			    "failed to add salt to nvlist");
248 			pw_free(key);
249 			return (NULL);
250 		}
251 		iters = DEFAULT_PBKDF2_ITERATIONS;
252 		if (nvlist_add_uint64(nvlist, zfs_prop_to_name(
253 		    ZFS_PROP_PBKDF2_ITERS), iters)) {
254 			pam_syslog(pamh, LOG_ERR,
255 			    "failed to add iters to nvlist");
256 			pw_free(key);
257 			return (NULL);
258 		}
259 	} else {
260 		salt = zfs_prop_get_int(ds, ZFS_PROP_PBKDF2_SALT);
261 		iters = zfs_prop_get_int(ds, ZFS_PROP_PBKDF2_ITERS);
262 	}
263 
264 	salt = LE_64(salt);
265 	if (!PKCS5_PBKDF2_HMAC_SHA1((char *)passphrase,
266 	    strlen(passphrase), (uint8_t *)&salt,
267 	    sizeof (uint64_t), iters, WRAPPING_KEY_LEN,
268 	    (uint8_t *)key->value)) {
269 		pam_syslog(pamh, LOG_ERR, "pbkdf failed");
270 		pw_free(key);
271 		return (NULL);
272 	}
273 	return (key);
274 }
275 
276 static int
277 is_key_loaded(pam_handle_t *pamh, const char *ds_name)
278 {
279 	zfs_handle_t *ds = zfs_open(g_zfs, ds_name, ZFS_TYPE_FILESYSTEM);
280 	if (ds == NULL) {
281 		pam_syslog(pamh, LOG_ERR, "dataset %s not found", ds_name);
282 		return (-1);
283 	}
284 	int keystatus = zfs_prop_get_int(ds, ZFS_PROP_KEYSTATUS);
285 	zfs_close(ds);
286 	return (keystatus != ZFS_KEYSTATUS_UNAVAILABLE);
287 }
288 
289 static int
290 change_key(pam_handle_t *pamh, const char *ds_name,
291     const char *passphrase)
292 {
293 	zfs_handle_t *ds = zfs_open(g_zfs, ds_name, ZFS_TYPE_FILESYSTEM);
294 	if (ds == NULL) {
295 		pam_syslog(pamh, LOG_ERR, "dataset %s not found", ds_name);
296 		return (-1);
297 	}
298 	nvlist_t *nvlist = fnvlist_alloc();
299 	pw_password_t *key = prepare_passphrase(pamh, ds, passphrase, nvlist);
300 	if (key == NULL) {
301 		nvlist_free(nvlist);
302 		zfs_close(ds);
303 		return (-1);
304 	}
305 	if (nvlist_add_string(nvlist,
306 	    zfs_prop_to_name(ZFS_PROP_KEYLOCATION),
307 	    "prompt")) {
308 		pam_syslog(pamh, LOG_ERR, "nvlist_add failed for keylocation");
309 		pw_free(key);
310 		nvlist_free(nvlist);
311 		zfs_close(ds);
312 		return (-1);
313 	}
314 	if (nvlist_add_uint64(nvlist,
315 	    zfs_prop_to_name(ZFS_PROP_KEYFORMAT),
316 	    ZFS_KEYFORMAT_PASSPHRASE)) {
317 		pam_syslog(pamh, LOG_ERR, "nvlist_add failed for keyformat");
318 		pw_free(key);
319 		nvlist_free(nvlist);
320 		zfs_close(ds);
321 		return (-1);
322 	}
323 	int ret = lzc_change_key(ds_name, DCP_CMD_NEW_KEY, nvlist,
324 	    (uint8_t *)key->value, WRAPPING_KEY_LEN);
325 	pw_free(key);
326 	if (ret) {
327 		pam_syslog(pamh, LOG_ERR, "change_key failed: %d", ret);
328 		nvlist_free(nvlist);
329 		zfs_close(ds);
330 		return (-1);
331 	}
332 	nvlist_free(nvlist);
333 	zfs_close(ds);
334 	return (0);
335 }
336 
337 static int
338 decrypt_mount(pam_handle_t *pamh, const char *ds_name,
339     const char *passphrase)
340 {
341 	zfs_handle_t *ds = zfs_open(g_zfs, ds_name, ZFS_TYPE_FILESYSTEM);
342 	if (ds == NULL) {
343 		pam_syslog(pamh, LOG_ERR, "dataset %s not found", ds_name);
344 		return (-1);
345 	}
346 	pw_password_t *key = prepare_passphrase(pamh, ds, passphrase, NULL);
347 	if (key == NULL) {
348 		zfs_close(ds);
349 		return (-1);
350 	}
351 	int ret = lzc_load_key(ds_name, B_FALSE, (uint8_t *)key->value,
352 	    WRAPPING_KEY_LEN);
353 	pw_free(key);
354 	if (ret) {
355 		pam_syslog(pamh, LOG_ERR, "load_key failed: %d", ret);
356 		zfs_close(ds);
357 		return (-1);
358 	}
359 	ret = zfs_mount(ds, NULL, 0);
360 	if (ret) {
361 		pam_syslog(pamh, LOG_ERR, "mount failed: %d", ret);
362 		zfs_close(ds);
363 		return (-1);
364 	}
365 	zfs_close(ds);
366 	return (0);
367 }
368 
369 static int
370 unmount_unload(pam_handle_t *pamh, const char *ds_name)
371 {
372 	zfs_handle_t *ds = zfs_open(g_zfs, ds_name, ZFS_TYPE_FILESYSTEM);
373 	if (ds == NULL) {
374 		pam_syslog(pamh, LOG_ERR, "dataset %s not found", ds_name);
375 		return (-1);
376 	}
377 	int ret = zfs_unmount(ds, NULL, 0);
378 	if (ret) {
379 		pam_syslog(pamh, LOG_ERR, "zfs_unmount failed with: %d", ret);
380 		zfs_close(ds);
381 		return (-1);
382 	}
383 
384 	ret = lzc_unload_key(ds_name);
385 	if (ret) {
386 		pam_syslog(pamh, LOG_ERR, "unload_key failed with: %d", ret);
387 		zfs_close(ds);
388 		return (-1);
389 	}
390 	zfs_close(ds);
391 	return (0);
392 }
393 
394 typedef struct {
395 	char *homes_prefix;
396 	char *runstatedir;
397 	char *homedir;
398 	char *dsname;
399 	uid_t uid;
400 	const char *username;
401 	int unmount_and_unload;
402 } zfs_key_config_t;
403 
404 static int
405 zfs_key_config_load(pam_handle_t *pamh, zfs_key_config_t *config,
406     int argc, const char **argv)
407 {
408 	config->homes_prefix = strdup("rpool/home");
409 	if (config->homes_prefix == NULL) {
410 		pam_syslog(pamh, LOG_ERR, "strdup failure");
411 		return (-1);
412 	}
413 	config->runstatedir = strdup(RUNSTATEDIR "/pam_zfs_key");
414 	if (config->runstatedir == NULL) {
415 		pam_syslog(pamh, LOG_ERR, "strdup failure");
416 		free(config->homes_prefix);
417 		return (-1);
418 	}
419 	const char *name;
420 	if (pam_get_user(pamh, &name, NULL) != PAM_SUCCESS) {
421 		pam_syslog(pamh, LOG_ERR,
422 		    "couldn't get username from PAM stack");
423 		free(config->runstatedir);
424 		free(config->homes_prefix);
425 		return (-1);
426 	}
427 	struct passwd *entry = getpwnam(name);
428 	if (!entry) {
429 		free(config->runstatedir);
430 		free(config->homes_prefix);
431 		return (-1);
432 	}
433 	config->uid = entry->pw_uid;
434 	config->username = name;
435 	config->unmount_and_unload = 1;
436 	config->dsname = NULL;
437 	config->homedir = NULL;
438 	for (int c = 0; c < argc; c++) {
439 		if (strncmp(argv[c], "homes=", 6) == 0) {
440 			free(config->homes_prefix);
441 			config->homes_prefix = strdup(argv[c] + 6);
442 		} else if (strncmp(argv[c], "runstatedir=", 12) == 0) {
443 			free(config->runstatedir);
444 			config->runstatedir = strdup(argv[c] + 12);
445 		} else if (strcmp(argv[c], "nounmount") == 0) {
446 			config->unmount_and_unload = 0;
447 		} else if (strcmp(argv[c], "prop_mountpoint") == 0) {
448 			config->homedir = strdup(entry->pw_dir);
449 		}
450 	}
451 	return (0);
452 }
453 
454 static void
455 zfs_key_config_free(zfs_key_config_t *config)
456 {
457 	free(config->homes_prefix);
458 	free(config->runstatedir);
459 	free(config->homedir);
460 	free(config->dsname);
461 }
462 
463 static int
464 find_dsname_by_prop_value(zfs_handle_t *zhp, void *data)
465 {
466 	zfs_type_t type = zfs_get_type(zhp);
467 	zfs_key_config_t *target = data;
468 	char mountpoint[ZFS_MAXPROPLEN];
469 
470 	/* Skip any datasets whose type does not match */
471 	if ((type & ZFS_TYPE_FILESYSTEM) == 0) {
472 		zfs_close(zhp);
473 		return (0);
474 	}
475 
476 	/* Skip any datasets whose mountpoint does not match */
477 	(void) zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT, mountpoint,
478 	    sizeof (mountpoint), NULL, NULL, 0, B_FALSE);
479 	if (strcmp(target->homedir, mountpoint) != 0) {
480 		zfs_close(zhp);
481 		return (0);
482 	}
483 
484 	target->dsname = strdup(zfs_get_name(zhp));
485 	zfs_close(zhp);
486 	return (1);
487 }
488 
489 static char *
490 zfs_key_config_get_dataset(zfs_key_config_t *config)
491 {
492 	if (config->homedir != NULL &&
493 	    config->homes_prefix != NULL) {
494 		zfs_handle_t *zhp = zfs_open(g_zfs, config->homes_prefix,
495 		    ZFS_TYPE_FILESYSTEM);
496 		if (zhp == NULL) {
497 			pam_syslog(NULL, LOG_ERR, "dataset %s not found",
498 			    config->homes_prefix);
499 			zfs_close(zhp);
500 			return (NULL);
501 		}
502 
503 		(void) zfs_iter_filesystems(zhp, find_dsname_by_prop_value,
504 		    config);
505 		zfs_close(zhp);
506 		char *dsname = config->dsname;
507 		config->dsname = NULL;
508 		return (dsname);
509 	}
510 
511 	size_t len = ZFS_MAX_DATASET_NAME_LEN;
512 	size_t total_len = strlen(config->homes_prefix) + 1
513 	    + strlen(config->username);
514 	if (total_len > len) {
515 		return (NULL);
516 	}
517 	char *ret = malloc(len + 1);
518 	if (!ret) {
519 		return (NULL);
520 	}
521 	ret[0] = 0;
522 	strcat(ret, config->homes_prefix);
523 	strcat(ret, "/");
524 	strcat(ret, config->username);
525 	return (ret);
526 }
527 
528 static int
529 zfs_key_config_modify_session_counter(pam_handle_t *pamh,
530     zfs_key_config_t *config, int delta)
531 {
532 	const char *runtime_path = config->runstatedir;
533 	if (mkdir(runtime_path, S_IRWXU) != 0 && errno != EEXIST) {
534 		pam_syslog(pamh, LOG_ERR, "Can't create runtime path: %d",
535 		    errno);
536 		return (-1);
537 	}
538 	if (chown(runtime_path, 0, 0) != 0) {
539 		pam_syslog(pamh, LOG_ERR, "Can't chown runtime path: %d",
540 		    errno);
541 		return (-1);
542 	}
543 	if (chmod(runtime_path, S_IRWXU) != 0) {
544 		pam_syslog(pamh, LOG_ERR, "Can't chmod runtime path: %d",
545 		    errno);
546 		return (-1);
547 	}
548 	size_t runtime_path_len = strlen(runtime_path);
549 	size_t counter_path_len = runtime_path_len + 1 + 10;
550 	char *counter_path = malloc(counter_path_len + 1);
551 	if (!counter_path) {
552 		return (-1);
553 	}
554 	counter_path[0] = 0;
555 	strcat(counter_path, runtime_path);
556 	snprintf(counter_path + runtime_path_len, counter_path_len, "/%d",
557 	    config->uid);
558 	const int fd = open(counter_path,
559 	    O_RDWR | O_CLOEXEC | O_CREAT | O_NOFOLLOW,
560 	    S_IRUSR | S_IWUSR);
561 	free(counter_path);
562 	if (fd < 0) {
563 		pam_syslog(pamh, LOG_ERR, "Can't open counter file: %d", errno);
564 		return (-1);
565 	}
566 	if (flock(fd, LOCK_EX) != 0) {
567 		pam_syslog(pamh, LOG_ERR, "Can't lock counter file: %d", errno);
568 		close(fd);
569 		return (-1);
570 	}
571 	char counter[20];
572 	char *pos = counter;
573 	int remaining = sizeof (counter) - 1;
574 	int ret;
575 	counter[sizeof (counter) - 1] = 0;
576 	while (remaining > 0 && (ret = read(fd, pos, remaining)) > 0) {
577 		remaining -= ret;
578 		pos += ret;
579 	}
580 	*pos = 0;
581 	long int counter_value = strtol(counter, NULL, 10);
582 	counter_value += delta;
583 	if (counter_value < 0) {
584 		counter_value = 0;
585 	}
586 	lseek(fd, 0, SEEK_SET);
587 	if (ftruncate(fd, 0) != 0) {
588 		pam_syslog(pamh, LOG_ERR, "Can't truncate counter file: %d",
589 		    errno);
590 		close(fd);
591 		return (-1);
592 	}
593 	snprintf(counter, sizeof (counter), "%ld", counter_value);
594 	remaining = strlen(counter);
595 	pos = counter;
596 	while (remaining > 0 && (ret = write(fd, pos, remaining)) > 0) {
597 		remaining -= ret;
598 		pos += ret;
599 	}
600 	close(fd);
601 	return (counter_value);
602 }
603 
604 __attribute__((visibility("default")))
605 PAM_EXTERN int
606 pam_sm_authenticate(pam_handle_t *pamh, int flags,
607     int argc, const char **argv)
608 {
609 	if (pw_fetch_lazy(pamh) == NULL) {
610 		return (PAM_AUTH_ERR);
611 	}
612 
613 	return (PAM_SUCCESS);
614 }
615 
616 __attribute__((visibility("default")))
617 PAM_EXTERN int
618 pam_sm_setcred(pam_handle_t *pamh, int flags,
619     int argc, const char **argv)
620 {
621 	return (PAM_SUCCESS);
622 }
623 
624 __attribute__((visibility("default")))
625 PAM_EXTERN int
626 pam_sm_chauthtok(pam_handle_t *pamh, int flags,
627     int argc, const char **argv)
628 {
629 	if (geteuid() != 0) {
630 		pam_syslog(pamh, LOG_ERR,
631 		    "Cannot zfs_mount when not being root.");
632 		return (PAM_PERM_DENIED);
633 	}
634 	zfs_key_config_t config;
635 	if (zfs_key_config_load(pamh, &config, argc, argv) == -1) {
636 		return (PAM_SERVICE_ERR);
637 	}
638 	if (config.uid < 1000) {
639 		zfs_key_config_free(&config);
640 		return (PAM_SUCCESS);
641 	}
642 	{
643 		if (pam_zfs_init(pamh) != 0) {
644 			zfs_key_config_free(&config);
645 			return (PAM_SERVICE_ERR);
646 		}
647 		char *dataset = zfs_key_config_get_dataset(&config);
648 		if (!dataset) {
649 			pam_zfs_free();
650 			zfs_key_config_free(&config);
651 			return (PAM_SERVICE_ERR);
652 		}
653 		int key_loaded = is_key_loaded(pamh, dataset);
654 		if (key_loaded == -1) {
655 			free(dataset);
656 			pam_zfs_free();
657 			zfs_key_config_free(&config);
658 			return (PAM_SERVICE_ERR);
659 		}
660 		free(dataset);
661 		pam_zfs_free();
662 		if (! key_loaded) {
663 			pam_syslog(pamh, LOG_ERR,
664 			    "key not loaded, returning try_again");
665 			zfs_key_config_free(&config);
666 			return (PAM_PERM_DENIED);
667 		}
668 	}
669 
670 	if ((flags & PAM_UPDATE_AUTHTOK) != 0) {
671 		const pw_password_t *token = pw_get(pamh);
672 		if (token == NULL) {
673 			zfs_key_config_free(&config);
674 			return (PAM_SERVICE_ERR);
675 		}
676 		if (pam_zfs_init(pamh) != 0) {
677 			zfs_key_config_free(&config);
678 			return (PAM_SERVICE_ERR);
679 		}
680 		char *dataset = zfs_key_config_get_dataset(&config);
681 		if (!dataset) {
682 			pam_zfs_free();
683 			zfs_key_config_free(&config);
684 			return (PAM_SERVICE_ERR);
685 		}
686 		if (change_key(pamh, dataset, token->value) == -1) {
687 			free(dataset);
688 			pam_zfs_free();
689 			zfs_key_config_free(&config);
690 			return (PAM_SERVICE_ERR);
691 		}
692 		free(dataset);
693 		pam_zfs_free();
694 		zfs_key_config_free(&config);
695 		if (pw_clear(pamh) == -1) {
696 			return (PAM_SERVICE_ERR);
697 		}
698 	} else {
699 		zfs_key_config_free(&config);
700 	}
701 	return (PAM_SUCCESS);
702 }
703 
704 PAM_EXTERN int
705 pam_sm_open_session(pam_handle_t *pamh, int flags,
706     int argc, const char **argv)
707 {
708 	if (geteuid() != 0) {
709 		pam_syslog(pamh, LOG_ERR,
710 		    "Cannot zfs_mount when not being root.");
711 		return (PAM_SUCCESS);
712 	}
713 	zfs_key_config_t config;
714 	zfs_key_config_load(pamh, &config, argc, argv);
715 	if (config.uid < 1000) {
716 		zfs_key_config_free(&config);
717 		return (PAM_SUCCESS);
718 	}
719 
720 	int counter = zfs_key_config_modify_session_counter(pamh, &config, 1);
721 	if (counter != 1) {
722 		zfs_key_config_free(&config);
723 		return (PAM_SUCCESS);
724 	}
725 
726 	const pw_password_t *token = pw_get(pamh);
727 	if (token == NULL) {
728 		zfs_key_config_free(&config);
729 		return (PAM_SESSION_ERR);
730 	}
731 	if (pam_zfs_init(pamh) != 0) {
732 		zfs_key_config_free(&config);
733 		return (PAM_SERVICE_ERR);
734 	}
735 	char *dataset = zfs_key_config_get_dataset(&config);
736 	if (!dataset) {
737 		pam_zfs_free();
738 		zfs_key_config_free(&config);
739 		return (PAM_SERVICE_ERR);
740 	}
741 	if (decrypt_mount(pamh, dataset, token->value) == -1) {
742 		free(dataset);
743 		pam_zfs_free();
744 		zfs_key_config_free(&config);
745 		return (PAM_SERVICE_ERR);
746 	}
747 	free(dataset);
748 	pam_zfs_free();
749 	zfs_key_config_free(&config);
750 	if (pw_clear(pamh) == -1) {
751 		return (PAM_SERVICE_ERR);
752 	}
753 	return (PAM_SUCCESS);
754 
755 }
756 
757 __attribute__((visibility("default")))
758 PAM_EXTERN int
759 pam_sm_close_session(pam_handle_t *pamh, int flags,
760     int argc, const char **argv)
761 {
762 	if (geteuid() != 0) {
763 		pam_syslog(pamh, LOG_ERR,
764 		    "Cannot zfs_mount when not being root.");
765 		return (PAM_SUCCESS);
766 	}
767 	zfs_key_config_t config;
768 	zfs_key_config_load(pamh, &config, argc, argv);
769 	if (config.uid < 1000) {
770 		zfs_key_config_free(&config);
771 		return (PAM_SUCCESS);
772 	}
773 
774 	int counter = zfs_key_config_modify_session_counter(pamh, &config, -1);
775 	if (counter != 0) {
776 		zfs_key_config_free(&config);
777 		return (PAM_SUCCESS);
778 	}
779 
780 	if (config.unmount_and_unload) {
781 		if (pam_zfs_init(pamh) != 0) {
782 			zfs_key_config_free(&config);
783 			return (PAM_SERVICE_ERR);
784 		}
785 		char *dataset = zfs_key_config_get_dataset(&config);
786 		if (!dataset) {
787 			pam_zfs_free();
788 			zfs_key_config_free(&config);
789 			return (PAM_SESSION_ERR);
790 		}
791 		if (unmount_unload(pamh, dataset) == -1) {
792 			free(dataset);
793 			pam_zfs_free();
794 			zfs_key_config_free(&config);
795 			return (PAM_SESSION_ERR);
796 		}
797 		free(dataset);
798 		pam_zfs_free();
799 	}
800 
801 	zfs_key_config_free(&config);
802 	return (PAM_SUCCESS);
803 }
804