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 #define	MAP_FLAGS MAP_PRIVATE | MAP_ANONYMOUS
45 #elif	defined(__FreeBSD__)
46 #include <security/pam_appl.h>
47 static void
48 pam_syslog(pam_handle_t *pamh, int loglevel, const char *fmt, ...)
49 {
50 	va_list args;
51 	va_start(args, fmt);
52 	vsyslog(loglevel, fmt, args);
53 	va_end(args);
54 }
55 #define	MAP_FLAGS MAP_PRIVATE | MAP_ANON | MAP_NOCORE
56 #endif
57 
58 #include <string.h>
59 #include <unistd.h>
60 #include <fcntl.h>
61 #include <sys/stat.h>
62 #include <sys/file.h>
63 #include <sys/wait.h>
64 #include <pwd.h>
65 
66 #include <sys/mman.h>
67 
68 static const char PASSWORD_VAR_NAME[] = "pam_zfs_key_authtok";
69 
70 static libzfs_handle_t *g_zfs;
71 
72 static void destroy_pw(pam_handle_t *pamh, void *data, int errcode);
73 
74 typedef int (*mlock_func_t) (const void *, size_t);
75 
76 typedef struct {
77 	size_t len;
78 	char *value;
79 } pw_password_t;
80 
81 /*
82  * Try to mlock(2) or munlock(2) addr while handling EAGAIN by retrying ten
83  * times and sleeping 10 milliseconds in between for a total of 0.1
84  * seconds. lock_func must point to either mlock(2) or munlock(2).
85  */
86 static int
87 try_lock(mlock_func_t lock_func, const void *addr, size_t len)
88 {
89 	int err;
90 	int retries = 10;
91 	useconds_t sleep_dur = 10 * 1000;
92 
93 	if ((err = (*lock_func)(addr, len)) != EAGAIN) {
94 		return (err);
95 	}
96 	for (int i = retries; i > 0; --i) {
97 		(void) usleep(sleep_dur);
98 		if ((err = (*lock_func)(addr, len)) != EAGAIN) {
99 			break;
100 		}
101 	}
102 	return (err);
103 }
104 
105 
106 static pw_password_t *
107 alloc_pw_size(size_t len)
108 {
109 	pw_password_t *pw = malloc(sizeof (pw_password_t));
110 	if (!pw) {
111 		return (NULL);
112 	}
113 	pw->len = len;
114 	/*
115 	 * We use mmap(2) rather than malloc(3) since later on we mlock(2) the
116 	 * memory region. Since mlock(2) and munlock(2) operate on whole memory
117 	 * pages we should allocate a whole page here as mmap(2) does. Further
118 	 * this ensures that the addresses passed to mlock(2) an munlock(2) are
119 	 * on a page boundary as suggested by FreeBSD and required by some
120 	 * other implementations. Finally we avoid inadvertently munlocking
121 	 * memory mlocked by an concurrently running instance of us.
122 	 */
123 	pw->value = mmap(NULL, pw->len, PROT_READ | PROT_WRITE, MAP_FLAGS,
124 	    -1, 0);
125 
126 	if (pw->value == MAP_FAILED) {
127 		free(pw);
128 		return (NULL);
129 	}
130 	if (try_lock(mlock, pw->value, pw->len) != 0) {
131 		(void) munmap(pw->value, pw->len);
132 		free(pw);
133 		return (NULL);
134 	}
135 	return (pw);
136 }
137 
138 static pw_password_t *
139 alloc_pw_string(const char *source)
140 {
141 	size_t len = strlen(source) + 1;
142 	pw_password_t *pw = alloc_pw_size(len);
143 
144 	if (!pw) {
145 		return (NULL);
146 	}
147 	memcpy(pw->value, source, pw->len);
148 	return (pw);
149 }
150 
151 static void
152 pw_free(pw_password_t *pw)
153 {
154 	bzero(pw->value, pw->len);
155 	if (try_lock(munlock, pw->value, pw->len) == 0) {
156 		(void) munmap(pw->value, pw->len);
157 	}
158 	free(pw);
159 }
160 
161 static pw_password_t *
162 pw_fetch(pam_handle_t *pamh)
163 {
164 	const char *token;
165 	if (pam_get_authtok(pamh, PAM_AUTHTOK, &token, NULL) != PAM_SUCCESS) {
166 		pam_syslog(pamh, LOG_ERR,
167 		    "couldn't get password from PAM stack");
168 		return (NULL);
169 	}
170 	if (!token) {
171 		pam_syslog(pamh, LOG_ERR,
172 		    "token from PAM stack is null");
173 		return (NULL);
174 	}
175 	return (alloc_pw_string(token));
176 }
177 
178 static const pw_password_t *
179 pw_fetch_lazy(pam_handle_t *pamh)
180 {
181 	pw_password_t *pw = pw_fetch(pamh);
182 	if (pw == NULL) {
183 		return (NULL);
184 	}
185 	int ret = pam_set_data(pamh, PASSWORD_VAR_NAME, pw, destroy_pw);
186 	if (ret != PAM_SUCCESS) {
187 		pw_free(pw);
188 		pam_syslog(pamh, LOG_ERR, "pam_set_data failed");
189 		return (NULL);
190 	}
191 	return (pw);
192 }
193 
194 static const pw_password_t *
195 pw_get(pam_handle_t *pamh)
196 {
197 	const pw_password_t *authtok = NULL;
198 	int ret = pam_get_data(pamh, PASSWORD_VAR_NAME,
199 	    (const void**)(&authtok));
200 	if (ret == PAM_SUCCESS)
201 		return (authtok);
202 	if (ret == PAM_NO_MODULE_DATA)
203 		return (pw_fetch_lazy(pamh));
204 	pam_syslog(pamh, LOG_ERR, "password not available");
205 	return (NULL);
206 }
207 
208 static int
209 pw_clear(pam_handle_t *pamh)
210 {
211 	int ret = pam_set_data(pamh, PASSWORD_VAR_NAME, NULL, NULL);
212 	if (ret != PAM_SUCCESS) {
213 		pam_syslog(pamh, LOG_ERR, "clearing password failed");
214 		return (-1);
215 	}
216 	return (0);
217 }
218 
219 static void
220 destroy_pw(pam_handle_t *pamh, void *data, int errcode)
221 {
222 	if (data != NULL) {
223 		pw_free((pw_password_t *)data);
224 	}
225 }
226 
227 static int
228 pam_zfs_init(pam_handle_t *pamh)
229 {
230 	int error = 0;
231 	if ((g_zfs = libzfs_init()) == NULL) {
232 		error = errno;
233 		pam_syslog(pamh, LOG_ERR, "Zfs initialization error: %s",
234 		    libzfs_error_init(error));
235 	}
236 	return (error);
237 }
238 
239 static void
240 pam_zfs_free(void)
241 {
242 	libzfs_fini(g_zfs);
243 }
244 
245 static pw_password_t *
246 prepare_passphrase(pam_handle_t *pamh, zfs_handle_t *ds,
247     const char *passphrase, nvlist_t *nvlist)
248 {
249 	pw_password_t *key = alloc_pw_size(WRAPPING_KEY_LEN);
250 	if (!key) {
251 		return (NULL);
252 	}
253 	uint64_t salt;
254 	uint64_t iters;
255 	if (nvlist != NULL) {
256 		int fd = open("/dev/urandom", O_RDONLY);
257 		if (fd < 0) {
258 			pw_free(key);
259 			return (NULL);
260 		}
261 		int bytes_read = 0;
262 		char *buf = (char *)&salt;
263 		size_t bytes = sizeof (uint64_t);
264 		while (bytes_read < bytes) {
265 			ssize_t len = read(fd, buf + bytes_read, bytes
266 			    - bytes_read);
267 			if (len < 0) {
268 				close(fd);
269 				pw_free(key);
270 				return (NULL);
271 			}
272 			bytes_read += len;
273 		}
274 		close(fd);
275 
276 		if (nvlist_add_uint64(nvlist,
277 		    zfs_prop_to_name(ZFS_PROP_PBKDF2_SALT), salt)) {
278 			pam_syslog(pamh, LOG_ERR,
279 			    "failed to add salt to nvlist");
280 			pw_free(key);
281 			return (NULL);
282 		}
283 		iters = DEFAULT_PBKDF2_ITERATIONS;
284 		if (nvlist_add_uint64(nvlist, zfs_prop_to_name(
285 		    ZFS_PROP_PBKDF2_ITERS), iters)) {
286 			pam_syslog(pamh, LOG_ERR,
287 			    "failed to add iters to nvlist");
288 			pw_free(key);
289 			return (NULL);
290 		}
291 	} else {
292 		salt = zfs_prop_get_int(ds, ZFS_PROP_PBKDF2_SALT);
293 		iters = zfs_prop_get_int(ds, ZFS_PROP_PBKDF2_ITERS);
294 	}
295 
296 	salt = LE_64(salt);
297 	if (!PKCS5_PBKDF2_HMAC_SHA1((char *)passphrase,
298 	    strlen(passphrase), (uint8_t *)&salt,
299 	    sizeof (uint64_t), iters, WRAPPING_KEY_LEN,
300 	    (uint8_t *)key->value)) {
301 		pam_syslog(pamh, LOG_ERR, "pbkdf failed");
302 		pw_free(key);
303 		return (NULL);
304 	}
305 	return (key);
306 }
307 
308 static int
309 is_key_loaded(pam_handle_t *pamh, const char *ds_name)
310 {
311 	zfs_handle_t *ds = zfs_open(g_zfs, ds_name, ZFS_TYPE_FILESYSTEM);
312 	if (ds == NULL) {
313 		pam_syslog(pamh, LOG_ERR, "dataset %s not found", ds_name);
314 		return (-1);
315 	}
316 	int keystatus = zfs_prop_get_int(ds, ZFS_PROP_KEYSTATUS);
317 	zfs_close(ds);
318 	return (keystatus != ZFS_KEYSTATUS_UNAVAILABLE);
319 }
320 
321 static int
322 change_key(pam_handle_t *pamh, const char *ds_name,
323     const char *passphrase)
324 {
325 	zfs_handle_t *ds = zfs_open(g_zfs, ds_name, ZFS_TYPE_FILESYSTEM);
326 	if (ds == NULL) {
327 		pam_syslog(pamh, LOG_ERR, "dataset %s not found", ds_name);
328 		return (-1);
329 	}
330 	nvlist_t *nvlist = fnvlist_alloc();
331 	pw_password_t *key = prepare_passphrase(pamh, ds, passphrase, nvlist);
332 	if (key == NULL) {
333 		nvlist_free(nvlist);
334 		zfs_close(ds);
335 		return (-1);
336 	}
337 	if (nvlist_add_string(nvlist,
338 	    zfs_prop_to_name(ZFS_PROP_KEYLOCATION),
339 	    "prompt")) {
340 		pam_syslog(pamh, LOG_ERR, "nvlist_add failed for keylocation");
341 		pw_free(key);
342 		nvlist_free(nvlist);
343 		zfs_close(ds);
344 		return (-1);
345 	}
346 	if (nvlist_add_uint64(nvlist,
347 	    zfs_prop_to_name(ZFS_PROP_KEYFORMAT),
348 	    ZFS_KEYFORMAT_PASSPHRASE)) {
349 		pam_syslog(pamh, LOG_ERR, "nvlist_add failed for keyformat");
350 		pw_free(key);
351 		nvlist_free(nvlist);
352 		zfs_close(ds);
353 		return (-1);
354 	}
355 	int ret = lzc_change_key(ds_name, DCP_CMD_NEW_KEY, nvlist,
356 	    (uint8_t *)key->value, WRAPPING_KEY_LEN);
357 	pw_free(key);
358 	if (ret) {
359 		pam_syslog(pamh, LOG_ERR, "change_key failed: %d", ret);
360 		nvlist_free(nvlist);
361 		zfs_close(ds);
362 		return (-1);
363 	}
364 	nvlist_free(nvlist);
365 	zfs_close(ds);
366 	return (0);
367 }
368 
369 static int
370 decrypt_mount(pam_handle_t *pamh, const char *ds_name,
371     const char *passphrase)
372 {
373 	zfs_handle_t *ds = zfs_open(g_zfs, ds_name, ZFS_TYPE_FILESYSTEM);
374 	if (ds == NULL) {
375 		pam_syslog(pamh, LOG_ERR, "dataset %s not found", ds_name);
376 		return (-1);
377 	}
378 	pw_password_t *key = prepare_passphrase(pamh, ds, passphrase, NULL);
379 	if (key == NULL) {
380 		zfs_close(ds);
381 		return (-1);
382 	}
383 	int ret = lzc_load_key(ds_name, B_FALSE, (uint8_t *)key->value,
384 	    WRAPPING_KEY_LEN);
385 	pw_free(key);
386 	if (ret) {
387 		pam_syslog(pamh, LOG_ERR, "load_key failed: %d", ret);
388 		zfs_close(ds);
389 		return (-1);
390 	}
391 	ret = zfs_mount(ds, NULL, 0);
392 	if (ret) {
393 		pam_syslog(pamh, LOG_ERR, "mount failed: %d", ret);
394 		zfs_close(ds);
395 		return (-1);
396 	}
397 	zfs_close(ds);
398 	return (0);
399 }
400 
401 static int
402 unmount_unload(pam_handle_t *pamh, const char *ds_name)
403 {
404 	zfs_handle_t *ds = zfs_open(g_zfs, ds_name, ZFS_TYPE_FILESYSTEM);
405 	if (ds == NULL) {
406 		pam_syslog(pamh, LOG_ERR, "dataset %s not found", ds_name);
407 		return (-1);
408 	}
409 	int ret = zfs_unmount(ds, NULL, 0);
410 	if (ret) {
411 		pam_syslog(pamh, LOG_ERR, "zfs_unmount failed with: %d", ret);
412 		zfs_close(ds);
413 		return (-1);
414 	}
415 
416 	ret = lzc_unload_key(ds_name);
417 	if (ret) {
418 		pam_syslog(pamh, LOG_ERR, "unload_key failed with: %d", ret);
419 		zfs_close(ds);
420 		return (-1);
421 	}
422 	zfs_close(ds);
423 	return (0);
424 }
425 
426 typedef struct {
427 	char *homes_prefix;
428 	char *runstatedir;
429 	char *homedir;
430 	char *dsname;
431 	uid_t uid;
432 	const char *username;
433 	int unmount_and_unload;
434 } zfs_key_config_t;
435 
436 static int
437 zfs_key_config_load(pam_handle_t *pamh, zfs_key_config_t *config,
438     int argc, const char **argv)
439 {
440 	config->homes_prefix = strdup("rpool/home");
441 	if (config->homes_prefix == NULL) {
442 		pam_syslog(pamh, LOG_ERR, "strdup failure");
443 		return (-1);
444 	}
445 	config->runstatedir = strdup(RUNSTATEDIR "/pam_zfs_key");
446 	if (config->runstatedir == NULL) {
447 		pam_syslog(pamh, LOG_ERR, "strdup failure");
448 		free(config->homes_prefix);
449 		return (-1);
450 	}
451 	const char *name;
452 	if (pam_get_user(pamh, &name, NULL) != PAM_SUCCESS) {
453 		pam_syslog(pamh, LOG_ERR,
454 		    "couldn't get username from PAM stack");
455 		free(config->runstatedir);
456 		free(config->homes_prefix);
457 		return (-1);
458 	}
459 	struct passwd *entry = getpwnam(name);
460 	if (!entry) {
461 		free(config->runstatedir);
462 		free(config->homes_prefix);
463 		return (-1);
464 	}
465 	config->uid = entry->pw_uid;
466 	config->username = name;
467 	config->unmount_and_unload = 1;
468 	config->dsname = NULL;
469 	config->homedir = NULL;
470 	for (int c = 0; c < argc; c++) {
471 		if (strncmp(argv[c], "homes=", 6) == 0) {
472 			free(config->homes_prefix);
473 			config->homes_prefix = strdup(argv[c] + 6);
474 		} else if (strncmp(argv[c], "runstatedir=", 12) == 0) {
475 			free(config->runstatedir);
476 			config->runstatedir = strdup(argv[c] + 12);
477 		} else if (strcmp(argv[c], "nounmount") == 0) {
478 			config->unmount_and_unload = 0;
479 		} else if (strcmp(argv[c], "prop_mountpoint") == 0) {
480 			config->homedir = strdup(entry->pw_dir);
481 		}
482 	}
483 	return (0);
484 }
485 
486 static void
487 zfs_key_config_free(zfs_key_config_t *config)
488 {
489 	free(config->homes_prefix);
490 	free(config->runstatedir);
491 	free(config->homedir);
492 	free(config->dsname);
493 }
494 
495 static int
496 find_dsname_by_prop_value(zfs_handle_t *zhp, void *data)
497 {
498 	zfs_type_t type = zfs_get_type(zhp);
499 	zfs_key_config_t *target = data;
500 	char mountpoint[ZFS_MAXPROPLEN];
501 
502 	/* Skip any datasets whose type does not match */
503 	if ((type & ZFS_TYPE_FILESYSTEM) == 0) {
504 		zfs_close(zhp);
505 		return (0);
506 	}
507 
508 	/* Skip any datasets whose mountpoint does not match */
509 	(void) zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT, mountpoint,
510 	    sizeof (mountpoint), NULL, NULL, 0, B_FALSE);
511 	if (strcmp(target->homedir, mountpoint) != 0) {
512 		zfs_close(zhp);
513 		return (0);
514 	}
515 
516 	target->dsname = strdup(zfs_get_name(zhp));
517 	zfs_close(zhp);
518 	return (1);
519 }
520 
521 static char *
522 zfs_key_config_get_dataset(zfs_key_config_t *config)
523 {
524 	if (config->homedir != NULL &&
525 	    config->homes_prefix != NULL) {
526 		zfs_handle_t *zhp = zfs_open(g_zfs, config->homes_prefix,
527 		    ZFS_TYPE_FILESYSTEM);
528 		if (zhp == NULL) {
529 			pam_syslog(NULL, LOG_ERR, "dataset %s not found",
530 			    config->homes_prefix);
531 			zfs_close(zhp);
532 			return (NULL);
533 		}
534 
535 		(void) zfs_iter_filesystems(zhp, find_dsname_by_prop_value,
536 		    config);
537 		zfs_close(zhp);
538 		char *dsname = config->dsname;
539 		config->dsname = NULL;
540 		return (dsname);
541 	}
542 
543 	size_t len = ZFS_MAX_DATASET_NAME_LEN;
544 	size_t total_len = strlen(config->homes_prefix) + 1
545 	    + strlen(config->username);
546 	if (total_len > len) {
547 		return (NULL);
548 	}
549 	char *ret = malloc(len + 1);
550 	if (!ret) {
551 		return (NULL);
552 	}
553 	ret[0] = 0;
554 	strcat(ret, config->homes_prefix);
555 	strcat(ret, "/");
556 	strcat(ret, config->username);
557 	return (ret);
558 }
559 
560 static int
561 zfs_key_config_modify_session_counter(pam_handle_t *pamh,
562     zfs_key_config_t *config, int delta)
563 {
564 	const char *runtime_path = config->runstatedir;
565 	if (mkdir(runtime_path, S_IRWXU) != 0 && errno != EEXIST) {
566 		pam_syslog(pamh, LOG_ERR, "Can't create runtime path: %d",
567 		    errno);
568 		return (-1);
569 	}
570 	if (chown(runtime_path, 0, 0) != 0) {
571 		pam_syslog(pamh, LOG_ERR, "Can't chown runtime path: %d",
572 		    errno);
573 		return (-1);
574 	}
575 	if (chmod(runtime_path, S_IRWXU) != 0) {
576 		pam_syslog(pamh, LOG_ERR, "Can't chmod runtime path: %d",
577 		    errno);
578 		return (-1);
579 	}
580 	size_t runtime_path_len = strlen(runtime_path);
581 	size_t counter_path_len = runtime_path_len + 1 + 10;
582 	char *counter_path = malloc(counter_path_len + 1);
583 	if (!counter_path) {
584 		return (-1);
585 	}
586 	counter_path[0] = 0;
587 	strcat(counter_path, runtime_path);
588 	snprintf(counter_path + runtime_path_len, counter_path_len, "/%d",
589 	    config->uid);
590 	const int fd = open(counter_path,
591 	    O_RDWR | O_CLOEXEC | O_CREAT | O_NOFOLLOW,
592 	    S_IRUSR | S_IWUSR);
593 	free(counter_path);
594 	if (fd < 0) {
595 		pam_syslog(pamh, LOG_ERR, "Can't open counter file: %d", errno);
596 		return (-1);
597 	}
598 	if (flock(fd, LOCK_EX) != 0) {
599 		pam_syslog(pamh, LOG_ERR, "Can't lock counter file: %d", errno);
600 		close(fd);
601 		return (-1);
602 	}
603 	char counter[20];
604 	char *pos = counter;
605 	int remaining = sizeof (counter) - 1;
606 	int ret;
607 	counter[sizeof (counter) - 1] = 0;
608 	while (remaining > 0 && (ret = read(fd, pos, remaining)) > 0) {
609 		remaining -= ret;
610 		pos += ret;
611 	}
612 	*pos = 0;
613 	long int counter_value = strtol(counter, NULL, 10);
614 	counter_value += delta;
615 	if (counter_value < 0) {
616 		counter_value = 0;
617 	}
618 	lseek(fd, 0, SEEK_SET);
619 	if (ftruncate(fd, 0) != 0) {
620 		pam_syslog(pamh, LOG_ERR, "Can't truncate counter file: %d",
621 		    errno);
622 		close(fd);
623 		return (-1);
624 	}
625 	snprintf(counter, sizeof (counter), "%ld", counter_value);
626 	remaining = strlen(counter);
627 	pos = counter;
628 	while (remaining > 0 && (ret = write(fd, pos, remaining)) > 0) {
629 		remaining -= ret;
630 		pos += ret;
631 	}
632 	close(fd);
633 	return (counter_value);
634 }
635 
636 __attribute__((visibility("default")))
637 PAM_EXTERN int
638 pam_sm_authenticate(pam_handle_t *pamh, int flags,
639     int argc, const char **argv)
640 {
641 	if (pw_fetch_lazy(pamh) == NULL) {
642 		return (PAM_AUTH_ERR);
643 	}
644 
645 	return (PAM_SUCCESS);
646 }
647 
648 __attribute__((visibility("default")))
649 PAM_EXTERN int
650 pam_sm_setcred(pam_handle_t *pamh, int flags,
651     int argc, const char **argv)
652 {
653 	return (PAM_SUCCESS);
654 }
655 
656 __attribute__((visibility("default")))
657 PAM_EXTERN int
658 pam_sm_chauthtok(pam_handle_t *pamh, int flags,
659     int argc, const char **argv)
660 {
661 	if (geteuid() != 0) {
662 		pam_syslog(pamh, LOG_ERR,
663 		    "Cannot zfs_mount when not being root.");
664 		return (PAM_PERM_DENIED);
665 	}
666 	zfs_key_config_t config;
667 	if (zfs_key_config_load(pamh, &config, argc, argv) == -1) {
668 		return (PAM_SERVICE_ERR);
669 	}
670 	if (config.uid < 1000) {
671 		zfs_key_config_free(&config);
672 		return (PAM_SUCCESS);
673 	}
674 	{
675 		if (pam_zfs_init(pamh) != 0) {
676 			zfs_key_config_free(&config);
677 			return (PAM_SERVICE_ERR);
678 		}
679 		char *dataset = zfs_key_config_get_dataset(&config);
680 		if (!dataset) {
681 			pam_zfs_free();
682 			zfs_key_config_free(&config);
683 			return (PAM_SERVICE_ERR);
684 		}
685 		int key_loaded = is_key_loaded(pamh, dataset);
686 		if (key_loaded == -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 		if (! key_loaded) {
695 			pam_syslog(pamh, LOG_ERR,
696 			    "key not loaded, returning try_again");
697 			zfs_key_config_free(&config);
698 			return (PAM_PERM_DENIED);
699 		}
700 	}
701 
702 	if ((flags & PAM_UPDATE_AUTHTOK) != 0) {
703 		const pw_password_t *token = pw_get(pamh);
704 		if (token == NULL) {
705 			zfs_key_config_free(&config);
706 			return (PAM_SERVICE_ERR);
707 		}
708 		if (pam_zfs_init(pamh) != 0) {
709 			zfs_key_config_free(&config);
710 			return (PAM_SERVICE_ERR);
711 		}
712 		char *dataset = zfs_key_config_get_dataset(&config);
713 		if (!dataset) {
714 			pam_zfs_free();
715 			zfs_key_config_free(&config);
716 			return (PAM_SERVICE_ERR);
717 		}
718 		if (change_key(pamh, dataset, token->value) == -1) {
719 			free(dataset);
720 			pam_zfs_free();
721 			zfs_key_config_free(&config);
722 			return (PAM_SERVICE_ERR);
723 		}
724 		free(dataset);
725 		pam_zfs_free();
726 		zfs_key_config_free(&config);
727 		if (pw_clear(pamh) == -1) {
728 			return (PAM_SERVICE_ERR);
729 		}
730 	} else {
731 		zfs_key_config_free(&config);
732 	}
733 	return (PAM_SUCCESS);
734 }
735 
736 PAM_EXTERN int
737 pam_sm_open_session(pam_handle_t *pamh, int flags,
738     int argc, const char **argv)
739 {
740 	if (geteuid() != 0) {
741 		pam_syslog(pamh, LOG_ERR,
742 		    "Cannot zfs_mount when not being root.");
743 		return (PAM_SUCCESS);
744 	}
745 	zfs_key_config_t config;
746 	zfs_key_config_load(pamh, &config, argc, argv);
747 	if (config.uid < 1000) {
748 		zfs_key_config_free(&config);
749 		return (PAM_SUCCESS);
750 	}
751 
752 	int counter = zfs_key_config_modify_session_counter(pamh, &config, 1);
753 	if (counter != 1) {
754 		zfs_key_config_free(&config);
755 		return (PAM_SUCCESS);
756 	}
757 
758 	const pw_password_t *token = pw_get(pamh);
759 	if (token == NULL) {
760 		zfs_key_config_free(&config);
761 		return (PAM_SESSION_ERR);
762 	}
763 	if (pam_zfs_init(pamh) != 0) {
764 		zfs_key_config_free(&config);
765 		return (PAM_SERVICE_ERR);
766 	}
767 	char *dataset = zfs_key_config_get_dataset(&config);
768 	if (!dataset) {
769 		pam_zfs_free();
770 		zfs_key_config_free(&config);
771 		return (PAM_SERVICE_ERR);
772 	}
773 	if (decrypt_mount(pamh, dataset, token->value) == -1) {
774 		free(dataset);
775 		pam_zfs_free();
776 		zfs_key_config_free(&config);
777 		return (PAM_SERVICE_ERR);
778 	}
779 	free(dataset);
780 	pam_zfs_free();
781 	zfs_key_config_free(&config);
782 	if (pw_clear(pamh) == -1) {
783 		return (PAM_SERVICE_ERR);
784 	}
785 	return (PAM_SUCCESS);
786 
787 }
788 
789 __attribute__((visibility("default")))
790 PAM_EXTERN int
791 pam_sm_close_session(pam_handle_t *pamh, int flags,
792     int argc, const char **argv)
793 {
794 	if (geteuid() != 0) {
795 		pam_syslog(pamh, LOG_ERR,
796 		    "Cannot zfs_mount when not being root.");
797 		return (PAM_SUCCESS);
798 	}
799 	zfs_key_config_t config;
800 	zfs_key_config_load(pamh, &config, argc, argv);
801 	if (config.uid < 1000) {
802 		zfs_key_config_free(&config);
803 		return (PAM_SUCCESS);
804 	}
805 
806 	int counter = zfs_key_config_modify_session_counter(pamh, &config, -1);
807 	if (counter != 0) {
808 		zfs_key_config_free(&config);
809 		return (PAM_SUCCESS);
810 	}
811 
812 	if (config.unmount_and_unload) {
813 		if (pam_zfs_init(pamh) != 0) {
814 			zfs_key_config_free(&config);
815 			return (PAM_SERVICE_ERR);
816 		}
817 		char *dataset = zfs_key_config_get_dataset(&config);
818 		if (!dataset) {
819 			pam_zfs_free();
820 			zfs_key_config_free(&config);
821 			return (PAM_SESSION_ERR);
822 		}
823 		if (unmount_unload(pamh, dataset) == -1) {
824 			free(dataset);
825 			pam_zfs_free();
826 			zfs_key_config_free(&config);
827 			return (PAM_SESSION_ERR);
828 		}
829 		free(dataset);
830 		pam_zfs_free();
831 	}
832 
833 	zfs_key_config_free(&config);
834 	return (PAM_SUCCESS);
835 }
836