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 const int runtime_fd = open(runtime_path,
844 O_RDONLY | O_CLOEXEC | O_NOFOLLOW | O_DIRECTORY);
845 if (runtime_fd < 0) {
846 pam_syslog(pamh, LOG_ERR, "Can't open runtime path: %d", errno);
847 return (-1);
848 }
849 if (fchown(runtime_fd, 0, 0) != 0) {
850 pam_syslog(pamh, LOG_ERR, "Can't chown runtime path: %d",
851 errno);
852 close(runtime_fd);
853 return (-1);
854 }
855 if (fchmod(runtime_fd, S_IRWXU) != 0) {
856 pam_syslog(pamh, LOG_ERR, "Can't chmod runtime path: %d",
857 errno);
858 close(runtime_fd);
859 return (-1);
860 }
861
862 char *counter_path;
863 if (asprintf(&counter_path, "%u", config->uid) == -1) {
864 close(runtime_fd);
865 return (-1);
866 }
867
868 const int fd = openat(runtime_fd, counter_path,
869 O_RDWR | O_CLOEXEC | O_CREAT | O_NOFOLLOW,
870 S_IRUSR | S_IWUSR);
871 int ret = errno;
872
873 free(counter_path);
874 close(runtime_fd);
875
876 if (fd < 0) {
877 pam_syslog(pamh, LOG_ERR, "Can't open counter file: %d", ret);
878 return (-1);
879 }
880 if (flock(fd, LOCK_EX) != 0) {
881 pam_syslog(pamh, LOG_ERR, "Can't lock counter file: %d", errno);
882 close(fd);
883 return (-1);
884 }
885 char counter[20];
886 char *pos = counter;
887 int remaining = sizeof (counter) - 1;
888 counter[sizeof (counter) - 1] = 0;
889 while (remaining > 0 && (ret = read(fd, pos, remaining)) > 0) {
890 remaining -= ret;
891 pos += ret;
892 }
893 *pos = 0;
894 long int counter_value = strtol(counter, NULL, 10);
895 counter_value += delta;
896 if (counter_value < 0) {
897 counter_value = 0;
898 }
899 lseek(fd, 0, SEEK_SET);
900 if (ftruncate(fd, 0) != 0) {
901 pam_syslog(pamh, LOG_ERR, "Can't truncate counter file: %d",
902 errno);
903 close(fd);
904 return (-1);
905 }
906 snprintf(counter, sizeof (counter), "%ld", counter_value);
907 remaining = strlen(counter);
908 pos = counter;
909 while (remaining > 0 && (ret = write(fd, pos, remaining)) > 0) {
910 remaining -= ret;
911 pos += ret;
912 }
913 close(fd);
914 return (counter_value);
915 }
916
917 /* Callback for authentication - verify password works (noop mode) */
918 static int
auth_callback(pam_handle_t * pamh,zfs_key_config_t * config,const char * dataset,void * data)919 auth_callback(pam_handle_t *pamh, zfs_key_config_t *config,
920 const char *dataset, void *data)
921 {
922 const char *passphrase = data;
923 return (decrypt_mount(pamh, config, dataset, passphrase, B_TRUE));
924 }
925
926 __attribute__((visibility("default")))
927 PAM_EXTERN int
pam_sm_authenticate(pam_handle_t * pamh,int flags,int argc,const char ** argv)928 pam_sm_authenticate(pam_handle_t *pamh, int flags,
929 int argc, const char **argv)
930 {
931 (void) flags;
932
933 if (geteuid() != 0) {
934 pam_syslog(pamh, LOG_ERR,
935 "Cannot zfs_mount when not being root.");
936 return (PAM_SERVICE_ERR);
937 }
938 zfs_key_config_t config;
939 int config_err = zfs_key_config_load(pamh, &config, argc, argv);
940 if (config_err != PAM_SUCCESS) {
941 return (config_err);
942 }
943 if (config.uid < config.uid_min || config.uid > config.uid_max) {
944 zfs_key_config_free(&config);
945 return (PAM_SERVICE_ERR);
946 }
947
948 const pw_password_t *token = pw_fetch_lazy(pamh,
949 PAM_AUTHTOK, PASSWORD_VAR_NAME);
950 if (token == NULL) {
951 zfs_key_config_free(&config);
952 return (PAM_AUTH_ERR);
953 }
954 if (pam_zfs_init(pamh) != 0) {
955 zfs_key_config_free(&config);
956 return (PAM_SERVICE_ERR);
957 }
958
959 int ret = foreach_dataset(pamh, &config, auth_callback,
960 (void *)token->value);
961 pam_zfs_free();
962 zfs_key_config_free(&config);
963 if (ret < 0) {
964 return (PAM_AUTH_ERR);
965 }
966 return (PAM_SUCCESS);
967 }
968
969 __attribute__((visibility("default")))
970 PAM_EXTERN int
pam_sm_setcred(pam_handle_t * pamh,int flags,int argc,const char ** argv)971 pam_sm_setcred(pam_handle_t *pamh, int flags,
972 int argc, const char **argv)
973 {
974 (void) pamh, (void) flags, (void) argc, (void) argv;
975 return (PAM_SUCCESS);
976 }
977
978 /* Context for password change callback */
979 typedef struct {
980 const char *old_pass;
981 const char *new_pass;
982 } chauthtok_ctx_t;
983
984 /* Callback for password change */
985 static int
chauthtok_callback(pam_handle_t * pamh,zfs_key_config_t * config,const char * dataset,void * data)986 chauthtok_callback(pam_handle_t *pamh, zfs_key_config_t *config,
987 const char *dataset, void *data)
988 {
989 chauthtok_ctx_t *ctx = data;
990 int was_loaded = is_key_loaded(pamh, dataset);
991 if (!was_loaded) {
992 int ret = decrypt_mount(pamh, config, dataset,
993 ctx->old_pass, B_FALSE);
994 if (ret == -1) {
995 pam_syslog(pamh, LOG_ERR,
996 "failed to load key for '%s' during "
997 "password change", dataset);
998 return (-1);
999 }
1000 }
1001 int ret = change_key(pamh, dataset, ctx->new_pass);
1002 if (ret == -1) {
1003 pam_syslog(pamh, LOG_ERR,
1004 "failed to change key for dataset '%s'", dataset);
1005 }
1006 if (!was_loaded)
1007 unmount_unload(pamh, dataset, config);
1008 return (ret);
1009 }
1010
1011 __attribute__((visibility("default")))
1012 PAM_EXTERN int
pam_sm_chauthtok(pam_handle_t * pamh,int flags,int argc,const char ** argv)1013 pam_sm_chauthtok(pam_handle_t *pamh, int flags,
1014 int argc, const char **argv)
1015 {
1016 if (geteuid() != 0) {
1017 pam_syslog(pamh, LOG_ERR,
1018 "Cannot zfs_mount when not being root.");
1019 return (PAM_PERM_DENIED);
1020 }
1021 zfs_key_config_t config;
1022 if (zfs_key_config_load(pamh, &config, argc, argv) != PAM_SUCCESS) {
1023 return (PAM_SERVICE_ERR);
1024 }
1025 if (config.uid < config.uid_min || config.uid > config.uid_max) {
1026 zfs_key_config_free(&config);
1027 return (PAM_SERVICE_ERR);
1028 }
1029 const pw_password_t *old_token = pw_get(pamh,
1030 PAM_OLDAUTHTOK, OLD_PASSWORD_VAR_NAME);
1031
1032 if (!old_token) {
1033 pam_syslog(pamh, LOG_ERR,
1034 "old password from PAM stack is null");
1035 zfs_key_config_free(&config);
1036 return (PAM_SERVICE_ERR);
1037 }
1038
1039 if (pam_zfs_init(pamh) != 0) {
1040 zfs_key_config_free(&config);
1041 return (PAM_SERVICE_ERR);
1042 }
1043
1044 /* First verify old password works for all datasets */
1045 int ret = foreach_dataset(pamh, &config, auth_callback,
1046 (void *)old_token->value);
1047 if (ret < 0) {
1048 pam_syslog(pamh, LOG_ERR, "old token mismatch");
1049 pam_zfs_free();
1050 zfs_key_config_free(&config);
1051 return (PAM_PERM_DENIED);
1052 }
1053
1054 if ((flags & PAM_UPDATE_AUTHTOK) != 0) {
1055 const pw_password_t *token = pw_get(pamh, PAM_AUTHTOK,
1056 PASSWORD_VAR_NAME);
1057 if (token == NULL) {
1058 pam_syslog(pamh, LOG_ERR, "new password unavailable");
1059 pam_zfs_free();
1060 zfs_key_config_free(&config);
1061 pw_clear(pamh, OLD_PASSWORD_VAR_NAME);
1062 return (PAM_SERVICE_ERR);
1063 }
1064
1065 chauthtok_ctx_t ctx = {
1066 .old_pass = old_token->value,
1067 .new_pass = token->value
1068 };
1069
1070 ret = foreach_dataset(pamh, &config, chauthtok_callback, &ctx);
1071 pam_zfs_free();
1072 zfs_key_config_free(&config);
1073
1074 if (ret < 0) {
1075 pw_clear(pamh, OLD_PASSWORD_VAR_NAME);
1076 pw_clear(pamh, PASSWORD_VAR_NAME);
1077 return (PAM_SERVICE_ERR);
1078 }
1079
1080 if (pw_clear(pamh, OLD_PASSWORD_VAR_NAME) == -1 ||
1081 pw_clear(pamh, PASSWORD_VAR_NAME) == -1) {
1082 return (PAM_SERVICE_ERR);
1083 }
1084 } else {
1085 pam_zfs_free();
1086 zfs_key_config_free(&config);
1087 }
1088 return (PAM_SUCCESS);
1089 }
1090
1091 /* Callback for session open - decrypt and mount */
1092 static int
open_session_callback(pam_handle_t * pamh,zfs_key_config_t * config,const char * dataset,void * data)1093 open_session_callback(pam_handle_t *pamh, zfs_key_config_t *config,
1094 const char *dataset, void *data)
1095 {
1096 const char *passphrase = data;
1097 return (decrypt_mount(pamh, config, dataset, passphrase, B_FALSE));
1098 }
1099
1100 /* Callback for session close - unmount and unload */
1101 static int
close_session_callback(pam_handle_t * pamh,zfs_key_config_t * config,const char * dataset,void * data)1102 close_session_callback(pam_handle_t *pamh, zfs_key_config_t *config,
1103 const char *dataset, void *data)
1104 {
1105 (void) data;
1106 return (unmount_unload(pamh, dataset, config));
1107 }
1108
1109 PAM_EXTERN int
pam_sm_open_session(pam_handle_t * pamh,int flags,int argc,const char ** argv)1110 pam_sm_open_session(pam_handle_t *pamh, int flags,
1111 int argc, const char **argv)
1112 {
1113 (void) flags;
1114
1115 if (geteuid() != 0) {
1116 pam_syslog(pamh, LOG_ERR,
1117 "Cannot zfs_mount when not being root.");
1118 return (PAM_SUCCESS);
1119 }
1120 zfs_key_config_t config;
1121 if (zfs_key_config_load(pamh, &config, argc, argv) != PAM_SUCCESS) {
1122 return (PAM_SESSION_ERR);
1123 }
1124
1125 if (config.uid < config.uid_min || config.uid > config.uid_max) {
1126 zfs_key_config_free(&config);
1127 return (PAM_SUCCESS);
1128 }
1129
1130 int counter = zfs_key_config_modify_session_counter(pamh, &config, 1);
1131 if (counter != 1) {
1132 zfs_key_config_free(&config);
1133 return (PAM_SUCCESS);
1134 }
1135
1136 const pw_password_t *token = pw_get(pamh,
1137 PAM_AUTHTOK, PASSWORD_VAR_NAME);
1138 if (token == NULL) {
1139 zfs_key_config_free(&config);
1140 return (PAM_SESSION_ERR);
1141 }
1142 if (pam_zfs_init(pamh) != 0) {
1143 zfs_key_config_free(&config);
1144 return (PAM_SERVICE_ERR);
1145 }
1146
1147 int ret = foreach_dataset(pamh, &config, open_session_callback,
1148 (void *)token->value);
1149 pam_zfs_free();
1150 zfs_key_config_free(&config);
1151
1152 if (ret < 0) {
1153 return (PAM_SERVICE_ERR);
1154 }
1155 if (pw_clear(pamh, PASSWORD_VAR_NAME) == -1) {
1156 return (PAM_SERVICE_ERR);
1157 }
1158 return (PAM_SUCCESS);
1159
1160 }
1161
1162 __attribute__((visibility("default")))
1163 PAM_EXTERN int
pam_sm_close_session(pam_handle_t * pamh,int flags,int argc,const char ** argv)1164 pam_sm_close_session(pam_handle_t *pamh, int flags,
1165 int argc, const char **argv)
1166 {
1167 (void) flags;
1168
1169 if (geteuid() != 0) {
1170 pam_syslog(pamh, LOG_ERR,
1171 "Cannot zfs_mount when not being root.");
1172 return (PAM_SUCCESS);
1173 }
1174 zfs_key_config_t config;
1175 if (zfs_key_config_load(pamh, &config, argc, argv) != PAM_SUCCESS) {
1176 return (PAM_SESSION_ERR);
1177 }
1178 if (config.uid < config.uid_min || config.uid > config.uid_max) {
1179 zfs_key_config_free(&config);
1180 return (PAM_SUCCESS);
1181 }
1182
1183 int counter = zfs_key_config_modify_session_counter(pamh, &config, -1);
1184 if (counter != 0) {
1185 zfs_key_config_free(&config);
1186 return (PAM_SUCCESS);
1187 }
1188
1189 if (config.unmount_and_unload) {
1190 if (pam_zfs_init(pamh) != 0) {
1191 zfs_key_config_free(&config);
1192 return (PAM_SERVICE_ERR);
1193 }
1194
1195 int ret = foreach_dataset(pamh, &config,
1196 close_session_callback, NULL);
1197 pam_zfs_free();
1198
1199 if (ret < 0) {
1200 zfs_key_config_free(&config);
1201 return (PAM_SESSION_ERR);
1202 }
1203 }
1204
1205 zfs_key_config_free(&config);
1206 return (PAM_SUCCESS);
1207 }
1208