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