Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions contrib/debian/openzfs-libpam-zfs.install
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
usr/lib/*/security/pam_zfs_key.so
usr/share/man/man8/pam_zfs_key.8
usr/share/pam-configs/zfs_key
278 changes: 190 additions & 88 deletions contrib/pam_zfs_key/pam_zfs_key.c
Original file line number Diff line number Diff line change
Expand Up @@ -754,6 +754,82 @@ zfs_key_config_get_dataset(pam_handle_t *pamh, zfs_key_config_t *config)
return (ret);
}

/*
* Callback type for foreach_dataset.
* Returns 0 on success, -1 on failure.
*/
typedef int (*dataset_callback_t)(pam_handle_t *, zfs_key_config_t *,
const char *, void *);

/*
* Iterate over comma-separated homes prefixes and call callback for each
* existing dataset. Returns number of successful callbacks, or -1 if none
* succeeded.
*/
static int
foreach_dataset(pam_handle_t *pamh, zfs_key_config_t *config,
dataset_callback_t callback, void *data)
{
if (config->homes_prefix == NULL)
return (-1);

/* Check if this is a comma-separated list */
if (strchr(config->homes_prefix, ',') == NULL) {
/* Single home - use existing logic */
char *dataset = zfs_key_config_get_dataset(pamh, config);
if (dataset == NULL)
return (-1);
int ret = callback(pamh, config, dataset, data);
free(dataset);
return (ret == 0 ? 1 : -1);
}

/* Multiple homes - parse and iterate */
pam_syslog(pamh, LOG_DEBUG,
"processing multiple home prefixes: %s", config->homes_prefix);

char *homes_copy = strdup(config->homes_prefix);
if (homes_copy == NULL)
return (-1);

char *saved_prefix = config->homes_prefix;
char *saveptr;
char *token = strtok_r(homes_copy, ",", &saveptr);
int success_count = 0;
boolean_t failed = B_FALSE;

while (token != NULL) {
/* Temporarily set homes_prefix to this single prefix */
config->homes_prefix = token;
char *dataset = zfs_key_config_get_dataset(pamh, config);
if (dataset != NULL) {
pam_syslog(pamh, LOG_DEBUG,
"processing dataset '%s' for prefix '%s'",
dataset, token);
if (callback(pamh, config, dataset, data) == 0) {
success_count++;
} else {
failed = B_TRUE;
pam_syslog(pamh, LOG_WARNING,
"operation failed for dataset '%s'",
dataset);
}
free(dataset);
} else {
pam_syslog(pamh, LOG_DEBUG,
"no dataset found for prefix '%s', skip", token);
}
token = strtok_r(NULL, ",", &saveptr);
}

config->homes_prefix = saved_prefix;
free(homes_copy);
pam_syslog(pamh, LOG_DEBUG,
"processed %d datasets, %s",
success_count, failed ? "with failures" : "all successful");
return (!failed && success_count > 0 ? success_count : -1);
}

static int
zfs_key_config_modify_session_counter(pam_handle_t *pamh,
zfs_key_config_t *config, int delta)
Expand Down Expand Up @@ -825,6 +901,15 @@ zfs_key_config_modify_session_counter(pam_handle_t *pamh,
return (counter_value);
}

/* Callback for authentication - verify password works (noop mode) */
static int
auth_callback(pam_handle_t *pamh, zfs_key_config_t *config,
const char *dataset, void *data)
{
const char *passphrase = data;
return (decrypt_mount(pamh, config, dataset, passphrase, B_TRUE));
}

__attribute__((visibility("default")))
PAM_EXTERN int
pam_sm_authenticate(pam_handle_t *pamh, int flags,
Expand Down Expand Up @@ -857,21 +942,14 @@ pam_sm_authenticate(pam_handle_t *pamh, int flags,
zfs_key_config_free(&config);
return (PAM_SERVICE_ERR);
}
char *dataset = zfs_key_config_get_dataset(pamh, &config);
if (!dataset) {
pam_zfs_free();
zfs_key_config_free(&config);
return (PAM_SERVICE_ERR);
}
if (decrypt_mount(pamh, &config, dataset, token->value, B_TRUE) == -1) {
free(dataset);
pam_zfs_free();
zfs_key_config_free(&config);
return (PAM_AUTH_ERR);
}
free(dataset);

int ret = foreach_dataset(pamh, &config, auth_callback,
(void *)token->value);
pam_zfs_free();
zfs_key_config_free(&config);
if (ret < 0) {
return (PAM_AUTH_ERR);
}
return (PAM_SUCCESS);
}

Expand All @@ -884,6 +962,39 @@ pam_sm_setcred(pam_handle_t *pamh, int flags,
return (PAM_SUCCESS);
}

/* Context for password change callback */
typedef struct {
const char *old_pass;
const char *new_pass;
} chauthtok_ctx_t;

/* Callback for password change */
static int
chauthtok_callback(pam_handle_t *pamh, zfs_key_config_t *config,
const char *dataset, void *data)
{
chauthtok_ctx_t *ctx = data;
int was_loaded = is_key_loaded(pamh, dataset);
if (!was_loaded) {
int ret = decrypt_mount(pamh, config, dataset,
ctx->old_pass, B_FALSE);
if (ret == -1) {
pam_syslog(pamh, LOG_ERR,
"failed to load key for '%s' during "
"password change", dataset);
return (-1);
}
}
int ret = change_key(pamh, dataset, ctx->new_pass);
if (ret == -1) {
pam_syslog(pamh, LOG_ERR,
"failed to change key for dataset '%s'", dataset);
}
if (!was_loaded)
unmount_unload(pamh, dataset, config);
return (ret);
}

__attribute__((visibility("default")))
PAM_EXTERN int
pam_sm_chauthtok(pam_handle_t *pamh, int flags,
Expand All @@ -904,34 +1015,27 @@ pam_sm_chauthtok(pam_handle_t *pamh, int flags,
}
const pw_password_t *old_token = pw_get(pamh,
PAM_OLDAUTHTOK, OLD_PASSWORD_VAR_NAME);
{
if (pam_zfs_init(pamh) != 0) {
zfs_key_config_free(&config);
return (PAM_SERVICE_ERR);
}
char *dataset = zfs_key_config_get_dataset(pamh, &config);
if (!dataset) {
pam_zfs_free();
zfs_key_config_free(&config);
return (PAM_SERVICE_ERR);
}
if (!old_token) {
pam_syslog(pamh, LOG_ERR,
"old password from PAM stack is null");
free(dataset);
pam_zfs_free();
zfs_key_config_free(&config);
return (PAM_SERVICE_ERR);
}
if (decrypt_mount(pamh, &config, dataset,
old_token->value, B_TRUE) == -1) {
pam_syslog(pamh, LOG_ERR,
"old token mismatch");
free(dataset);
pam_zfs_free();
zfs_key_config_free(&config);
return (PAM_PERM_DENIED);
}

if (!old_token) {
pam_syslog(pamh, LOG_ERR,
"old password from PAM stack is null");
zfs_key_config_free(&config);
return (PAM_SERVICE_ERR);
}

if (pam_zfs_init(pamh) != 0) {
zfs_key_config_free(&config);
return (PAM_SERVICE_ERR);
}

/* First verify old password works for all datasets */
int ret = foreach_dataset(pamh, &config, auth_callback,
(void *)old_token->value);
if (ret < 0) {
pam_syslog(pamh, LOG_ERR, "old token mismatch");
pam_zfs_free();
zfs_key_config_free(&config);
return (PAM_PERM_DENIED);
}

if ((flags & PAM_UPDATE_AUTHTOK) != 0) {
Expand All @@ -944,41 +1048,51 @@ pam_sm_chauthtok(pam_handle_t *pamh, int flags,
pw_clear(pamh, OLD_PASSWORD_VAR_NAME);
return (PAM_SERVICE_ERR);
}
char *dataset = zfs_key_config_get_dataset(pamh, &config);
if (!dataset) {
pam_zfs_free();
zfs_key_config_free(&config);
pw_clear(pamh, OLD_PASSWORD_VAR_NAME);
pw_clear(pamh, PASSWORD_VAR_NAME);
return (PAM_SERVICE_ERR);
}
int was_loaded = is_key_loaded(pamh, dataset);
if (!was_loaded && decrypt_mount(pamh, &config, dataset,
old_token->value, B_FALSE) == -1) {
free(dataset);
pam_zfs_free();
zfs_key_config_free(&config);

chauthtok_ctx_t ctx = {
.old_pass = old_token->value,
.new_pass = token->value
};

ret = foreach_dataset(pamh, &config, chauthtok_callback, &ctx);
pam_zfs_free();
zfs_key_config_free(&config);

if (ret < 0) {
pw_clear(pamh, OLD_PASSWORD_VAR_NAME);
pw_clear(pamh, PASSWORD_VAR_NAME);
return (PAM_SERVICE_ERR);
}
int changed = change_key(pamh, dataset, token->value);
if (!was_loaded) {
unmount_unload(pamh, dataset, &config);
}
free(dataset);
pam_zfs_free();
zfs_key_config_free(&config);

if (pw_clear(pamh, OLD_PASSWORD_VAR_NAME) == -1 ||
pw_clear(pamh, PASSWORD_VAR_NAME) == -1 || changed == -1) {
pw_clear(pamh, PASSWORD_VAR_NAME) == -1) {
return (PAM_SERVICE_ERR);
}
} else {
pam_zfs_free();
zfs_key_config_free(&config);
}
return (PAM_SUCCESS);
}

/* Callback for session open - decrypt and mount */
static int
open_session_callback(pam_handle_t *pamh, zfs_key_config_t *config,
const char *dataset, void *data)
{
const char *passphrase = data;
return (decrypt_mount(pamh, config, dataset, passphrase, B_FALSE));
}

/* Callback for session close - unmount and unload */
static int
close_session_callback(pam_handle_t *pamh, zfs_key_config_t *config,
const char *dataset, void *data)
{
(void) data;
return (unmount_unload(pamh, dataset, config));
}

PAM_EXTERN int
pam_sm_open_session(pam_handle_t *pamh, int flags,
int argc, const char **argv)
Expand Down Expand Up @@ -1016,22 +1130,15 @@ pam_sm_open_session(pam_handle_t *pamh, int flags,
zfs_key_config_free(&config);
return (PAM_SERVICE_ERR);
}
char *dataset = zfs_key_config_get_dataset(pamh, &config);
if (!dataset) {
pam_zfs_free();
zfs_key_config_free(&config);
return (PAM_SERVICE_ERR);
}
if (decrypt_mount(pamh, &config, dataset,
token->value, B_FALSE) == -1) {
free(dataset);
pam_zfs_free();
zfs_key_config_free(&config);
return (PAM_SERVICE_ERR);
}
free(dataset);

int ret = foreach_dataset(pamh, &config, open_session_callback,
(void *)token->value);
pam_zfs_free();
zfs_key_config_free(&config);

if (ret < 0) {
return (PAM_SERVICE_ERR);
}
if (pw_clear(pamh, PASSWORD_VAR_NAME) == -1) {
return (PAM_SERVICE_ERR);
}
Expand Down Expand Up @@ -1071,20 +1178,15 @@ pam_sm_close_session(pam_handle_t *pamh, int flags,
zfs_key_config_free(&config);
return (PAM_SERVICE_ERR);
}
char *dataset = zfs_key_config_get_dataset(pamh, &config);
if (!dataset) {
pam_zfs_free();
zfs_key_config_free(&config);
return (PAM_SESSION_ERR);
}
if (unmount_unload(pamh, dataset, &config) == -1) {
free(dataset);
pam_zfs_free();

int ret = foreach_dataset(pamh, &config,
close_session_callback, NULL);
pam_zfs_free();

if (ret < 0) {
zfs_key_config_free(&config);
return (PAM_SESSION_ERR);
}
free(dataset);
pam_zfs_free();
}

zfs_key_config_free(&config);
Expand Down
1 change: 1 addition & 0 deletions man/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ endif

if BUILD_LINUX
dist_man_MANS += \
%D%/man8/pam_zfs_key.8 \
%D%/man8/zfs-unzone.8 \
%D%/man8/zfs-zone.8
endif
Expand Down
Loading
Loading