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