diff options
Diffstat (limited to 'src/responder/pam')
-rw-r--r-- | src/responder/pam/pam_helpers.c | 162 | ||||
-rw-r--r-- | src/responder/pam/pam_helpers.h | 42 | ||||
-rw-r--r-- | src/responder/pam/pam_prompting_config.c | 309 | ||||
-rw-r--r-- | src/responder/pam/pamsrv.c | 529 | ||||
-rw-r--r-- | src/responder/pam/pamsrv.h | 173 | ||||
-rw-r--r-- | src/responder/pam/pamsrv_cmd.c | 3085 | ||||
-rw-r--r-- | src/responder/pam/pamsrv_dp.c | 106 | ||||
-rw-r--r-- | src/responder/pam/pamsrv_gssapi.c | 1043 | ||||
-rw-r--r-- | src/responder/pam/pamsrv_p11.c | 1312 | ||||
-rw-r--r-- | src/responder/pam/pamsrv_passkey.c | 1438 | ||||
-rw-r--r-- | src/responder/pam/pamsrv_passkey.h | 83 |
11 files changed, 8282 insertions, 0 deletions
diff --git a/src/responder/pam/pam_helpers.c b/src/responder/pam/pam_helpers.c new file mode 100644 index 0000000..d0a79c5 --- /dev/null +++ b/src/responder/pam/pam_helpers.c @@ -0,0 +1,162 @@ +/* + SSSD + + Authors: + Stephen Gallagher <sgallagh@redhat.com> + + Copyright (C) 2011 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + + +#include "src/responder/pam/pam_helpers.h" + +struct pam_initgr_table_ctx { + hash_table_t *id_table; + char *name; +}; + +static void pam_initgr_cache_remove(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, + void *pvt); + +errno_t pam_initgr_cache_set(struct tevent_context *ev, + hash_table_t *id_table, + char *name, + long timeout) +{ + errno_t ret; + hash_key_t key; + hash_value_t val; + int hret; + struct tevent_timer *te; + struct timeval tv; + struct pam_initgr_table_ctx *table_ctx; + + ret = pam_initgr_check_timeout(id_table, name); + if (ret == EOK) { + /* user is already in the cache */ + goto done; + } + + table_ctx = talloc_zero(id_table, struct pam_initgr_table_ctx); + if (!table_ctx) return ENOMEM; + + table_ctx->id_table = id_table; + table_ctx->name = talloc_strdup(table_ctx, name); + if (!table_ctx->name) { + ret = ENOMEM; + goto done; + } + + key.type = HASH_KEY_STRING; + key.str = name; + + /* The value isn't relevant, since we're using + * a timer to remove the entry. + */ + val.type = HASH_VALUE_UNDEF; + + hret = hash_enter(id_table, &key, &val); + if (hret != HASH_SUCCESS) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not update initgr cache for [%s]: [%s]\n", + name, hash_error_string(hret)); + ret = EIO; + goto done; + } else { + DEBUG(SSSDBG_TRACE_INTERNAL, + "[%s] added to PAM initgroup cache\n", + name); + } + + /* Create a timer event to remove the entry from the cache */ + tv = tevent_timeval_current_ofs(timeout, 0); + te = tevent_add_timer(ev, table_ctx, tv, + pam_initgr_cache_remove, + table_ctx); + if (!te) { + ret = ENOMEM; + goto done; + } + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(table_ctx); + } + return ret; +} + +static void pam_initgr_cache_remove(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, + void *pvt) +{ + int hret; + hash_key_t key; + + struct pam_initgr_table_ctx *table_ctx = + talloc_get_type(pvt, struct pam_initgr_table_ctx); + + key.type = HASH_KEY_STRING; + key.str = table_ctx->name; + + hret = hash_delete(table_ctx->id_table, &key); + if (hret != HASH_SUCCESS + && hret != HASH_ERROR_KEY_NOT_FOUND) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not clear [%s] from initgr cache: [%s]\n", + table_ctx->name, + hash_error_string(hret)); + } else { + DEBUG(SSSDBG_TRACE_INTERNAL, + "[%s] removed from PAM initgroup cache\n", + table_ctx->name); + } + + talloc_free(table_ctx); +} + +errno_t pam_initgr_check_timeout(hash_table_t *id_table, + char *name) +{ + hash_key_t key; + hash_value_t val; + int hret; + + key.type = HASH_KEY_STRING; + key.str = name; + + hret = hash_lookup(id_table, &key, &val); + if (hret != HASH_SUCCESS + && hret != HASH_ERROR_KEY_NOT_FOUND) { + DEBUG(SSSDBG_TRACE_ALL, "Error searching user [%s] in PAM cache.\n", + name); + return EIO; + } else if (hret == HASH_ERROR_KEY_NOT_FOUND) { + DEBUG(SSSDBG_TRACE_ALL, "User [%s] not found in PAM cache.\n", name); + return ENOENT; + } + + /* If there's a value here, then the cache + * entry is still valid. + */ + DEBUG(SSSDBG_TRACE_INTERNAL, "User [%s] found in PAM cache.\n", name); + return EOK; +} + diff --git a/src/responder/pam/pam_helpers.h b/src/responder/pam/pam_helpers.h new file mode 100644 index 0000000..23fd308 --- /dev/null +++ b/src/responder/pam/pam_helpers.h @@ -0,0 +1,42 @@ +/* + SSSD + + Authors: + Stephen Gallagher <sgallagh@redhat.com> + + Copyright (C) 2011 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef PAM_HELPERS_H_ +#define PAM_HELPERS_H_ + +#include "util/util.h" + +#define CERT_AUTH_DEFAULT_MATCHING_RULE "KRB5:<EKU>clientAuth" + +errno_t pam_initgr_cache_set(struct tevent_context *ev, + hash_table_t *id_table, + char *name, + long timeout); + +/* Returns EOK if the cache is still valid + * Returns ENOENT if the user is not found or is expired + * May report other errors if the hash lookup fails. + */ +errno_t pam_initgr_check_timeout(hash_table_t *id_table, + char *name); + +#endif /* PAM_HELPERS_H_ */ diff --git a/src/responder/pam/pam_prompting_config.c b/src/responder/pam/pam_prompting_config.c new file mode 100644 index 0000000..7d0362f --- /dev/null +++ b/src/responder/pam/pam_prompting_config.c @@ -0,0 +1,309 @@ +/* + SSSD + + PAM Responder - helpers for PAM prompting configuration + + Copyright (C) Sumit Bose <sbose@redhat.com> 2019 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "util/util.h" +#include "util/sss_pam_data.h" +#include "confdb/confdb.h" +#include "sss_client/sss_cli.h" +#include "responder/pam/pamsrv.h" + +#define DEFAULT_PASSKEY_PROMPT_INTERACTIVE _("Insert your Passkey device, then press ENTER.") +#define DEFAULT_PASSKEY_PROMPT_TOUCH _("Please touch the device.") + +typedef errno_t (pam_set_prompting_fn_t)(TALLOC_CTX *, struct confdb_ctx *, + const char *, + struct prompt_config ***); + + +static errno_t pam_set_password_prompting_options(TALLOC_CTX *tmp_ctx, + struct confdb_ctx *cdb, + const char *section, + struct prompt_config ***pc_list) +{ + int ret; + char *value = NULL; + + ret = confdb_get_string(cdb, tmp_ctx, section, CONFDB_PC_PASSWORD_PROMPT, + NULL, &value); + if (ret == EOK && value != NULL) { + ret = pc_list_add_password(pc_list, value); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "pc_list_add_password failed.\n"); + } + return ret; + } + + return ENOENT; +} + +static errno_t pam_set_2fa_prompting_options(TALLOC_CTX *tmp_ctx, + struct confdb_ctx *cdb, + const char *section, + struct prompt_config ***pc_list) +{ + bool single_2fa_prompt = false; + char *first_prompt = NULL; + char *second_prompt = NULL; + int ret; + + + ret = confdb_get_bool(cdb, section, CONFDB_PC_2FA_SINGLE_PROMPT, false, + &single_2fa_prompt); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "confdb_get_bool failed, using defaults"); + } + ret = confdb_get_string(cdb, tmp_ctx, section, CONFDB_PC_2FA_1ST_PROMPT, + NULL, &first_prompt); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "confdb_get_string failed, using defaults"); + } + + if (single_2fa_prompt) { + ret = pc_list_add_2fa_single(pc_list, first_prompt); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "pc_list_add_2fa_single failed.\n"); + } + return ret; + } else { + ret = confdb_get_string(cdb, tmp_ctx, section, CONFDB_PC_2FA_2ND_PROMPT, + NULL, &second_prompt); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "confdb_get_string failed, using defaults"); + } + + ret = pc_list_add_2fa(pc_list, first_prompt, second_prompt); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "pc_list_add_2fa failed.\n"); + } + return ret; + } + + return ENOENT; +} + +static errno_t pam_set_passkey_prompting_options(TALLOC_CTX *tmp_ctx, + struct confdb_ctx *cdb, + const char *section, + struct prompt_config ***pc_list) +{ + bool passkey_interactive = false; + char *passkey_interactive_prompt = NULL; + bool passkey_touch = false; + char *passkey_touch_prompt = NULL; + int ret; + + + ret = confdb_get_bool(cdb, section, CONFDB_PC_PASSKEY_INTERACTIVE, false, + &passkey_interactive); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "confdb_get_bool failed, using defaults"); + } + + if (passkey_interactive) { + ret = confdb_get_string(cdb, tmp_ctx, section, CONFDB_PC_PASSKEY_INTERACTIVE_PROMPT, + DEFAULT_PASSKEY_PROMPT_INTERACTIVE, &passkey_interactive_prompt); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "confdb_get_string failed, using defaults"); + } + } + + ret = confdb_get_bool(cdb, section, CONFDB_PC_PASSKEY_TOUCH, false, + &passkey_touch); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "confdb_get_bool failed, using defaults"); + } + + if (passkey_touch) { + ret = confdb_get_string(cdb, tmp_ctx, section, CONFDB_PC_PASSKEY_TOUCH_PROMPT, + DEFAULT_PASSKEY_PROMPT_TOUCH, &passkey_touch_prompt); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "confdb_get_string failed, using defaults"); + } + } + + ret = pc_list_add_passkey(pc_list, passkey_interactive_prompt, passkey_touch_prompt); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "pc_list_add_passkey_touch failed.\n"); + } + + return ret; +} +static errno_t pam_set_prompting_options(struct confdb_ctx *cdb, + const char *service_name, + char **sections, + int num_sections, + const char *section_path, + pam_set_prompting_fn_t *setter, + struct prompt_config ***pc_list) +{ + char *dummy; + size_t c; + bool global = false; + bool specific = false; + char *section = NULL; + int ret; + char *last; + TALLOC_CTX *tmp_ctx = NULL; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + + dummy = talloc_asprintf(tmp_ctx, "%s/%s", section_path, + service_name); + for (c = 0; c < num_sections; c++) { + if (strcmp(sections[c], section_path) == 0) { + global = true; + } + if (dummy != NULL && strcmp(sections[c], dummy) == 0) { + specific = true; + } + } + + section = talloc_asprintf(tmp_ctx, "%s/%s", CONFDB_PC_CONF_ENTRY, dummy); + if (section == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = ENOENT; + if (specific) { + ret = setter(tmp_ctx, cdb, section, pc_list); + } + if (global && ret == ENOENT) { + last = strrchr(section, '/'); + if (last != NULL) { + *last = '\0'; + ret = setter(tmp_ctx, cdb, section, pc_list); + } + } + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "setter failed.\n"); + goto done; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t pam_eval_prompting_config(struct pam_ctx *pctx, struct pam_data *pd) +{ + int ret; + struct prompt_config **pc_list = NULL; + int resp_len; + uint8_t *resp_data = NULL; + struct pam_resp_auth_type types; + + if (pctx->num_prompting_config_sections == 0) { + DEBUG(SSSDBG_TRACE_ALL, "No prompting configuration found.\n"); + return EOK; + } + + ret = pam_get_auth_types(pd, &types); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to get authentication types\n"); + goto done; + } + + if (types.passkey_auth) { + ret = pam_set_prompting_options(pctx->rctx->cdb, pd->service, + pctx->prompting_config_sections, + pctx->num_prompting_config_sections, + CONFDB_PC_TYPE_PASSKEY, + pam_set_passkey_prompting_options, + &pc_list); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "pam_set_prompting_options failed.\n"); + goto done; + } + } + + if (types.cert_auth) { + /* If certificate based authentication is possilbe, i.e. a Smartcard + * or similar with the mapped certificate is available we currently + * prefer this authentication type unconditionally. If other types + * should be used the Smartcard can be removed during authentication. + * Since there currently are no specific options for cert_auth we are + * done. */ + ret = EOK; + goto done; + } + + /* If OTP and password auth are possible we currently prefer OTP. */ + if (types.otp_auth) { + ret = pam_set_prompting_options(pctx->rctx->cdb, pd->service, + pctx->prompting_config_sections, + pctx->num_prompting_config_sections, + CONFDB_PC_TYPE_2FA, + pam_set_2fa_prompting_options, + &pc_list); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "pam_set_prompting_options failed.\n"); + goto done; + } + } + + if (types.password_auth) { + ret = pam_set_prompting_options(pctx->rctx->cdb, pd->service, + pctx->prompting_config_sections, + pctx->num_prompting_config_sections, + CONFDB_PC_TYPE_PASSWORD, + pam_set_password_prompting_options, + &pc_list); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "pam_set_prompting_options failed.\n"); + goto done; + } + } + + if (pc_list != NULL) { + ret = pam_get_response_prompt_config(pc_list, &resp_len, &resp_data); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "pam_get_response_prompt_config failed.\n"); + goto done; + } + + ret = pam_add_response(pd, SSS_PAM_PROMPT_CONFIG, resp_len, resp_data); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "pam_add_response failed.\n"); + goto done; + } + } + + ret = EOK; +done: + free(resp_data); + pc_list_free(pc_list); + + return ret; +} diff --git a/src/responder/pam/pamsrv.c b/src/responder/pam/pamsrv.c new file mode 100644 index 0000000..73ebb0a --- /dev/null +++ b/src/responder/pam/pamsrv.c @@ -0,0 +1,529 @@ +/* + SSSD + + PAM Responder + + Copyright (C) Simo Sorce <ssorce@redhat.com> 2009 + Copyright (C) Sumit Bose <sbose@redhat.com> 2009 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <string.h> +#include <sys/time.h> +#include <errno.h> +#include <popt.h> +#include <dbus/dbus.h> + +#include "config.h" +#include "util/util.h" +#include "db/sysdb.h" +#include "confdb/confdb.h" +#include "responder/common/responder_packet.h" +#include "providers/data_provider.h" +#include "responder/pam/pamsrv.h" +#include "responder/common/negcache.h" +#include "sss_iface/sss_iface_async.h" + +#define DEFAULT_PAM_FD_LIMIT 8192 +#define ALL_UIDS_ALLOWED "all" +#define ALL_DOMAINS_ARE_PUBLIC "all" +#define NO_DOMAINS_ARE_PUBLIC "none" +#define DEFAULT_ALLOWED_UIDS ALL_UIDS_ALLOWED +#define DEFAULT_PAM_CERT_AUTH false +#define DEFAULT_PAM_PASSKEY_AUTH true +#define DEFAULT_PAM_CERT_DB_PATH SYSCONFDIR"/sssd/pki/sssd_auth_ca_db.pem" +#define DEFAULT_PAM_INITGROUPS_SCHEME "no_session" + +static errno_t get_trusted_uids(struct pam_ctx *pctx) +{ + char *uid_str; + errno_t ret; + + ret = confdb_get_string(pctx->rctx->cdb, pctx->rctx, + CONFDB_PAM_CONF_ENTRY, CONFDB_PAM_TRUSTED_USERS, + DEFAULT_ALLOWED_UIDS, &uid_str); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to get allowed UIDs.\n"); + goto done; + } + + if (strcmp(uid_str, ALL_UIDS_ALLOWED) == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "All UIDs are allowed.\n"); + pctx->trusted_uids_count = 0; + } else { + ret = csv_string_to_uid_array(pctx->rctx, uid_str, + &pctx->trusted_uids_count, + &pctx->trusted_uids); + } + + talloc_free(uid_str); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to set allowed UIDs.\n"); + goto done; + } + +done: + return ret; +} + +static errno_t get_public_domains(struct pam_ctx *pctx) +{ + char *domains_str = NULL; + errno_t ret; + + ret = confdb_get_string(pctx->rctx->cdb, pctx->rctx, + CONFDB_PAM_CONF_ENTRY, CONFDB_PAM_PUBLIC_DOMAINS, + NO_DOMAINS_ARE_PUBLIC, &domains_str); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to get allowed UIDs.\n"); + goto done; + } + + if (strcmp(domains_str, ALL_DOMAINS_ARE_PUBLIC) == 0) { /* all */ + /* copy all domains */ + ret = get_dom_names(pctx, + pctx->rctx->domains, + &pctx->public_domains, + &pctx->public_domains_count); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "get_dom_names failed.\n"); + goto done; + } + } else if (strcmp(domains_str, NO_DOMAINS_ARE_PUBLIC) == 0) { /* none */ + pctx->public_domains = NULL; + pctx->public_domains_count = 0; + } else { + ret = split_on_separator(pctx, domains_str, ',', true, false, + &pctx->public_domains, + &pctx->public_domains_count); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "split_on_separator failed [%d][%s].\n", + ret, strerror(ret)); + goto done; + } + } + + ret = EOK; + +done: + talloc_free(domains_str); + return ret; +} + +static errno_t get_app_services(struct pam_ctx *pctx) +{ + errno_t ret; + + ret = confdb_get_string_as_list(pctx->rctx->cdb, pctx, + CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_APP_SERVICES, + &pctx->app_services); + if (ret == ENOENT) { + pctx->app_services = talloc_zero_array(pctx, char *, 1); + if (pctx->app_services == NULL) { + return ENOMEM; + } + /* Allocating an empty array makes it easier for the consumer + * to iterate over it + */ + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot read "CONFDB_PAM_APP_SERVICES" [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + return EOK; +} + +static void pam_get_domains_callback(void *pvt) +{ + struct pam_ctx *pctx; + int ret; + + pctx = talloc_get_type(pvt, struct pam_ctx); + ret = p11_refresh_certmap_ctx(pctx, pctx->rctx->domains); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "p11_refresh_certmap_ctx failed.\n"); + } +} + +static int pam_process_init(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct confdb_ctx *cdb, + int pipe_fd, int priv_pipe_fd) +{ + struct resp_ctx *rctx; + struct sss_cmd_table *pam_cmds; + struct pam_ctx *pctx; + int ret; + int id_timeout; + int fd_limit; + char *tmpstr = NULL; + + pam_cmds = get_pam_cmds(); + ret = sss_process_init(mem_ctx, ev, cdb, + pam_cmds, + SSS_PAM_SOCKET_NAME, pipe_fd, + SSS_PAM_PRIV_SOCKET_NAME, priv_pipe_fd, + CONFDB_PAM_CONF_ENTRY, + SSS_BUS_PAM, SSS_PAM_SBUS_SERVICE_NAME, + sss_connection_setup, + &rctx); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "sss_process_init() failed\n"); + return ret; + } + + pctx = talloc_zero(rctx, struct pam_ctx); + if (!pctx) { + ret = ENOMEM; + goto done; + } + + pctx->rctx = rctx; + pctx->rctx->pvt_ctx = pctx; + + ret = get_trusted_uids(pctx); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "get_trusted_uids failed: %d:[%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = get_public_domains(pctx); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "get_public_domains failed: %d:[%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = get_app_services(pctx); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "get_app_services failed: %d:[%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + /* Set up the PAM identity timeout */ + ret = confdb_get_int(cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_ID_TIMEOUT, 5, + &id_timeout); + if (ret != EOK) goto done; + + pctx->id_timeout = (size_t)id_timeout; + + ret = sss_ncache_prepopulate(pctx->rctx->ncache, cdb, pctx->rctx); + if (ret != EOK) { + goto done; + } + + /* Create table for initgroup lookups */ + ret = sss_hash_create(pctx, 0, &pctx->id_table); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Could not create initgroups hash table: [%s]\n", + strerror(ret)); + goto done; + } + + /* Set up file descriptor limits */ + ret = confdb_get_int(pctx->rctx->cdb, + CONFDB_PAM_CONF_ENTRY, + CONFDB_SERVICE_FD_LIMIT, + DEFAULT_PAM_FD_LIMIT, + &fd_limit); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to set up file descriptor limit\n"); + goto done; + } + responder_set_fd_limit(fd_limit); + + ret = schedule_get_domains_task(rctx, rctx->ev, rctx, pctx->rctx->ncache, + pam_get_domains_callback, pctx); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "schedule_get_domains_tasks failed.\n"); + goto done; + } + + /* Check if there is a prompting configuration */ + pctx->prompting_config_sections = NULL; + pctx->num_prompting_config_sections = 0; + ret = confdb_get_sub_sections(pctx, pctx->rctx->cdb, CONFDB_PC_CONF_ENTRY, + &pctx->prompting_config_sections, + &pctx->num_prompting_config_sections); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "confdb_get_sub_sections failed, not fatal.\n"); + } + + /* Check if certificate based authentication is enabled */ + ret = confdb_get_bool(pctx->rctx->cdb, + CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_CERT_AUTH, + DEFAULT_PAM_CERT_AUTH, + &pctx->cert_auth); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to determine get cert db path.\n"); + goto done; + } + + if (pctx->cert_auth) { + ret = p11_child_init(pctx); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "p11_child_init failed.\n"); + goto done; + } + + ret = confdb_get_string(pctx->rctx->cdb, pctx, + CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_CERT_DB_PATH, + DEFAULT_PAM_CERT_DB_PATH, + &pctx->ca_db); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to determine if certificate based authentication is " \ + "enabled or not.\n"); + goto done; + } + + } + + /* Check if passkey authentication is enabled */ + ret = confdb_get_bool(pctx->rctx->cdb, + CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_PASSKEY_AUTH, + DEFAULT_PAM_PASSKEY_AUTH, + &pctx->passkey_auth); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to check if passkey authentication is " \ + "enabled.\n"); + goto done; + } + + if (pctx->cert_auth + || pctx->passkey_auth + || pctx->num_prompting_config_sections != 0) { + ret = create_preauth_indicator(); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to create pre-authentication indicator file, " + "Smartcard/passkey authentication or configured prompting might " + "not work as expected.\n"); + } + } + + ret = confdb_get_string(pctx->rctx->cdb, pctx, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_INITGROUPS_SCHEME, + DEFAULT_PAM_INITGROUPS_SCHEME, &tmpstr); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to determine initgroups scheme.\n"); + goto done; + } + DEBUG(SSSDBG_TRACE_INTERNAL, "Found value [%s] for option [%s].\n", tmpstr, + CONFDB_PAM_INITGROUPS_SCHEME); + + if (tmpstr == NULL) { + pctx->initgroups_scheme = PAM_INITGR_NO_SESSION; + } else { + pctx->initgroups_scheme = pam_initgroups_string_to_enum(tmpstr); + if (pctx->initgroups_scheme == PAM_INITGR_INVALID) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unknown value [%s] for option %s.\n", + tmpstr, CONFDB_PAM_INITGROUPS_SCHEME); + ret = EINVAL; + goto done; + } + } + + ret = confdb_get_string(pctx->rctx->cdb, pctx, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_GSSAPI_SERVICES, "-", &tmpstr); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to determine gssapi services.\n"); + goto done; + } + DEBUG(SSSDBG_TRACE_INTERNAL, "Found value [%s] for option [%s].\n", tmpstr, + CONFDB_PAM_GSSAPI_SERVICES); + + if (tmpstr != NULL) { + ret = split_on_separator(pctx, tmpstr, ',', true, true, + &pctx->gssapi_services, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "split_on_separator() failed [%d]: [%s].\n", ret, + sss_strerror(ret)); + goto done; + } + } + + ret = confdb_get_bool(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_GSSAPI_CHECK_UPN, true, + &pctx->gssapi_check_upn); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to read %s [%d]: %s\n", + CONFDB_PAM_GSSAPI_CHECK_UPN, ret, sss_strerror(ret)); + goto done; + } + + ret = confdb_get_string(pctx->rctx->cdb, pctx, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_GSSAPI_INDICATORS_MAP, "-", &tmpstr); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to determine gssapi services.\n"); + goto done; + } + DEBUG(SSSDBG_TRACE_INTERNAL, "Found value [%s] for option [%s].\n", tmpstr, + CONFDB_PAM_GSSAPI_INDICATORS_MAP); + + if (tmpstr != NULL) { + ret = split_on_separator(pctx, tmpstr, ',', true, true, + &pctx->gssapi_indicators_map, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "split_on_separator() failed [%d]: [%s].\n", ret, + sss_strerror(ret)); + goto done; + } + } + + /* The responder is initialized. Now tell it to the monitor. */ + ret = sss_monitor_service_init(rctx, rctx->ev, SSS_BUS_PAM, + SSS_PAM_SBUS_SERVICE_NAME, + SSS_PAM_SBUS_SERVICE_VERSION, + MT_SVC_SERVICE, + &rctx->last_request_time, &rctx->mon_conn); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "fatal error setting up message bus\n"); + goto done; + } + + ret = sss_resp_register_service_iface(rctx); + if (ret != EOK) { + goto done; + } + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(rctx); + } + return ret; +} + +int main(int argc, const char *argv[]) +{ + int opt; + poptContext pc; + char *opt_logger = NULL; + struct main_context *main_ctx; + int ret; + uid_t uid = 0; + gid_t gid = 0; + int pipe_fd = -1; + int priv_pipe_fd = -1; + + struct poptOption long_options[] = { + POPT_AUTOHELP + SSSD_MAIN_OPTS + SSSD_LOGGER_OPTS + SSSD_SERVER_OPTS(uid, gid) + SSSD_RESPONDER_OPTS + POPT_TABLEEND + }; + + /* Set debug level to invalid value so we can decide if -d 0 was used. */ + debug_level = SSSDBG_INVALID; + + umask(DFL_RSP_UMASK); + + pc = poptGetContext(argv[0], argc, argv, long_options, 0); + while((opt = poptGetNextOpt(pc)) != -1) { + switch(opt) { + default: + fprintf(stderr, "\nInvalid option %s: %s\n\n", + poptBadOption(pc, 0), poptStrerror(opt)); + poptPrintUsage(pc, stderr, 0); + return 1; + } + } + + poptFreeContext(pc); + + /* set up things like debug, signals, daemonization, etc. */ + debug_log_file = "sssd_pam"; + DEBUG_INIT(debug_level, opt_logger); + + if (!is_socket_activated()) { + /* Create pipe file descriptors here before privileges are dropped + * in server_setup() */ + ret = create_pipe_fd(SSS_PAM_SOCKET_NAME, &pipe_fd, SCKT_RSP_UMASK); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "create_pipe_fd failed [%d]: %s.\n", + ret, sss_strerror(ret)); + return 2; + } + + ret = create_pipe_fd(SSS_PAM_PRIV_SOCKET_NAME, &priv_pipe_fd, + DFL_RSP_UMASK); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "create_pipe_fd failed (privileged pipe) [%d]: %s.\n", + ret, sss_strerror(ret)); + return 2; + } + } + + /* server_setup() might switch to an unprivileged user, so the permissions + * for p11_child.log have to be fixed first. */ + ret = chown_debug_file("p11_child", uid, gid); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot chown the p11_child debug file, " + "debugging might not work!\n"); + } + + ret = server_setup("pam", true, 0, uid, gid, CONFDB_PAM_CONF_ENTRY, + &main_ctx, false); + if (ret != EOK) return 2; + + ret = die_if_parent_died(); + if (ret != EOK) { + /* This is not fatal, don't return */ + DEBUG(SSSDBG_OP_FAILURE, + "Could not set up to exit when parent process does\n"); + } + + ret = pam_process_init(main_ctx, + main_ctx->event_ctx, + main_ctx->confdb_ctx, + pipe_fd, priv_pipe_fd); + if (ret != EOK) return 3; + + /* loop on main */ + server_loop(main_ctx); + + return 0; +} + diff --git a/src/responder/pam/pamsrv.h b/src/responder/pam/pamsrv.h new file mode 100644 index 0000000..7013a8e --- /dev/null +++ b/src/responder/pam/pamsrv.h @@ -0,0 +1,173 @@ +/* + Authors: + Simo Sorce <ssorce@redhat.com> + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2009 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __PAMSRV_H__ +#define __PAMSRV_H__ + +#include <security/pam_appl.h> +#include "util/util.h" +#include "responder/common/responder.h" +#include "responder/common/cache_req/cache_req.h" +#include "lib/certmap/sss_certmap.h" + +struct pam_auth_req; + +typedef void (pam_dp_callback_t)(struct pam_auth_req *preq); + +enum pam_initgroups_scheme { + PAM_INITGR_NEVER, + PAM_INITGR_NO_SESSION, + PAM_INITGR_ALWAYS, + PAM_INITGR_INVALID +}; + +struct pam_ctx { + struct resp_ctx *rctx; + time_t id_timeout; + hash_table_t *id_table; + size_t trusted_uids_count; + uid_t *trusted_uids; + + /* List of domains that are accessible even for untrusted users. */ + char **public_domains; + int public_domains_count; + + /* What services are permitted to access application domains */ + char **app_services; + + bool cert_auth; + char *ca_db; + struct sss_certmap_ctx *sss_certmap_ctx; + char **smartcard_services; + + /* parsed list of pam_response_filter option */ + char **pam_filter_opts; + + char **prompting_config_sections; + int num_prompting_config_sections; + + enum pam_initgroups_scheme initgroups_scheme; + + /* List of PAM services that are allowed to authenticate with GSSAPI. */ + char **gssapi_services; + /* List of authentication indicators associated with a PAM service */ + char **gssapi_indicators_map; + bool gssapi_check_upn; + bool passkey_auth; + struct pam_passkey_table_data *pk_table_data; +}; + +struct pam_auth_req { + struct cli_ctx *cctx; + struct sss_domain_info *domain; + enum cache_req_dom_type req_dom_type; + + struct pam_data *pd; + + pam_dp_callback_t *callback; + + bool is_uid_trusted; + void *data; + bool use_cached_auth; + /* whether cached authentication was tried and failed */ + bool cached_auth_failed; + + struct ldb_message *user_obj; + struct cert_auth_info *cert_list; + struct cert_auth_info *current_cert; + bool cert_auth_local; + + bool passkey_data_exists; + uint32_t client_id_num; +}; + +struct pam_resp_auth_type { + bool password_auth; + bool otp_auth; + bool cert_auth; + bool passkey_auth; +}; + +struct sss_cmd_table *get_pam_cmds(void); + +errno_t +pam_dp_send_req(struct pam_auth_req *preq); + +int pam_check_user_search(struct pam_auth_req *preq); +int pam_check_user_done(struct pam_auth_req *preq, int ret); +void pam_reply(struct pam_auth_req *preq); + +errno_t p11_child_init(struct pam_ctx *pctx); + +struct cert_auth_info; +const char *sss_cai_get_cert(struct cert_auth_info *i); +const char *sss_cai_get_token_name(struct cert_auth_info *i); +const char *sss_cai_get_module_name(struct cert_auth_info *i); +const char *sss_cai_get_key_id(struct cert_auth_info *i); +const char *sss_cai_get_label(struct cert_auth_info *i); +struct cert_auth_info *sss_cai_get_next(struct cert_auth_info *i); +struct ldb_result *sss_cai_get_cert_user_objs(struct cert_auth_info *i); +void sss_cai_set_cert_user_objs(struct cert_auth_info *i, + struct ldb_result *cert_user_objs); +void sss_cai_check_users(struct cert_auth_info **list, size_t *_cert_count, + size_t *_cert_user_count); + +struct tevent_req *pam_check_cert_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char *ca_db, + time_t timeout, + const char *verify_opts, + struct sss_certmap_ctx *sss_certmap_ctx, + const char *uri, + struct pam_data *pd); +errno_t pam_check_cert_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + struct cert_auth_info **cert_list); + +errno_t add_pam_cert_response(struct pam_data *pd, struct sss_domain_info *dom, + const char *sysdb_username, + struct cert_auth_info *cert_info, + enum response_type type); + +bool may_do_cert_auth(struct pam_ctx *pctx, struct pam_data *pd); + +errno_t p11_refresh_certmap_ctx(struct pam_ctx *pctx, + struct sss_domain_info *domains); + +errno_t +pam_set_last_online_auth_with_curr_token(struct sss_domain_info *domain, + const char *username, + uint64_t value); + +errno_t filter_responses(struct pam_ctx *pctx, + struct response_data *resp_list, + struct pam_data *pd); + +errno_t pam_get_auth_types(struct pam_data *pd, + struct pam_resp_auth_type *_auth_types); +errno_t pam_eval_prompting_config(struct pam_ctx *pctx, struct pam_data *pd); + +enum pam_initgroups_scheme pam_initgroups_string_to_enum(const char *str); +const char *pam_initgroup_enum_to_string(enum pam_initgroups_scheme scheme); + +int pam_cmd_gssapi_init(struct cli_ctx *cli_ctx); +int pam_cmd_gssapi_sec_ctx(struct cli_ctx *cctx); + +#endif /* __PAMSRV_H__ */ diff --git a/src/responder/pam/pamsrv_cmd.c b/src/responder/pam/pamsrv_cmd.c new file mode 100644 index 0000000..c23ea7b --- /dev/null +++ b/src/responder/pam/pamsrv_cmd.c @@ -0,0 +1,3085 @@ +/* + SSSD + + PAM Responder + + Copyright (C) Simo Sorce <ssorce@redhat.com> 2009 + Copyright (C) Sumit Bose <sbose@redhat.com> 2009 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#define _GNU_SOURCE + +#include <time.h> +#include <string.h> +#include "util/util.h" +#include "util/auth_utils.h" +#include "util/find_uid.h" +#include "util/sss_ptr_hash.h" +#include "db/sysdb.h" +#include "confdb/confdb.h" +#include "responder/common/responder_packet.h" +#include "responder/common/responder.h" +#include "responder/common/negcache.h" +#include "providers/data_provider.h" +#include "responder/pam/pamsrv.h" +#include "responder/pam/pamsrv_passkey.h" +#include "responder/pam/pam_helpers.h" +#include "responder/common/cache_req/cache_req.h" + +enum pam_verbosity { + PAM_VERBOSITY_NO_MESSAGES = 0, + PAM_VERBOSITY_IMPORTANT, + PAM_VERBOSITY_INFO, + PAM_VERBOSITY_DEBUG +}; + +#define DEFAULT_PAM_VERBOSITY PAM_VERBOSITY_IMPORTANT + +struct pam_initgroup_enum_str { + enum pam_initgroups_scheme scheme; + const char *option; +}; + +struct pam_initgroup_enum_str pam_initgroup_enum_str[] = { + { PAM_INITGR_NEVER, "never" }, + { PAM_INITGR_NO_SESSION, "no_session" }, + { PAM_INITGR_ALWAYS, "always" }, + { PAM_INITGR_INVALID, NULL } +}; + +enum pam_initgroups_scheme pam_initgroups_string_to_enum(const char *str) +{ + size_t c; + + for (c = 0 ; pam_initgroup_enum_str[c].option != NULL; c++) { + if (strcasecmp(pam_initgroup_enum_str[c].option, str) == 0) { + return pam_initgroup_enum_str[c].scheme; + } + } + + return PAM_INITGR_INVALID; +} + +const char *pam_initgroup_enum_to_string(enum pam_initgroups_scheme scheme) +{ + size_t c; + + for (c = 0 ; pam_initgroup_enum_str[c].option != NULL; c++) { + if (pam_initgroup_enum_str[c].scheme == scheme) { + return pam_initgroup_enum_str[c].option; + } + } + + return "(NULL)"; +} + +static errno_t +pam_null_last_online_auth_with_curr_token(struct sss_domain_info *domain, + const char *username); +static errno_t +pam_get_last_online_auth_with_curr_token(struct sss_domain_info *domain, + const char *name, + uint64_t *_value); + +void pam_reply(struct pam_auth_req *preq); + +static errno_t check_cert(TALLOC_CTX *mctx, + struct tevent_context *ev, + struct pam_ctx *pctx, + struct pam_auth_req *preq, + struct pam_data *pd); + +int pam_check_user_done(struct pam_auth_req *preq, int ret); + +static errno_t pack_user_info_msg(TALLOC_CTX *mem_ctx, + const char *user_error_message, + size_t *resp_len, + uint8_t **_resp) +{ + uint32_t resp_type = SSS_PAM_USER_INFO_ACCOUNT_EXPIRED; + size_t err_len; + uint8_t *resp; + size_t p; + + err_len = strlen(user_error_message); + *resp_len = 2 * sizeof(uint32_t) + err_len; + resp = talloc_size(mem_ctx, *resp_len); + if (resp == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_size failed.\n"); + return ENOMEM; + } + + p = 0; + SAFEALIGN_SET_UINT32(&resp[p], resp_type, &p); + SAFEALIGN_SET_UINT32(&resp[p], err_len, &p); + safealign_memcpy(&resp[p], user_error_message, err_len, &p); + if (p != *resp_len) { + DEBUG(SSSDBG_FATAL_FAILURE, "Size mismatch\n"); + } + + *_resp = resp; + return EOK; +} + +static void inform_user(struct pam_data* pd, const char *pam_message) +{ + size_t msg_len; + uint8_t *msg; + errno_t ret; + + ret = pack_user_info_msg(pd, pam_message, &msg_len, &msg); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "pack_user_info_msg failed.\n"); + } else { + ret = pam_add_response(pd, SSS_PAM_USER_INFO, msg_len, msg); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + } + } +} + +static bool is_domain_requested(struct pam_data *pd, const char *domain_name) +{ + int i; + + /* If none specific domains got requested via pam, all domains are allowed. + * Which mimics the default/original behaviour. + */ + if (!pd->requested_domains) { + return true; + } + + for (i = 0; pd->requested_domains[i]; i++) { + if (strcasecmp(domain_name, pd->requested_domains[i])) { + continue; + } + + return true; + } + + return false; +} + +static int extract_authtok_v2(struct sss_auth_token *tok, + size_t data_size, uint8_t *body, size_t blen, + size_t *c) +{ + uint32_t auth_token_type; + uint32_t auth_token_length; + uint8_t *auth_token_data; + int ret = EOK; + + if (data_size < sizeof(uint32_t) || *c+data_size > blen || + SIZE_T_OVERFLOW(*c, data_size)) return EINVAL; + + SAFEALIGN_COPY_UINT32_CHECK(&auth_token_type, &body[*c], blen, c); + auth_token_length = data_size - sizeof(uint32_t); + auth_token_data = body+(*c); + + switch (auth_token_type) { + case SSS_AUTHTOK_TYPE_EMPTY: + sss_authtok_set_empty(tok); + break; + case SSS_AUTHTOK_TYPE_PASSWORD: + if (auth_token_length == 0) { + sss_authtok_set_empty(tok); + } else { + ret = sss_authtok_set_password(tok, (const char *)auth_token_data, + auth_token_length); + } + break; + case SSS_AUTHTOK_TYPE_2FA: + case SSS_AUTHTOK_TYPE_2FA_SINGLE: + case SSS_AUTHTOK_TYPE_SC_PIN: + case SSS_AUTHTOK_TYPE_SC_KEYPAD: + case SSS_AUTHTOK_TYPE_OAUTH2: + case SSS_AUTHTOK_TYPE_PASSKEY: + case SSS_AUTHTOK_TYPE_PASSKEY_KRB: + case SSS_AUTHTOK_TYPE_PASSKEY_REPLY: + ret = sss_authtok_set(tok, auth_token_type, + auth_token_data, auth_token_length); + break; + default: + return EINVAL; + } + + *c += auth_token_length; + + return ret; +} + +static int extract_string(char **var, size_t size, uint8_t *body, size_t blen, + size_t *c) { + uint8_t *str; + + if (*c+size > blen || SIZE_T_OVERFLOW(*c, size)) return EINVAL; + + str = body+(*c); + + if (str[size-1]!='\0') return EINVAL; + + /* If the string isn't valid UTF-8, fail */ + if (!sss_utf8_check(str, size-1)) { + return EINVAL; + } + + *c += size; + + *var = (char *) str; + + return EOK; +} + +static int extract_uint32_t(uint32_t *var, size_t size, uint8_t *body, + size_t blen, size_t *c) { + + if (size != sizeof(uint32_t) || *c+size > blen || SIZE_T_OVERFLOW(*c, size)) + return EINVAL; + + SAFEALIGN_COPY_UINT32_CHECK(var, &body[*c], blen, c); + + return EOK; +} + +static int pd_set_primary_name(const struct ldb_message *msg,struct pam_data *pd) +{ + const char *name; + + name = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL); + if (!name) { + DEBUG(SSSDBG_CRIT_FAILURE, "A user with no name?\n"); + return EIO; + } + + if (strcmp(pd->user, name)) { + DEBUG(SSSDBG_TRACE_FUNC, "User's primary name is %s\n", name); + talloc_free(pd->user); + pd->user = talloc_strdup(pd, name); + if (!pd->user) return ENOMEM; + } + + return EOK; +} + +static int pam_parse_in_data_v2(struct pam_data *pd, + uint8_t *body, size_t blen) +{ + size_t c; + uint32_t type; + uint32_t size; + int ret; + uint32_t start; + uint32_t terminator; + char *requested_domains; + + if (blen < 4*sizeof(uint32_t)+2) { + DEBUG(SSSDBG_CRIT_FAILURE, "Received data is invalid.\n"); + return EINVAL; + } + + SAFEALIGN_COPY_UINT32(&start, body, NULL); + SAFEALIGN_COPY_UINT32(&terminator, body + blen - sizeof(uint32_t), NULL); + + if (start != SSS_START_OF_PAM_REQUEST + || terminator != SSS_END_OF_PAM_REQUEST) { + DEBUG(SSSDBG_CRIT_FAILURE, "Received data is invalid.\n"); + return EINVAL; + } + + c = sizeof(uint32_t); + do { + SAFEALIGN_COPY_UINT32_CHECK(&type, &body[c], blen, &c); + + if (type == SSS_END_OF_PAM_REQUEST) { + if (c != blen) return EINVAL; + } else { + SAFEALIGN_COPY_UINT32_CHECK(&size, &body[c], blen, &c); + /* the uint32_t end maker SSS_END_OF_PAM_REQUEST does not count to + * the remaining buffer */ + if (size > (blen - c - sizeof(uint32_t))) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid data size.\n"); + return EINVAL; + } + + switch(type) { + case SSS_PAM_ITEM_USER: + ret = extract_string(&pd->logon_name, size, body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_SERVICE: + ret = extract_string(&pd->service, size, body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_TTY: + ret = extract_string(&pd->tty, size, body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_RUSER: + ret = extract_string(&pd->ruser, size, body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_RHOST: + ret = extract_string(&pd->rhost, size, body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_REQUESTED_DOMAINS: + ret = extract_string(&requested_domains, size, body, blen, + &c); + if (ret != EOK) return ret; + + ret = split_on_separator(pd, requested_domains, ',', true, + true, &pd->requested_domains, + NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to parse requested_domains list!\n"); + return ret; + } + break; + case SSS_PAM_ITEM_CLI_PID: + ret = extract_uint32_t(&pd->cli_pid, size, + body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_CHILD_PID: + /* This is optional. */ + ret = extract_uint32_t(&pd->child_pid, size, + body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_AUTHTOK: + ret = extract_authtok_v2(pd->authtok, + size, body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_NEWAUTHTOK: + ret = extract_authtok_v2(pd->newauthtok, + size, body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_FLAGS: + ret = extract_uint32_t(&pd->cli_flags, size, + body, blen, &c); + if (ret != EOK) return ret; + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, + "Ignoring unknown data type [%d].\n", type); + c += size; + } + } + + } while(c < blen); + + return EOK; + +} + +static int pam_parse_in_data_v3(struct pam_data *pd, + uint8_t *body, size_t blen) +{ + int ret; + + ret = pam_parse_in_data_v2(pd, body, blen); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_parse_in_data_v2 failed.\n"); + return ret; + } + + if (pd->cli_pid == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing client PID.\n"); + return EINVAL; + } + + return EOK; +} + +static int extract_authtok_v1(struct sss_auth_token *tok, + uint8_t *body, size_t blen, size_t *c) +{ + uint32_t auth_token_type; + uint32_t auth_token_length; + uint8_t *auth_token_data; + int ret = EOK; + + SAFEALIGN_COPY_UINT32_CHECK(&auth_token_type, &body[*c], blen, c); + SAFEALIGN_COPY_UINT32_CHECK(&auth_token_length, &body[*c], blen, c); + auth_token_data = body+(*c); + + switch (auth_token_type) { + case SSS_AUTHTOK_TYPE_EMPTY: + sss_authtok_set_empty(tok); + break; + case SSS_AUTHTOK_TYPE_PASSWORD: + ret = sss_authtok_set_password(tok, (const char *)auth_token_data, + auth_token_length); + break; + default: + return EINVAL; + } + + *c += auth_token_length; + + return ret; +} + +static int pam_parse_in_data(struct pam_data *pd, + uint8_t *body, size_t blen) +{ + size_t start; + size_t end; + size_t last; + int ret; + + last = blen - 1; + end = 0; + + /* user name */ + for (start = end; end < last; end++) if (body[end] == '\0') break; + if (body[end++] != '\0') return EINVAL; + pd->logon_name = (char *) &body[start]; + + for (start = end; end < last; end++) if (body[end] == '\0') break; + if (body[end++] != '\0') return EINVAL; + pd->service = (char *) &body[start]; + + for (start = end; end < last; end++) if (body[end] == '\0') break; + if (body[end++] != '\0') return EINVAL; + pd->tty = (char *) &body[start]; + + for (start = end; end < last; end++) if (body[end] == '\0') break; + if (body[end++] != '\0') return EINVAL; + pd->ruser = (char *) &body[start]; + + for (start = end; end < last; end++) if (body[end] == '\0') break; + if (body[end++] != '\0') return EINVAL; + pd->rhost = (char *) &body[start]; + + ret = extract_authtok_v1(pd->authtok, body, blen, &end); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid auth token\n"); + return ret; + } + ret = extract_authtok_v1(pd->newauthtok, body, blen, &end); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid new auth token\n"); + return ret; + } + + DEBUG_PAM_DATA(SSSDBG_CONF_SETTINGS, pd); + + return EOK; +} + +static errno_t +pam_get_local_auth_policy(struct sss_domain_info *domain, + const char *name, + bool *_sc_allow, + bool *_passkey_allow) +{ + TALLOC_CTX *tmp_ctx = NULL; + const char *attrs[] = { SYSDB_LOCAL_SMARTCARD_AUTH, SYSDB_LOCAL_PASSKEY_AUTH, NULL }; + struct ldb_message *ldb_msg; + bool sc_allow = false; + bool passkey_allow = false; + errno_t ret; + + if (name == NULL || *name == '\0') { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing user name.\n"); + ret = EINVAL; + goto done; + } + + if (domain->sysdb == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing sysdb db context.\n"); + ret = EINVAL; + goto done; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_search_user_by_name(tmp_ctx, domain, name, attrs, &ldb_msg); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sysdb_search_user_by_name failed [%d][%s].\n", + ret, strerror(ret)); + goto done; + } + + sc_allow = ldb_msg_find_attr_as_bool(ldb_msg, SYSDB_LOCAL_SMARTCARD_AUTH, + false); + + passkey_allow = ldb_msg_find_attr_as_bool(ldb_msg, SYSDB_LOCAL_PASSKEY_AUTH, + true); + + ret = EOK; + +done: + if (ret == EOK) { + *_sc_allow = sc_allow; + *_passkey_allow = passkey_allow; + } + + talloc_free(tmp_ctx); + return ret; +} +static errno_t set_local_auth_type(struct pam_auth_req *preq, + bool sc_allow, + bool passkey_allow) +{ + struct sysdb_attrs *attrs; + errno_t ret; + + attrs = sysdb_new_attrs(preq); + if (!attrs) { + ret = ENOMEM; + goto fail; + } + + ret = sysdb_attrs_add_bool(attrs, SYSDB_LOCAL_SMARTCARD_AUTH, sc_allow); + if (ret != EOK) { + goto fail; + } + + ret = sysdb_attrs_add_bool(attrs, SYSDB_LOCAL_PASSKEY_AUTH, passkey_allow); + if (ret != EOK) { + goto fail; + } + + ret = sysdb_set_user_attr(preq->domain, preq->pd->user, attrs, + SYSDB_MOD_REP); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "set_local_auth_type failed.\n"); + preq->pd->pam_status = PAM_SYSTEM_ERR; + goto fail; + } + + return EOK; + +fail: + return ret; +} +/*=Save-Last-Login-State===================================================*/ + +static errno_t set_last_login(struct pam_auth_req *preq) +{ + struct sysdb_attrs *attrs; + errno_t ret; + + attrs = sysdb_new_attrs(preq); + if (!attrs) { + ret = ENOMEM; + goto fail; + } + + ret = sysdb_attrs_add_time_t(attrs, SYSDB_LAST_ONLINE_AUTH, time(NULL)); + if (ret != EOK) { + goto fail; + } + + ret = sysdb_attrs_add_time_t(attrs, + SYSDB_LAST_ONLINE_AUTH_WITH_CURR_TOKEN, + time(NULL)); + if (ret != EOK) { + goto fail; + } + + ret = sysdb_attrs_add_time_t(attrs, SYSDB_LAST_LOGIN, time(NULL)); + if (ret != EOK) { + goto fail; + } + + ret = sysdb_set_user_attr(preq->domain, preq->pd->user, attrs, + SYSDB_MOD_REP); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "set_last_login failed.\n"); + preq->pd->pam_status = PAM_SYSTEM_ERR; + goto fail; + } else { + preq->pd->last_auth_saved = true; + } + preq->callback(preq); + + return EOK; + +fail: + return ret; +} + +static errno_t filter_responses_env(struct response_data *resp, + struct pam_data *pd, + char * const *pam_filter_opts) +{ + size_t c; + const char *var_name; + size_t var_name_len; + const char *service; + + if (pam_filter_opts == NULL) { + return EOK; + } + + for (c = 0; pam_filter_opts[c] != NULL; c++) { + if (strncmp(pam_filter_opts[c], "ENV", 3) != 0) { + continue; + } + + var_name = NULL; + var_name_len = 0; + service = NULL; + if (pam_filter_opts[c][3] != '\0') { + if (pam_filter_opts[c][3] != ':') { + /* Neither plain ENV nor ENV:, ignored */ + continue; + } + + var_name = pam_filter_opts[c] + 4; + /* check if there is a second ':' in the option and use the following + * data, if any, as service name. */ + service = strchr(var_name, ':'); + if (service == NULL) { + var_name_len = strlen(var_name); + } else { + var_name_len = service - var_name; + + service++; + /* handle empty service name "ENV:var:" */ + if (*service == '\0') { + service = NULL; + } + } + } + /* handle empty var name "ENV:" or "ENV::service" */ + if (var_name_len == 0) { + var_name = NULL; + } + + DEBUG(SSSDBG_TRACE_ALL, + "Found PAM ENV filter for variable [%.*s] and service [%s].\n", + (int) var_name_len, + (var_name ? var_name : "(NULL)"), + (service ? service : "(NULL)")); + + if (service != NULL && pd->service != NULL + && strcmp(service, pd->service) != 0) { + /* current service does not match the filter */ + continue; + } + + if (var_name == NULL) { + /* All environment variables should be filtered */ + resp->do_not_send_to_client = true; + continue; + } + + if (resp->len > var_name_len && resp->data[var_name_len] == '=' + && memcmp(resp->data, var_name, var_name_len) == 0) { + resp->do_not_send_to_client = true; + } + } + + return EOK; +} + +errno_t filter_responses(struct pam_ctx *pctx, + struct response_data *resp_list, + struct pam_data *pd) +{ + int ret; + struct response_data *resp; + uint32_t user_info_type; + int64_t expire_date = 0; + int pam_verbosity = DEFAULT_PAM_VERBOSITY; + char **new_opts; + size_t c; + const char *default_pam_response_filter[] = { "ENV:KRB5CCNAME:sudo", + "ENV:KRB5CCNAME:sudo-i", + NULL }; + + ret = confdb_get_int(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_VERBOSITY, DEFAULT_PAM_VERBOSITY, + &pam_verbosity); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to read PAM verbosity, not fatal.\n"); + pam_verbosity = DEFAULT_PAM_VERBOSITY; + } + + if (pctx->pam_filter_opts == NULL) { + ret = confdb_get_string_as_list(pctx->rctx->cdb, pctx, + CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_RESPONSE_FILTER, + &pctx->pam_filter_opts); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read values of [%s], not fatal.\n", + CONFDB_PAM_RESPONSE_FILTER); + pctx->pam_filter_opts = NULL; + } else { + if (pctx->pam_filter_opts == NULL + || *pctx->pam_filter_opts[0] == '+' + || *pctx->pam_filter_opts[0] == '-') { + ret = mod_defaults_list(pctx, default_pam_response_filter, + pctx->pam_filter_opts, &new_opts); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to modify [%s] defaults.\n", + CONFDB_PAM_RESPONSE_FILTER); + return ret; + } + talloc_free(pctx->pam_filter_opts); + pctx->pam_filter_opts = new_opts; + } + } + + if (pctx->pam_filter_opts == NULL) { + DEBUG(SSSDBG_CONF_SETTINGS, "No PAM response filter set.\n"); + } else { + /* Make sure there are no '+' or '-' prefixes anymore */ + for (c = 0; pctx->pam_filter_opts[c] != NULL; c++) { + if (*pctx->pam_filter_opts[0] == '+' + || *pctx->pam_filter_opts[0] == '-') { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unsupport mix of prefixed and not prefixed " + "values of [%s].\n", CONFDB_PAM_RESPONSE_FILTER); + return EINVAL; + } + DEBUG(SSSDBG_CONF_SETTINGS, + "PAM response filter: [%s].\n", + pctx->pam_filter_opts[c]); + } + } + } + + resp = resp_list; + while(resp != NULL) { + if (resp->type == SSS_PAM_USER_INFO) { + if (resp->len < sizeof(uint32_t)) { + DEBUG(SSSDBG_CRIT_FAILURE, "User info entry is too short.\n"); + ret = EINVAL; + goto done; + } + + if (pam_verbosity == PAM_VERBOSITY_NO_MESSAGES) { + resp->do_not_send_to_client = true; + resp = resp->next; + continue; + } + + memcpy(&user_info_type, resp->data, sizeof(uint32_t)); + + resp->do_not_send_to_client = false; + switch (user_info_type) { + case SSS_PAM_USER_INFO_OFFLINE_AUTH: + if (resp->len != sizeof(uint32_t) + sizeof(int64_t)) { + DEBUG(SSSDBG_CRIT_FAILURE, + "User info offline auth entry is " + "too short.\n"); + ret = EINVAL; + goto done; + } + memcpy(&expire_date, resp->data + sizeof(uint32_t), + sizeof(int64_t)); + if ((expire_date == 0 && + pam_verbosity < PAM_VERBOSITY_INFO) || + (expire_date > 0 && + pam_verbosity < PAM_VERBOSITY_IMPORTANT)) { + resp->do_not_send_to_client = true; + } + + break; + default: + DEBUG(SSSDBG_TRACE_LIBS, + "User info type [%d] not filtered.\n", + user_info_type); + } + } else if (resp->type == SSS_PAM_ENV_ITEM) { + resp->do_not_send_to_client = false; + ret = filter_responses_env(resp, pd, pctx->pam_filter_opts); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "filter_responses_env failed.\n"); + goto done; + } + } else if (resp->type & SSS_SERVER_INFO) { + resp->do_not_send_to_client = true; + } + + resp = resp->next; + } + + ret = EOK; +done: + + return ret; +} + +static void do_not_send_cert_info(struct pam_data *pd) +{ + struct response_data *resp; + + resp = pd->resp_list; + while (resp != NULL) { + switch (resp->type) { + case SSS_PAM_CERT_INFO: + case SSS_PAM_CERT_INFO_WITH_HINT: + resp->do_not_send_to_client = true; + break; + default: + break; + } + resp = resp->next; + } +} + +static void evaluate_pam_resp_list(struct pam_data *pd, + struct pam_resp_auth_type *_auth_types, + bool *_found_cert_info) +{ + struct response_data *resp; + struct pam_resp_auth_type types = {0}; + bool found_cert_info = false; + + resp = pd->resp_list; + while (resp != NULL) { + switch (resp->type) { + case SSS_PAM_OTP_INFO: + types.otp_auth = true; + break; + case SSS_PAM_CERT_INFO: + case SSS_PAM_CERT_INFO_WITH_HINT: + found_cert_info = true; + break; + case SSS_PAM_PASSKEY_INFO: + case SSS_PAM_PASSKEY_KRB_INFO: + types.passkey_auth = true; + break; + case SSS_PASSWORD_PROMPTING: + types.password_auth = true; + break; + case SSS_CERT_AUTH_PROMPTING: + types.cert_auth = true; + break; + default: + break; + } + resp = resp->next; + } + + if (_auth_types != NULL) { + *_auth_types = types; + } + if (_found_cert_info != NULL) { + *_found_cert_info = found_cert_info; + } +} + +static void evalute_sending_cert_info(struct pam_data *pd) +{ + struct pam_resp_auth_type types = {0}; + bool found_cert_info = false; + + evaluate_pam_resp_list(pd, &types, &found_cert_info); + + if (found_cert_info && !types.cert_auth) { + do_not_send_cert_info(pd); + } +} + +errno_t pam_get_auth_types(struct pam_data *pd, + struct pam_resp_auth_type *_auth_types) +{ + int ret; + struct pam_resp_auth_type types = {0}; + + evaluate_pam_resp_list(pd, &types, NULL); + + if (!types.password_auth && !types.otp_auth && !types.cert_auth && !types.passkey_auth) { + /* If the backend cannot determine which authentication types are + * available the default would be to prompt for a password. */ + types.password_auth = true; + } + + DEBUG(SSSDBG_TRACE_ALL, "Authentication types for user [%s] and service " + "[%s]:%s%s%s%s\n", pd->user, pd->service, + types.password_auth ? " password": "", + types.otp_auth ? " two-factor" : "", + types.passkey_auth ? " passkey" : "", + types.cert_auth ? " smartcard" : ""); + + ret = EOK; + + *_auth_types = types; + + return ret; +} + +static errno_t pam_eval_local_auth_policy(TALLOC_CTX *mem_ctx, + struct pam_ctx *pctx, + struct pam_data *pd, + struct pam_auth_req *preq, + bool *_sc_allow, + bool *_passkey_allow, + char **_local_policy) { + + TALLOC_CTX *tmp_ctx; + errno_t ret; + const char *domain_cdb; + char *local_policy = NULL; + bool sc_allow = false; + bool passkey_allow = false; + struct pam_resp_auth_type auth_types; + char **opts; + size_t c; + +#ifdef BUILD_FILES_PROVIDER + if (is_files_provider(preq->domain)) { + *_sc_allow = true; + *_passkey_allow = false; + *_local_policy = NULL; + + return EOK; + } +#endif + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + /* Check local auth policy */ + domain_cdb = talloc_asprintf(tmp_ctx, CONFDB_DOMAIN_PATH_TMPL, preq->domain->name); + if (domain_cdb == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = confdb_get_string(pctx->rctx->cdb, tmp_ctx, domain_cdb, + CONFDB_DOMAIN_LOCAL_AUTH_POLICY, + "match", &local_policy); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to get the confdb local_auth_policy\n"); + return ret; + } + + /* "only" ignores online methods and allows all local ones */ + if (strcasecmp(local_policy, "only") == 0) { + sc_allow = true; + passkey_allow = true; + /* Match what the KDC supports and provides */ + } else if (strcasecmp(local_policy, "match") == 0) { + /* Don't overwrite the local auth type when offline */ + if (pd->pam_status == PAM_SUCCESS && pd->cmd == SSS_PAM_PREAUTH && + !is_domain_provider(preq->domain, "ldap")) { + ret = pam_get_auth_types(pd, &auth_types); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to get authentication types\n"); + goto done; + } + + if (auth_types.cert_auth) { + sc_allow = true; + } else if (auth_types.passkey_auth) { + passkey_allow = true; + } + + /* Store the local auth types, in case we go offline */ + if (!auth_types.password_auth) { + ret = set_local_auth_type(preq, sc_allow, passkey_allow); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to evaluate local auth policy\n"); + goto done; + } + } + } + + /* Read the latest auth types */ + ret = pam_get_local_auth_policy(preq->domain, preq->pd->user, + &sc_allow, &passkey_allow); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to get PAM local auth policy\n"); + goto done; + } + /* Check for enable */ + } else { + ret = split_on_separator(tmp_ctx, local_policy, ',', true, true, &opts, + NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "split_on_separator failed [%d], %s.\n", + ret, sss_strerror(ret)); + goto done; + } + + for (c = 0; opts[c] != NULL; c++) { + if (strcasestr(opts[c], "passkey") != NULL) { + passkey_allow = strstr(opts[c], "enable") ? true : false; + } else if (strcasestr(opts[c], "smartcard") != NULL) { + sc_allow = strstr(opts[c], "enable") ? true : false; + } else { + DEBUG(SSSDBG_MINOR_FAILURE, + "Unexpected local auth policy option [%s], " \ + "skipping.\n", opts[c]); + } + } + } + + /* if passkey is enabled but local Smartcard authentication is not but + * possible, the cert info data has to be remove as well if only local + * Smartcard authentication is possible. If Smartcard authentication + * is possible on the server side we have to keep it because the + * 'enable' option should only add local methods but not reject remote + * ones. */ + if (!sc_allow) { + evalute_sending_cert_info(pd); + } + + *_sc_allow = sc_allow; + *_passkey_allow = passkey_allow; + *_local_policy = talloc_steal(mem_ctx, local_policy); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static void pam_reply_delay(struct tevent_context *ev, struct tevent_timer *te, + struct timeval tv, void *pvt) +{ + struct pam_auth_req *preq; + + DEBUG(SSSDBG_CONF_SETTINGS, "pam_reply_delay get called.\n"); + + preq = talloc_get_type(pvt, struct pam_auth_req); + + pam_reply(preq); +} + +static errno_t get_password_for_cache_auth(struct sss_auth_token *authtok, + const char **password) +{ + int ret; + size_t pw_len; + const char *fa2; + size_t fa2_len; + + switch (sss_authtok_get_type(authtok)) { + case SSS_AUTHTOK_TYPE_PASSWORD: + ret = sss_authtok_get_password(authtok, password, NULL); + break; + case SSS_AUTHTOK_TYPE_2FA: + ret = sss_authtok_get_2fa(authtok, password, &pw_len, &fa2, &fa2_len); + break; + default: + DEBUG(SSSDBG_FATAL_FAILURE, "Unsupported auth token type [%d].\n", + sss_authtok_get_type(authtok)); + ret = EINVAL; + } + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to get password.\n"); + return ret; + } + + return EOK; +} + +static int pam_forwarder(struct cli_ctx *cctx, int pam_cmd); +static void pam_handle_cached_login(struct pam_auth_req *preq, int ret, + time_t expire_date, time_t delayed_until, bool cached_auth); + +/* + * Add a request to add a variable to the PAM user environment, containing the + * actual (not overridden) user shell, in case session recording is enabled. + */ +static int pam_reply_sr_export_shell(struct pam_auth_req *preq, + const char *var_name) +{ + int ret; + TALLOC_CTX *ctx = NULL; + bool enabled; + const char *enabled_str; + const char *shell; + char *buf; + + /* Create temporary talloc context */ + ctx = talloc_new(NULL); + if (ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new failed.\n"); + ret = ENOMEM; + goto done; + } + + /* Check if session recording is enabled */ + if (preq->cctx->rctx->sr_conf.scope == + SESSION_RECORDING_SCOPE_NONE) { + enabled = false; + } else { + enabled_str = ldb_msg_find_attr_as_string(preq->user_obj, + SYSDB_SESSION_RECORDING, NULL); + if (enabled_str == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "%s attribute not found\n", SYSDB_SESSION_RECORDING); + ret = ENOENT; + goto done; + } else if (strcmp(enabled_str, "TRUE") == 0) { + enabled = true; + } else if (strcmp(enabled_str, "FALSE") == 0) { + enabled = false; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "invalid value of %s attribute: %s\n", + SYSDB_SESSION_RECORDING, enabled_str); + ret = ENOENT; + goto done; + } + } + + /* Export original shell if recording is enabled and so it's overridden */ + if (enabled) { + /* Extract the shell */ + shell = sss_resp_get_shell_override(preq->user_obj, + preq->cctx->rctx, preq->domain); + if (shell == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "user has no shell\n"); + ret = ENOENT; + goto done; + } + + /* Format environment entry */ + buf = talloc_asprintf(ctx, "%s=%s", var_name, shell); + if (buf == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + + /* Add request to add the entry to user environment */ + ret = pam_add_response(preq->pd, SSS_PAM_ENV_ITEM, + strlen(buf) + 1, (uint8_t *)buf); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + goto done; + } + } + + ret = EOK; + +done: + talloc_free(ctx); + return ret; +} + +void pam_reply(struct pam_auth_req *preq) +{ + struct cli_ctx *cctx; + struct cli_protocol *prctx; + uint8_t *body; + size_t blen; + int ret; + int32_t resp_c; + int32_t resp_size; + struct response_data *resp; + int p; + struct timeval tv; + struct tevent_timer *te; + struct pam_data *pd; + char *local_policy = NULL; + struct pam_ctx *pctx; + uint32_t user_info_type; + time_t exp_date = -1; + time_t delay_until = -1; + char* pam_account_expired_message; + char* pam_account_locked_message; + int pam_verbosity; + bool local_sc_auth_allow = false; + bool local_passkey_auth_allow = false; +#ifdef BUILD_PASSKEY + bool pk_preauth_done = false; +#endif /* BUILD_PASSKEY */ + + pd = preq->pd; + cctx = preq->cctx; + pctx = talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx); + prctx = talloc_get_type(cctx->protocol_ctx, struct cli_protocol); + + ret = confdb_get_int(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_VERBOSITY, DEFAULT_PAM_VERBOSITY, + &pam_verbosity); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to read PAM verbosity, not fatal.\n"); + pam_verbosity = DEFAULT_PAM_VERBOSITY; + } + + DEBUG(SSSDBG_TRACE_ALL, + "pam_reply initially called with result [%d]: %s. " + "this result might be changed during processing\n", + pd->pam_status, pam_strerror(NULL, pd->pam_status)); + + if (preq->domain != NULL && preq->domain->name != NULL) { + ret = pam_eval_local_auth_policy(cctx, pctx, pd, preq, + &local_sc_auth_allow, + &local_passkey_auth_allow, + &local_policy); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to evaluate local auth policy\n"); + goto done; + } + } + + /* Ignore local_auth_policy for the files provider, allow local + * smartcard auth (default behavior prior to local_auth_policy) */ + if (is_domain_provider(preq->domain, "files")) { + local_sc_auth_allow = true; + /* For the ldap auth provider we currently only support + * password based authentication */ + } else if (is_domain_provider(preq->domain, "ldap") && local_policy != NULL + && strcasecmp(local_policy, "match") == 0) { + local_passkey_auth_allow = false; + local_sc_auth_allow = false; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Local auth policy allowed: smartcard [%s], passkey [%s]\n", + local_sc_auth_allow ? "True" : "False", + local_passkey_auth_allow ? "True" : "False"); + + if (pd->cmd == SSS_PAM_AUTHENTICATE + && !preq->cert_auth_local + && (pd->pam_status == PAM_AUTHINFO_UNAVAIL + || pd->pam_status == PAM_NO_MODULE_DATA + || pd->pam_status == PAM_BAD_ITEM) + && may_do_cert_auth(pctx, pd)) { + /* We have Smartcard credentials and the backend indicates that it is + * offline (PAM_AUTHINFO_UNAVAIL) or cannot handle the credentials + * (PAM_BAD_ITEM), so let's try authentication against the Smartcard + * PAM_NO_MODULE_DATA is returned by the krb5 backend if no + * authentication method was found at all, this might happen if the + * user has a Smartcard assigned but the pkint plugin is not available + * on the client. */ + DEBUG(SSSDBG_IMPORTANT_INFO, + "Backend cannot handle Smartcard authentication, " + "trying local Smartcard authentication.\n"); + if (local_sc_auth_allow) { + preq->cert_auth_local = true; + ret = check_cert(cctx, cctx->ev, pctx, preq, pd); + pam_check_user_done(preq, ret); + return; + } else { + DEBUG(SSSDBG_IMPORTANT_INFO, + "Local smartcard auth not allowed by local_auth_policy"); + } + } + + if (pd->pam_status == PAM_AUTHINFO_UNAVAIL || preq->use_cached_auth) { + + switch(pd->cmd) { + case SSS_PAM_AUTHENTICATE: + if ((preq->domain != NULL) && + (preq->domain->cache_credentials == true) && + (pd->offline_auth == false)) { + const char *password = NULL; + bool use_cached_auth; + + /* backup value of preq->use_cached_auth*/ + use_cached_auth = preq->use_cached_auth; + /* set to false to avoid entering this branch when pam_reply() + * is recursively called from pam_handle_cached_login() */ + preq->use_cached_auth = false; + + /* do auth with offline credentials */ + pd->offline_auth = true; + + if (preq->domain->sysdb == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Fatal: Sysdb CTX not found for domain" + " [%s]!\n", preq->domain->name); + goto done; + } + + ret = get_password_for_cache_auth(pd->authtok, &password); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "get_password_and_type_for_cache_auth failed.\n"); + goto done; + } + + ret = sysdb_cache_auth(preq->domain, + pd->user, password, + pctx->rctx->cdb, false, + &exp_date, &delay_until); + + pam_handle_cached_login(preq, ret, exp_date, delay_until, + use_cached_auth); + return; + } + break; + case SSS_PAM_CHAUTHTOK_PRELIM: + case SSS_PAM_CHAUTHTOK: + DEBUG(SSSDBG_FUNC_DATA, + "Password change not possible while offline.\n"); + pd->pam_status = PAM_AUTHTOK_ERR; + user_info_type = SSS_PAM_USER_INFO_OFFLINE_CHPASS; + ret = pam_add_response(pd, SSS_PAM_USER_INFO, sizeof(uint32_t), + (const uint8_t *) &user_info_type); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + goto done; + } + break; +/* TODO: we need the pam session cookie here to make sure that cached + * authentication was successful */ + case SSS_PAM_PREAUTH: + case SSS_PAM_SETCRED: + case SSS_PAM_ACCT_MGMT: + case SSS_PAM_OPEN_SESSION: + case SSS_PAM_CLOSE_SESSION: + DEBUG(SSSDBG_OP_FAILURE, + "Assuming offline authentication setting status for " + "pam call %d to PAM_SUCCESS.\n", pd->cmd); + pd->pam_status = PAM_SUCCESS; + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Unknown PAM call [%d].\n", pd->cmd); + pd->pam_status = PAM_MODULE_UNKNOWN; + } + } + + if (pd->pam_status == PAM_SUCCESS && pd->cmd == SSS_PAM_CHAUTHTOK) { + ret = pam_null_last_online_auth_with_curr_token(preq->domain, + pd->user); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sysdb_null_last_online_auth_with_curr_token failed: " + "%s [%d].\n", sss_strerror(ret), ret); + goto done; + } + } + + if (pd->response_delay > 0) { + ret = gettimeofday(&tv, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "gettimeofday failed [%d][%s].\n", + errno, strerror(errno)); + goto done; + } + tv.tv_sec += pd->response_delay; + tv.tv_usec = 0; + pd->response_delay = 0; + + te = tevent_add_timer(cctx->ev, cctx, tv, pam_reply_delay, preq); + if (te == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to add event pam_reply_delay.\n"); + goto done; + } + + return; + } + + /* If this was a successful login, save the lastLogin time */ + if (pd->cmd == SSS_PAM_AUTHENTICATE && + pd->pam_status == PAM_SUCCESS && + preq->domain && + preq->domain->cache_credentials && + !pd->offline_auth && + !pd->last_auth_saved && + !is_files_provider(preq->domain)) { + ret = set_last_login(preq); + if (ret != EOK) { + goto done; + } + return; + } + + ret = sss_packet_new(prctx->creq, 0, sss_packet_get_cmd(prctx->creq->in), + &prctx->creq->out); + if (ret != EOK) { + goto done; + } + + /* Passkey auth user notification if no TGT is granted */ + if (pd->cmd == SSS_PAM_AUTHENTICATE && + pd->pam_status == PAM_SUCCESS && + preq->pd->passkey_local_done) { + user_info_type = SSS_PAM_USER_INFO_NO_KRB_TGT; + pam_add_response(pd, SSS_PAM_USER_INFO, + sizeof(uint32_t), (const uint8_t *) &user_info_type); + DEBUG(SSSDBG_IMPORTANT_INFO, + "User [%s] logged in with local passkey authentication, single " + "sign on ticket is not obtained.\n", pd->user); + } + + /* Account expiration warning is printed for sshd. If pam_verbosity + * is equal or above PAM_VERBOSITY_INFO then all services are informed + * about account expiration. + */ + if (pd->pam_status == PAM_ACCT_EXPIRED && + ((pd->service != NULL && strcasecmp(pd->service, "sshd") == 0) || + pam_verbosity >= PAM_VERBOSITY_INFO)) { + + ret = confdb_get_string(pctx->rctx->cdb, pd, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_ACCOUNT_EXPIRED_MESSAGE, "", + &pam_account_expired_message); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to get expiration message: %d:[%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + inform_user(pd, pam_account_expired_message); + } + + if (pd->account_locked) { + + ret = confdb_get_string(pctx->rctx->cdb, pd, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_ACCOUNT_LOCKED_MESSAGE, "", + &pam_account_locked_message); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to get expiration message: %d:[%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + inform_user(pd, pam_account_locked_message); + } + + ret = filter_responses(pctx, pd->resp_list, pd); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "filter_responses failed, not fatal.\n"); + } + + if (pd->domain != NULL) { + ret = pam_add_response(pd, SSS_PAM_DOMAIN_NAME, strlen(pd->domain)+1, + (uint8_t *) pd->domain); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + goto done; + } + } + + if (pd->cmd == SSS_PAM_PREAUTH) { + ret = pam_eval_prompting_config(pctx, pd); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to add prompting information, " + "using defaults.\n"); + } + +#ifdef BUILD_PASSKEY + ret = pam_eval_passkey_response(pctx, pd, preq, &pk_preauth_done); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to eval passkey response\n"); + goto done; + } + + if (may_do_passkey_auth(pctx, pd) + && !pk_preauth_done + && preq->passkey_data_exists + && local_passkey_auth_allow) { + ret = passkey_local(cctx, cctx->ev, pctx, preq, pd); + pam_check_user_done(preq, ret); + return; + } +#endif /* BUILD_PASSKEY */ + } + + /* + * Export non-overridden shell to tlog-rec-session when opening the session + */ + if (pd->cmd == SSS_PAM_OPEN_SESSION && pd->pam_status == PAM_SUCCESS) { + ret = pam_reply_sr_export_shell(preq, "TLOG_REC_SESSION_SHELL"); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "failed to export the shell to tlog-rec-session.\n"); + goto done; + } + } + + resp_c = 0; + resp_size = 0; + resp = pd->resp_list; + while(resp != NULL) { + if (!resp->do_not_send_to_client) { + resp_c++; + resp_size += resp->len; + } + resp = resp->next; + } + + ret = sss_packet_grow(prctx->creq->out, sizeof(int32_t) + + sizeof(int32_t) + + resp_c * 2* sizeof(int32_t) + + resp_size); + if (ret != EOK) { + goto done; + } + + sss_packet_get_body(prctx->creq->out, &body, &blen); + DEBUG(SSSDBG_FUNC_DATA, "blen: %zu\n", blen); + p = 0; + + memcpy(&body[p], &pd->pam_status, sizeof(int32_t)); + p += sizeof(int32_t); + + memcpy(&body[p], &resp_c, sizeof(int32_t)); + p += sizeof(int32_t); + + resp = pd->resp_list; + while(resp != NULL) { + if (!resp->do_not_send_to_client) { + memcpy(&body[p], &resp->type, sizeof(int32_t)); + p += sizeof(int32_t); + memcpy(&body[p], &resp->len, sizeof(int32_t)); + p += sizeof(int32_t); + memcpy(&body[p], resp->data, resp->len); + p += resp->len; + } + + resp = resp->next; + } + +done: + DEBUG(SSSDBG_FUNC_DATA, "Returning [%d]: %s to the client\n", + pd->pam_status, pam_strerror(NULL, pd->pam_status)); + sss_cmd_done(cctx, preq); +} + +static void pam_dom_forwarder(struct pam_auth_req *preq); + +static void pam_handle_cached_login(struct pam_auth_req *preq, int ret, + time_t expire_date, time_t delayed_until, + bool use_cached_auth) +{ + uint32_t resp_type; + size_t resp_len; + uint8_t *resp; + int64_t dummy; + + preq->pd->pam_status = cached_login_pam_status(ret); + + switch (preq->pd->pam_status) { + case PAM_SUCCESS: + resp_type = SSS_PAM_USER_INFO_OFFLINE_AUTH; + resp_len = sizeof(uint32_t) + sizeof(int64_t); + resp = talloc_size(preq->pd, resp_len); + if (resp == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "talloc_size failed, cannot prepare user info.\n"); + } else { + memcpy(resp, &resp_type, sizeof(uint32_t)); + dummy = (int64_t) expire_date; + memcpy(resp+sizeof(uint32_t), &dummy, sizeof(int64_t)); + ret = pam_add_response(preq->pd, SSS_PAM_USER_INFO, resp_len, + (const uint8_t *) resp); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + } + } + break; + case PAM_PERM_DENIED: + if (delayed_until >= 0) { + resp_type = SSS_PAM_USER_INFO_OFFLINE_AUTH_DELAYED; + resp_len = sizeof(uint32_t) + sizeof(int64_t); + resp = talloc_size(preq->pd, resp_len); + if (resp == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "talloc_size failed, cannot prepare user info.\n"); + } else { + memcpy(resp, &resp_type, sizeof(uint32_t)); + dummy = (int64_t) delayed_until; + memcpy(resp+sizeof(uint32_t), &dummy, sizeof(int64_t)); + ret = pam_add_response(preq->pd, SSS_PAM_USER_INFO, resp_len, + (const uint8_t *) resp); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "pam_add_response failed.\n"); + } + } + } + break; + case PAM_AUTH_ERR: + /* Was this attempt to authenticate from cache? */ + if (use_cached_auth) { + /* Don't try cached authentication again, try online check. */ + DEBUG(SSSDBG_FUNC_DATA, + "Cached authentication failed for: %s\n", + preq->pd->user); + preq->cached_auth_failed = true; + pam_dom_forwarder(preq); + return; + } + break; + default: + DEBUG(SSSDBG_TRACE_LIBS, + "cached login returned: %d\n", preq->pd->pam_status); + } + + pam_reply(preq); + return; +} + +static void pam_forwarder_cb(struct tevent_req *req); +static void pam_forwarder_cert_cb(struct tevent_req *req); +int pam_check_user_search(struct pam_auth_req *preq); + + +/* TODO: we should probably return some sort of cookie that is set in the + * PAM_ENVIRONMENT, so that we can save performing some calls and cache + * data. */ + +static errno_t pam_forwarder_parse_data(struct cli_ctx *cctx, struct pam_data *pd) +{ + struct cli_protocol *prctx; + uint8_t *body; + size_t blen; + errno_t ret; + uint32_t terminator; + const char *key_id; + + prctx = talloc_get_type(cctx->protocol_ctx, struct cli_protocol); + + sss_packet_get_body(prctx->creq->in, &body, &blen); + if (blen >= sizeof(uint32_t)) { + SAFEALIGN_COPY_UINT32(&terminator, + body + blen - sizeof(uint32_t), + NULL); + if (terminator != SSS_END_OF_PAM_REQUEST) { + DEBUG(SSSDBG_CRIT_FAILURE, "Received data not terminated.\n"); + ret = EINVAL; + goto done; + } + } + + switch (prctx->cli_protocol_version->version) { + case 1: + ret = pam_parse_in_data(pd, body, blen); + break; + case 2: + ret = pam_parse_in_data_v2(pd, body, blen); + break; + case 3: + ret = pam_parse_in_data_v3(pd, body, blen); + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Illegal protocol version [%d].\n", + prctx->cli_protocol_version->version); + ret = EINVAL; + } + if (ret != EOK) { + goto done; + } + + if (pd->logon_name != NULL) { + ret = sss_parse_name_for_domains(pd, cctx->rctx->domains, + cctx->rctx->default_domain, + pd->logon_name, + &pd->domain, &pd->user); + } else { + /* SSS_PAM_PREAUTH request may have a missing name, e.g. if the + * name is determined with the help of a certificate. During + * SSS_PAM_AUTHENTICATE at least a key ID is needed to identify the + * selected certificate. */ + if (pd->cmd == SSS_PAM_AUTHENTICATE + && may_do_cert_auth(talloc_get_type(cctx->rctx->pvt_ctx, + struct pam_ctx), pd) + && (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_SC_PIN + || sss_authtok_get_type(pd->authtok) + == SSS_AUTHTOK_TYPE_SC_KEYPAD)) { + ret = sss_authtok_get_sc(pd->authtok, NULL, NULL, NULL, NULL, NULL, + NULL, &key_id, NULL, NULL, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_authtok_get_sc failed.\n"); + goto done; + } + + if (key_id == NULL || *key_id == '\0') { + DEBUG(SSSDBG_CRIT_FAILURE, + "Missing logon and Smartcard key ID during " + "authentication.\n"); + ret = ERR_NO_CREDS; + goto done; + } + + ret = EOK; + } else if (pd->cmd == SSS_PAM_PREAUTH + && may_do_cert_auth(talloc_get_type(cctx->rctx->pvt_ctx, + struct pam_ctx), pd)) { + ret = EOK; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing logon name in PAM request.\n"); + ret = ERR_NO_CREDS; + goto done; + } + } + + DEBUG_PAM_DATA(SSSDBG_CONF_SETTINGS, pd); + +done: + return ret; +} + +static bool is_uid_trusted(struct cli_creds *creds, + size_t trusted_uids_count, + uid_t *trusted_uids) +{ + errno_t ret; + + /* root is always trusted */ + if (client_euid(creds) == 0) { + return true; + } + + /* All uids are allowed */ + if (trusted_uids_count == 0) { + return true; + } + + ret = check_allowed_uids(client_euid(creds), trusted_uids_count, trusted_uids); + if (ret == EOK) return true; + + return false; +} + +static bool is_domain_public(char *name, + char **public_dom_names, + size_t public_dom_names_count) +{ + size_t i; + + for(i=0; i < public_dom_names_count; i++) { + if (strcasecmp(name, public_dom_names[i]) == 0) { + return true; + } + } + return false; +} + +static enum cache_req_dom_type +get_domain_request_type(struct pam_auth_req *preq, + struct pam_ctx *pctx) +{ + enum cache_req_dom_type req_dom_type; + + /* By default, only POSIX domains are to be contacted */ + req_dom_type = CACHE_REQ_POSIX_DOM; + + for (int i = 0; pctx->app_services[i]; i++) { + if (strcmp(pctx->app_services[i], preq->pd->service) == 0) { + req_dom_type = CACHE_REQ_APPLICATION_DOM; + break; + } + } + + return req_dom_type; +} + +static errno_t check_cert(TALLOC_CTX *mctx, + struct tevent_context *ev, + struct pam_ctx *pctx, + struct pam_auth_req *preq, + struct pam_data *pd) +{ + int p11_child_timeout; + int wait_for_card_timeout; + char *cert_verification_opts; + errno_t ret; + struct tevent_req *req; + char *uri = NULL; + + ret = confdb_get_int(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_P11_CHILD_TIMEOUT, + P11_CHILD_TIMEOUT_DEFAULT, + &p11_child_timeout); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to read p11_child_timeout from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + if ((pd->cli_flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH) && pd->priv == 1) { + ret = confdb_get_int(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_WAIT_FOR_CARD_TIMEOUT, + P11_WAIT_FOR_CARD_TIMEOUT_DEFAULT, + &wait_for_card_timeout); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to read [%s] from confdb: [%d]: %s\n", + CONFDB_PAM_WAIT_FOR_CARD_TIMEOUT, ret, sss_strerror(ret)); + return ret; + } + + p11_child_timeout += wait_for_card_timeout; + } + + ret = confdb_get_string(pctx->rctx->cdb, mctx, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_CERT_VERIFICATION, + NULL, &cert_verification_opts); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to read '"CONFDB_PAM_CERT_VERIFICATION"' from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + if (cert_verification_opts == NULL) { + ret = confdb_get_string(pctx->rctx->cdb, mctx, CONFDB_MONITOR_CONF_ENTRY, + CONFDB_MONITOR_CERT_VERIFICATION, NULL, + &cert_verification_opts); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to read '"CONFDB_MONITOR_CERT_VERIFICATION"' from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + } + + ret = confdb_get_string(pctx->rctx->cdb, mctx, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_P11_URI, NULL, &uri); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to read '"CONFDB_PAM_P11_URI"' from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + req = pam_check_cert_send(mctx, ev, + pctx->ca_db, p11_child_timeout, + cert_verification_opts, pctx->sss_certmap_ctx, + uri, pd); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "pam_check_cert_send failed.\n"); + return ENOMEM; + } + + tevent_req_set_callback(req, pam_forwarder_cert_cb, preq); + return EAGAIN; +} + + +static int pam_forwarder(struct cli_ctx *cctx, int pam_cmd) +{ + struct pam_auth_req *preq; + struct pam_data *pd; + int ret; + struct pam_ctx *pctx = + talloc_get_type(cctx->rctx->pvt_ctx, struct pam_ctx); + struct tevent_req *req; + + preq = talloc_zero(cctx, struct pam_auth_req); + if (!preq) { + return ENOMEM; + } + preq->cctx = cctx; + preq->cert_auth_local = false; + preq->client_id_num = cctx->client_id_num; + + preq->pd = create_pam_data(preq); + if (!preq->pd) { + talloc_free(preq); + return ENOMEM; + } + pd = preq->pd; + + preq->is_uid_trusted = is_uid_trusted(cctx->creds, + pctx->trusted_uids_count, + pctx->trusted_uids); + + if (!preq->is_uid_trusted) { + DEBUG(SSSDBG_MINOR_FAILURE, "uid %"SPRIuid" is not trusted.\n", + client_euid(cctx->creds)); + } + + + pd->cmd = pam_cmd; + pd->priv = cctx->priv; + pd->client_id_num = cctx->client_id_num; + + ret = pam_forwarder_parse_data(cctx, pd); + if (ret == EAGAIN) { + req = sss_dp_get_domains_send(cctx->rctx, cctx->rctx, true, pd->domain); + if (req == NULL) { + ret = ENOMEM; + } else { + tevent_req_set_callback(req, pam_forwarder_cb, preq); + ret = EAGAIN; + } + goto done; + } else if (ret != EOK) { + goto done; + } + + /* Determine what domain type to contact */ + preq->req_dom_type = get_domain_request_type(preq, pctx); + + if (pd->cmd == SSS_PAM_AUTHENTICATE + && (pd->cli_flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH) + && !IS_SC_AUTHTOK(pd->authtok)) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Smartcard authentication required but authentication " + "token [%d][%s] is not suitable.\n", + sss_authtok_get_type(pd->authtok), + sss_authtok_type_to_str(sss_authtok_get_type(pd->authtok))); + ret = ERR_NO_CREDS; + goto done; + } + + /* Try backend first for authentication before doing local Smartcard + * authentication if a logon name is available. Otherwise try to derive + * the logon name from the certificate first. */ + if ((pd->cmd != SSS_PAM_AUTHENTICATE + || (pd->cmd == SSS_PAM_AUTHENTICATE && pd->logon_name == NULL)) + && may_do_cert_auth(pctx, pd)) { + ret = check_cert(cctx, cctx->ev, pctx, preq, pd); + /* Finish here */ + goto done; + } + + /* This is set to false inside passkey_local() if no passkey data is found. + * It is checked in pam_reply() to avoid an endless loop */ + preq->passkey_data_exists = true; + +#ifdef BUILD_PASSKEY + if ((pd->cmd == SSS_PAM_AUTHENTICATE)) { + if (may_do_passkey_auth(pctx, pd)) { + if (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY_KRB) { + ret = passkey_kerberos(pctx, preq->pd, preq); + goto done; + } else if ((sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY) || + (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_EMPTY)) { + ret = passkey_local(cctx, cctx->ev, pctx, preq, pd); + goto done; + } + } + } +#endif /* BUILD_PASSKEY */ + + ret = pam_check_user_search(preq); + +done: + return pam_check_user_done(preq, ret); +} + +static errno_t pam_user_by_cert_step(struct pam_auth_req *preq); +static void pam_forwarder_lookup_by_cert_done(struct tevent_req *req); +static void pam_forwarder_cert_cb(struct tevent_req *req) +{ + struct pam_auth_req *preq = tevent_req_callback_data(req, + struct pam_auth_req); + struct pam_data *pd; + errno_t ret = EOK; + const char *cert; + + ret = pam_check_cert_recv(req, preq, &preq->cert_list); + talloc_free(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_cert request failed.\n"); + goto done; + } + + pd = preq->pd; + + cert = sss_cai_get_cert(preq->cert_list); + + if (cert == NULL) { + if (pd->logon_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "No certificate found and no logon name given, " \ + "authentication not possible.\n"); + ret = ENOENT; + } else if (pd->cmd == SSS_PAM_PREAUTH + && (pd->cli_flags & PAM_CLI_FLAGS_TRY_CERT_AUTH)) { + DEBUG(SSSDBG_TRACE_ALL, + "try_cert_auth flag set but no certificate available, " + "request finished.\n"); + preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL; + pam_reply(preq); + return; + } else { + if (pd->cmd == SSS_PAM_AUTHENTICATE) { + DEBUG(SSSDBG_CRIT_FAILURE, + "No certificate returned, authentication failed.\n"); + preq->pd->pam_status = PAM_AUTH_ERR; + pam_reply(preq); + return; + } else { + ret = pam_check_user_search(preq); + } + + } + goto done; + } + + preq->current_cert = preq->cert_list; + ret = pam_user_by_cert_step(preq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "pam_user_by_cert_step failed.\n"); + goto done; + } + + return; + +done: + pam_check_user_done(preq, ret); +} + +static errno_t pam_user_by_cert_step(struct pam_auth_req *preq) +{ + struct cli_ctx *cctx = preq->cctx; + struct tevent_req *req; + struct pam_ctx *pctx = + talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx); + + if (preq->current_cert == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing certificate data.\n"); + return EINVAL; + } + + req = cache_req_user_by_cert_send(preq, cctx->ev, cctx->rctx, + pctx->rctx->ncache, 0, + preq->req_dom_type, NULL, + sss_cai_get_cert(preq->current_cert)); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "cache_req_user_by_cert_send failed.\n"); + return ENOMEM; + } + + tevent_req_set_callback(req, pam_forwarder_lookup_by_cert_done, preq); + return EOK; +} + +static errno_t get_results_from_all_domains(TALLOC_CTX *mem_ctx, + struct cache_req_result **results, + struct ldb_result **ldb_results) +{ + int ret; + size_t count = 0; + size_t c; + size_t d; + size_t r = 0; + struct ldb_result *res; + + for (d = 0; results != NULL && results[d] != NULL; d++) { + count += results[d]->count; + } + + res = talloc_zero(mem_ctx, struct ldb_result); + if (res == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero failed.\n"); + return ENOMEM; + } + + if (count == 0) { + *ldb_results = res; + return EOK; + } + + res->msgs = talloc_zero_array(res, struct ldb_message *, count); + if (res->msgs == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n"); + return ENOMEM; + } + res->count = count; + + for (d = 0; results != NULL && results[d] != NULL; d++) { + for (c = 0; c < results[d]->count; c++) { + if (r >= count) { + DEBUG(SSSDBG_CRIT_FAILURE, + "More results found then counted before.\n"); + ret = EINVAL; + goto done; + } + res->msgs[r++] = talloc_steal(res->msgs, results[d]->msgs[c]); + } + } + + *ldb_results = res; + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(res); + } + + return ret; +} + +/* Return true if hint is set for at least one domain */ +static bool get_user_name_hint(struct sss_domain_info *domains) +{ + struct sss_domain_info *d; + + DLIST_FOR_EACH(d, domains) { + if (d->user_name_hint == true) { + return true; + } + } + + return false; +} + +static void pam_forwarder_lookup_by_cert_done(struct tevent_req *req) +{ + int ret; + struct cache_req_result **results; + struct pam_auth_req *preq = tevent_req_callback_data(req, + struct pam_auth_req); + const char *cert_user = NULL; + size_t cert_count = 0; + size_t cert_user_count = 0; + struct ldb_result *cert_user_objs; + + ret = cache_req_recv(preq, req, &results); + talloc_zfree(req); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "cache_req_user_by_cert request failed.\n"); + goto done; + } + + if (ret == EOK) { + ret = get_results_from_all_domains(preq, results, + &cert_user_objs); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_results_from_all_domains failed.\n"); + goto done; + } + + sss_cai_set_cert_user_objs(preq->current_cert, cert_user_objs); + } + + preq->current_cert = sss_cai_get_next(preq->current_cert); + if (preq->current_cert != NULL) { + ret = pam_user_by_cert_step(preq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "pam_user_by_cert_step failed.\n"); + goto done; + } + return; + } + + sss_cai_check_users(&preq->cert_list, &cert_count, &cert_user_count); + DEBUG(SSSDBG_TRACE_ALL, + "Found [%zu] certificates and [%zu] related users.\n", + cert_count, cert_user_count); + + if (cert_user_count == 0) { + if (preq->pd->logon_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Missing logon name and no certificate user found.\n"); + ret = ENOENT; + goto done; + } + } else { + + if (preq->pd->logon_name == NULL) { + if (preq->pd->cmd != SSS_PAM_PREAUTH + && preq->pd->cmd != SSS_PAM_AUTHENTICATE) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Missing logon name only allowed during (pre-)auth.\n"); + ret = ENOENT; + goto done; + } + + if (cert_count > 1) { + for (preq->current_cert = preq->cert_list; + preq->current_cert != NULL; + preq->current_cert = sss_cai_get_next(preq->current_cert)) { + + ret = add_pam_cert_response(preq->pd, + preq->cctx->rctx->domains, "", + preq->current_cert, + get_user_name_hint(preq->cctx->rctx->domains) + ? SSS_PAM_CERT_INFO_WITH_HINT + : SSS_PAM_CERT_INFO); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "add_pam_cert_response failed.\n"); + preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL; + } + } + + ret = EOK; + preq->pd->pam_status = PAM_SUCCESS; + pam_reply(preq); + goto done; + } + + if (cert_user_count == 1) { + cert_user_objs = sss_cai_get_cert_user_objs(preq->cert_list); + if (cert_user_objs == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing certificate user.\n"); + ret = ENOENT; + goto done; + } + + cert_user = ldb_msg_find_attr_as_string( + cert_user_objs->msgs[0], + SYSDB_NAME, NULL); + if (cert_user == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Certificate user object has not name.\n"); + ret = ENOENT; + goto done; + } + + DEBUG(SSSDBG_FUNC_DATA, + "Found certificate user [%s].\n", cert_user); + + ret = sss_parse_name_for_domains(preq->pd, + preq->cctx->rctx->domains, + preq->cctx->rctx->default_domain, + cert_user, + &preq->pd->domain, + &preq->pd->user); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sss_parse_name_for_domains failed.\n"); + goto done; + } + } + + if (get_user_name_hint(preq->cctx->rctx->domains) + && preq->pd->cmd == SSS_PAM_PREAUTH) { + ret = add_pam_cert_response(preq->pd, + preq->cctx->rctx->domains, cert_user, + preq->cert_list, + SSS_PAM_CERT_INFO_WITH_HINT); + preq->pd->pam_status = PAM_SUCCESS; + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "add_pam_cert_response failed.\n"); + preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL; + } + ret = EOK; + pam_reply(preq); + goto done; + } + + /* Without user name hints the certificate must map to single user + * if no login name was given */ + if (cert_user == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "More than one user mapped to certificate.\n"); + ret = ERR_NO_CREDS; + goto done; + } + + /* If logon_name was not given during authentication add a + * SSS_PAM_CERT_INFO message to send the name to the caller. */ + if (preq->pd->cmd == SSS_PAM_AUTHENTICATE + && preq->pd->logon_name == NULL) { + ret = add_pam_cert_response(preq->pd, + preq->cctx->rctx->domains, cert_user, + preq->cert_list, + SSS_PAM_CERT_INFO); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "add_pam_cert_response failed.\n"); + preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL; + goto done; + } + } + + /* cert_user will be returned to the PAM client as user name, so + * we can use it here already e.g. to set in initgroups timeout */ + preq->pd->logon_name = talloc_strdup(preq->pd, cert_user); + if (preq->pd->logon_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + } + } + + if (preq->user_obj == NULL) { + ret = pam_check_user_search(preq); + } else { + ret = EOK; + } + + if (ret == EOK) { + pam_dom_forwarder(preq); + } + +done: + pam_check_user_done(preq, ret); +} + +static void pam_forwarder_cb(struct tevent_req *req) +{ + struct pam_auth_req *preq = tevent_req_callback_data(req, + struct pam_auth_req); + struct cli_ctx *cctx = preq->cctx; + struct pam_data *pd; + errno_t ret = EOK; + struct pam_ctx *pctx = + talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx); + + ret = sss_dp_get_domains_recv(req); + talloc_free(req); + if (ret != EOK) { + goto done; + } + + ret = p11_refresh_certmap_ctx(pctx, pctx->rctx->domains); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "p11_refresh_certmap_ctx failed, " + "certificate matching might not work as expected"); + } + + pd = preq->pd; + + ret = pam_forwarder_parse_data(cctx, pd); + if (ret == EAGAIN) { + DEBUG(SSSDBG_TRACE_FUNC, "Assuming %s is a UPN\n", pd->logon_name); + /* If not, cache_req will error out later */ + pd->user = talloc_strdup(pd, pd->logon_name); + if (pd->user == NULL) { + ret = ENOMEM; + goto done; + } + pd->domain = NULL; + } else if (ret != EOK) { + ret = EINVAL; + goto done; + } + + /* try backend first for authentication before doing local Smartcard + * authentication */ + if (pd->cmd != SSS_PAM_AUTHENTICATE && may_do_cert_auth(pctx, pd)) { + ret = check_cert(cctx, cctx->ev, pctx, preq, pd); + /* Finish here */ + goto done; + } + +#ifdef BUILD_PASSKEY + /* This is set to false inside passkey_local() if no passkey data is found. + * It is checked in pam_reply() to avoid an endless loop */ + preq->passkey_data_exists = true; + + if ((pd->cmd == SSS_PAM_AUTHENTICATE)) { + if (may_do_passkey_auth(pctx, pd)) { + if (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY_KRB) { + ret = passkey_kerberos(pctx, preq->pd, preq); + goto done; + } else if ((sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY) || + (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_EMPTY)) { + ret = passkey_local(cctx, cctx->ev, pctx, preq, pd); + goto done; + } + } + } +#endif /* BUILD_PASSKEY */ + + ret = pam_check_user_search(preq); + +done: + pam_check_user_done(preq, ret); +} + +static void pam_check_user_search_next(struct tevent_req *req); +static void pam_check_user_search_lookup(struct tevent_req *req); +static void pam_check_user_search_done(struct pam_auth_req *preq, int ret, + struct cache_req_result *result); + +/* lookup the user uid from the cache first, + * then we'll refresh initgroups if needed */ +int pam_check_user_search(struct pam_auth_req *preq) +{ + struct tevent_req *dpreq; + struct cache_req_data *data; + + data = cache_req_data_name(preq, + CACHE_REQ_INITGROUPS, + preq->pd->logon_name); + if (data == NULL) { + return ENOMEM; + } + + cache_req_data_set_bypass_cache(data, false); + cache_req_data_set_bypass_dp(data, true); + cache_req_data_set_requested_domains(data, preq->pd->requested_domains); + + dpreq = cache_req_send(preq, + preq->cctx->rctx->ev, + preq->cctx->rctx, + preq->cctx->rctx->ncache, + 0, + preq->req_dom_type, + NULL, + data); + if (!dpreq) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Out of memory sending data provider request\n"); + return ENOMEM; + } + + tevent_req_set_callback(dpreq, pam_check_user_search_next, preq); + + /* tell caller we are in an async call */ + return EAGAIN; +} + +static void pam_check_user_search_next(struct tevent_req *req) +{ + struct pam_auth_req *preq; + struct pam_ctx *pctx; + struct cache_req_result *result = NULL; + struct cache_req_data *data; + struct tevent_req *dpreq; + int ret; + + preq = tevent_req_callback_data(req, struct pam_auth_req); + pctx = talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx); + + ret = cache_req_single_domain_recv(preq, req, &result); + talloc_zfree(req); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "Cache lookup failed, trying to get fresh " + "data from the backend.\n"); + } + + DEBUG(SSSDBG_TRACE_ALL, "PAM initgroups scheme [%s].\n", + pam_initgroup_enum_to_string(pctx->initgroups_scheme)); + + if (ret == EOK) { + bool user_has_session = false; + + if (pctx->initgroups_scheme == PAM_INITGR_NO_SESSION) { + uid_t uid = ldb_msg_find_attr_as_uint64(result->msgs[0], + SYSDB_UIDNUM, 0); + if (!uid) { + DEBUG(SSSDBG_CRIT_FAILURE, "A user with no UID?\n"); + talloc_zfree(preq->cctx); + return; + } + + /* If a user already has a session on the system, we take the + * cache for granted and do not force an online lookup. This is + * because in most cases the user is just trying to authenticate + * but not create a new session (sudo, lockscreen, polkit, etc.) + * An online refresh in this situation would just delay operations + * without providing any useful additional information. + */ + (void)check_if_uid_is_active(uid, &user_has_session); + + DEBUG(SSSDBG_TRACE_ALL, "Found %s session for uid %"SPRIuid".\n", + user_has_session ? "a" : "no", uid); + } + + /* The initgr cache is used to make sure that during a single PAM + * session (auth, acct_mgtm, ....) the backend is contacted only + * once. logon_name is the name provided by the PAM client and + * will not be modified during the request, so it makes sense to + * use it here instead od the pd->user. + */ + ret = pam_initgr_check_timeout(pctx->id_table, preq->pd->logon_name); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "Could not look up initgroup timeout\n"); + } + + if ((ret == EOK) || user_has_session + || pctx->initgroups_scheme == PAM_INITGR_NEVER) { + DEBUG(SSSDBG_TRACE_ALL, "No new initgroups needed because:\n"); + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_ALL, "PAM initgr cache still valid.\n"); + } else if (user_has_session) { + DEBUG(SSSDBG_TRACE_ALL, "there is a active session for " + "user [%s].\n", preq->pd->logon_name); + } else if (pctx->initgroups_scheme == PAM_INITGR_NEVER) { + DEBUG(SSSDBG_TRACE_ALL, "initgroups scheme is 'never'.\n"); + } + pam_check_user_search_done(preq, EOK, result); + return; + } + } + + /* If we get here it means the user was not found or does not have a + * session, or initgr has not been cached before, so we force a new + * online lookup */ + data = cache_req_data_name(preq, + CACHE_REQ_INITGROUPS, + preq->pd->logon_name); + if (data == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory\n"); + talloc_zfree(preq->cctx); + return; + } + cache_req_data_set_bypass_cache(data, true); + cache_req_data_set_bypass_dp(data, false); + cache_req_data_set_requested_domains(data, preq->pd->requested_domains); + + dpreq = cache_req_send(preq, + preq->cctx->rctx->ev, + preq->cctx->rctx, + preq->cctx->rctx->ncache, + 0, + preq->req_dom_type, + NULL, + data); + if (!dpreq) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Out of memory sending data provider request\n"); + talloc_zfree(preq->cctx); + return; + } + + tevent_req_set_callback(dpreq, pam_check_user_search_lookup, preq); +} + +static void pam_check_user_search_lookup(struct tevent_req *req) +{ + struct cache_req_result *result; + struct pam_auth_req *preq; + int ret; + + preq = tevent_req_callback_data(req, struct pam_auth_req); + + ret = cache_req_single_domain_recv(preq, req, &result); + talloc_zfree(req); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Fatal error, killing connection!\n"); + talloc_zfree(preq->cctx); + return; + } + + pam_check_user_search_done(preq, ret, result); +} + +static void pam_check_user_search_done(struct pam_auth_req *preq, int ret, + struct cache_req_result *result) +{ + struct pam_ctx *pctx; + + pctx = talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx); + + if (ret == EOK) { + preq->user_obj = result->msgs[0]; + pd_set_primary_name(preq->user_obj, preq->pd); + preq->domain = result->domain; + + ret = pam_initgr_cache_set(pctx->rctx->ev, + pctx->id_table, + preq->pd->logon_name, + pctx->id_timeout); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not save initgr timestamp." + "Proceeding with PAM actions\n"); + } + + pam_dom_forwarder(preq); + } + + ret = pam_check_user_done(preq, ret); + if (ret != EOK) { + preq->pd->pam_status = PAM_SYSTEM_ERR; + pam_reply(preq); + } +} + +int pam_check_user_done(struct pam_auth_req *preq, int ret) +{ + switch (ret) { + case EOK: + break; + + case EAGAIN: + /* performing async request, just return */ + break; + + case ENOENT: + preq->pd->pam_status = PAM_USER_UNKNOWN; + pam_reply(preq); + break; + + case ERR_P11_PIN_LOCKED: + preq->pd->pam_status = PAM_AUTH_ERR; + pam_reply(preq); + break; + + case ERR_NO_CREDS: + preq->pd->pam_status = PAM_CRED_INSUFFICIENT; + pam_reply(preq); + break; + + default: + preq->pd->pam_status = PAM_SYSTEM_ERR; + pam_reply(preq); + break; + } + + return EOK; +} + +static errno_t pam_is_last_online_login_fresh(struct sss_domain_info *domain, + const char* user, + int cached_auth_timeout, + bool *_result) +{ + errno_t ret; + bool result = true; + uint64_t last_login; + + ret = pam_get_last_online_auth_with_curr_token(domain, user, &last_login); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "sysdb_get_last_online_auth_with_curr_token failed: %s:[%d]\n", + sss_strerror(ret), ret); + goto done; + } + + result = time(NULL) < (last_login + cached_auth_timeout); + ret = EOK; + +done: + if (ret == EOK) { + *_result = result; + } + return ret; +} + +static bool pam_is_authtok_cachable(struct sss_auth_token *authtok) +{ + enum sss_authtok_type type; + bool cachable = false; + + type = sss_authtok_get_type(authtok); + if (type == SSS_AUTHTOK_TYPE_PASSWORD) { + cachable = true; + } else { + DEBUG(SSSDBG_TRACE_LIBS, "Authentication token can't be cached\n"); + } + + return cachable; +} + +static bool pam_can_user_cache_auth(struct sss_domain_info *domain, + int pam_cmd, + struct sss_auth_token *authtok, + const char* user, + bool cached_auth_failed) +{ + errno_t ret; + bool result = false; + + if (cached_auth_failed) { + /* Do not retry indefinitely */ + return false; + } + + if (!domain->cache_credentials || domain->cached_auth_timeout <= 0) { + return false; + } + + if (pam_cmd == SSS_PAM_PREAUTH + || (pam_cmd == SSS_PAM_AUTHENTICATE + && pam_is_authtok_cachable(authtok))) { + + ret = pam_is_last_online_login_fresh(domain, user, + domain->cached_auth_timeout, + &result); + if (ret != EOK) { + /* non-critical, consider fail as 'non-fresh value' */ + DEBUG(SSSDBG_MINOR_FAILURE, + "pam_is_last_online_login_fresh failed: %s:[%d]\n", + sss_strerror(ret), ret); + } + } + + return result; +} + +static void pam_dom_forwarder(struct pam_auth_req *preq) +{ + TALLOC_CTX *tmp_ctx = NULL; + int ret; + struct pam_ctx *pctx = + talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx); + const char *cert_user; + struct ldb_result *cert_user_objs; + bool sc_auth; + bool passkey_auth; + size_t c; + char *local_policy = NULL; + bool found = false; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return; + } + + if (!preq->pd->domain) { + preq->pd->domain = preq->domain->name; + } + + /* Untrusted users can access only public domains. */ + if (!preq->is_uid_trusted && + !is_domain_public(preq->pd->domain, pctx->public_domains, + pctx->public_domains_count)) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Untrusted user %"SPRIuid" cannot access non-public domain %s.\n", + client_euid(preq->cctx->creds), preq->pd->domain); + preq->pd->pam_status = PAM_PERM_DENIED; + pam_reply(preq); + return; + } + + /* skip this domain if not requested and the user is trusted + * as untrusted users can't request a domain */ + if (preq->is_uid_trusted && + !is_domain_requested(preq->pd, preq->pd->domain)) { + preq->pd->pam_status = PAM_USER_UNKNOWN; + pam_reply(preq); + return; + } + + if (pam_can_user_cache_auth(preq->domain, + preq->pd->cmd, + preq->pd->authtok, + preq->pd->user, + preq->cached_auth_failed)) { + preq->use_cached_auth = true; + pam_reply(preq); + return; + } + + /* Skip online auth when local auth policy = only */ +#ifdef BUILD_PASSKEY + if (may_do_cert_auth(pctx, preq->pd) || may_do_passkey_auth(pctx, preq->pd)) { +#else + if (may_do_cert_auth(pctx, preq->pd)) { +#endif /* BUILD_PASSKEY */ + if (preq->domain->name != NULL) { + ret = pam_eval_local_auth_policy(preq->cctx, pctx, preq->pd, preq, + &sc_auth, + &passkey_auth, + &local_policy); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to evaluate local auth policy\n"); + preq->pd->pam_status = PAM_AUTH_ERR; + pam_reply(preq); + return; + } + } + } + + if (may_do_cert_auth(pctx, preq->pd) && preq->cert_list != NULL) { + /* Check if user matches certificate user */ + found = false; + for (preq->current_cert = preq->cert_list; + preq->current_cert != NULL; + preq->current_cert = sss_cai_get_next(preq->current_cert)) { + + cert_user_objs = sss_cai_get_cert_user_objs(preq->current_cert); + if (cert_user_objs == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Unexpected missing certificate user, " + "trying next certificate.\n"); + continue; + } + + for (c = 0; c < cert_user_objs->count; c++) { + cert_user = ldb_msg_find_attr_as_string(cert_user_objs->msgs[c], + SYSDB_NAME, NULL); + if (cert_user == NULL) { + /* Even if there might be other users mapped to the + * certificate a missing SYSDB_NAME indicates some critical + * condition which justifies that the whole request is aborted + * */ + DEBUG(SSSDBG_CRIT_FAILURE, + "Certificate user object has no name.\n"); + preq->pd->pam_status = PAM_USER_UNKNOWN; + pam_reply(preq); + return; + } + + if (ldb_dn_compare(cert_user_objs->msgs[c]->dn, + preq->user_obj->dn) == 0) { + found = true; + if (preq->pd->cmd == SSS_PAM_PREAUTH) { + ret = sss_authtok_set_sc(preq->pd->authtok, + SSS_AUTHTOK_TYPE_SC_PIN, NULL, 0, + sss_cai_get_token_name(preq->current_cert), 0, + sss_cai_get_module_name(preq->current_cert), 0, + sss_cai_get_key_id(preq->current_cert), 0, + sss_cai_get_label(preq->current_cert), 0); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sss_authtok_set_sc failed, Smartcard " + "authentication detection might fail in " + "the backend.\n"); + } + + ret = add_pam_cert_response(preq->pd, + preq->cctx->rctx->domains, + cert_user, + preq->current_cert, + SSS_PAM_CERT_INFO); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "add_pam_cert_response failed.\n"); + preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL; + } + } + + } + } + } + + if (found) { + if (local_policy != NULL && strcasecmp(local_policy, "only") == 0) { + talloc_free(tmp_ctx); + DEBUG(SSSDBG_IMPORTANT_INFO, "Local auth only set, skipping online auth\n"); + if (preq->pd->cmd == SSS_PAM_PREAUTH) { + preq->pd->pam_status = PAM_SUCCESS; + } else if (preq->pd->cmd == SSS_PAM_AUTHENTICATE + && IS_SC_AUTHTOK(preq->pd->authtok) + && preq->cert_auth_local) { + preq->pd->pam_status = PAM_SUCCESS; + preq->callback = pam_reply; + } + + pam_reply(preq); + return; + } + + /* We are done if we do not have to call the backend */ + if (preq->pd->cmd == SSS_PAM_AUTHENTICATE + && preq->cert_auth_local) { + preq->pd->pam_status = PAM_SUCCESS; + preq->callback = pam_reply; + pam_reply(preq); + return; + } + } else { + if (preq->pd->cmd == SSS_PAM_PREAUTH) { + DEBUG(SSSDBG_TRACE_FUNC, + "User and certificate user do not match, " + "continue with other authentication methods.\n"); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "User and certificate user do not match.\n"); + preq->pd->pam_status = PAM_AUTH_ERR; + pam_reply(preq); + return; + } + } + } + + if (local_policy != NULL && strcasecmp(local_policy, "only") == 0) { + talloc_free(tmp_ctx); + DEBUG(SSSDBG_IMPORTANT_INFO, "Local auth only set, skipping online auth\n"); + if (preq->pd->cmd == SSS_PAM_PREAUTH) { + preq->pd->pam_status = PAM_SUCCESS; + } else if (preq->pd->cmd == SSS_PAM_AUTHENTICATE && IS_SC_AUTHTOK(preq->pd->authtok)) { + /* Trigger offline smartcardcard autheitcation */ + preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL; + } + + pam_reply(preq); + return; + } + + preq->callback = pam_reply; + ret = pam_dp_send_req(preq); + DEBUG(SSSDBG_CONF_SETTINGS, "pam_dp_send_req returned %d\n", ret); + + talloc_free(tmp_ctx); + + if (ret != EOK) { + preq->pd->pam_status = PAM_SYSTEM_ERR; + pam_reply(preq); + } +} + +static int pam_cmd_authenticate(struct cli_ctx *cctx) { + DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_authenticate\n"); + return pam_forwarder(cctx, SSS_PAM_AUTHENTICATE); +} + +static int pam_cmd_setcred(struct cli_ctx *cctx) { + DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_setcred\n"); + return pam_forwarder(cctx, SSS_PAM_SETCRED); +} + +static int pam_cmd_acct_mgmt(struct cli_ctx *cctx) { + DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_acct_mgmt\n"); + return pam_forwarder(cctx, SSS_PAM_ACCT_MGMT); +} + +static int pam_cmd_open_session(struct cli_ctx *cctx) { + DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_open_session\n"); + return pam_forwarder(cctx, SSS_PAM_OPEN_SESSION); +} + +static int pam_cmd_close_session(struct cli_ctx *cctx) { + DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_close_session\n"); + return pam_forwarder(cctx, SSS_PAM_CLOSE_SESSION); +} + +static int pam_cmd_chauthtok(struct cli_ctx *cctx) { + DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_chauthtok\n"); + return pam_forwarder(cctx, SSS_PAM_CHAUTHTOK); +} + +static int pam_cmd_chauthtok_prelim(struct cli_ctx *cctx) { + DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_chauthtok_prelim\n"); + return pam_forwarder(cctx, SSS_PAM_CHAUTHTOK_PRELIM); +} + +static int pam_cmd_preauth(struct cli_ctx *cctx) +{ + DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_preauth\n"); + return pam_forwarder(cctx, SSS_PAM_PREAUTH); +} + +struct cli_protocol_version *register_cli_protocol_version(void) +{ + static struct cli_protocol_version pam_cli_protocol_version[] = { + {3, "2009-09-14", "make cli_pid mandatory"}, + {2, "2009-05-12", "new format <type><size><data>"}, + {1, "2008-09-05", "initial version, \\0 terminated strings"}, + {0, NULL, NULL} + }; + + return pam_cli_protocol_version; +} + +struct sss_cmd_table *get_pam_cmds(void) +{ + static struct sss_cmd_table sss_cmds[] = { + {SSS_GET_VERSION, sss_cmd_get_version}, + {SSS_PAM_AUTHENTICATE, pam_cmd_authenticate}, + {SSS_PAM_SETCRED, pam_cmd_setcred}, + {SSS_PAM_ACCT_MGMT, pam_cmd_acct_mgmt}, + {SSS_PAM_OPEN_SESSION, pam_cmd_open_session}, + {SSS_PAM_CLOSE_SESSION, pam_cmd_close_session}, + {SSS_PAM_CHAUTHTOK, pam_cmd_chauthtok}, + {SSS_PAM_CHAUTHTOK_PRELIM, pam_cmd_chauthtok_prelim}, + {SSS_PAM_PREAUTH, pam_cmd_preauth}, + {SSS_GSSAPI_INIT, pam_cmd_gssapi_init}, + {SSS_GSSAPI_SEC_CTX, pam_cmd_gssapi_sec_ctx}, + {SSS_CLI_NULL, NULL} + }; + + return sss_cmds; +} + +errno_t +pam_set_last_online_auth_with_curr_token(struct sss_domain_info *domain, + const char *username, + uint64_t value) +{ + TALLOC_CTX *tmp_ctx; + struct sysdb_attrs *attrs; + int ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + attrs = sysdb_new_attrs(tmp_ctx); + if (attrs == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_add_time_t(attrs, + SYSDB_LAST_ONLINE_AUTH_WITH_CURR_TOKEN, + value); + if (ret != EOK) { goto done; } + + ret = sysdb_set_user_attr(domain, username, attrs, SYSDB_MOD_REP); + if (ret != EOK) { goto done; } + +done: + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_FUNC, "Error: %d (%s)\n", ret, sss_strerror(ret)); + } + + talloc_zfree(tmp_ctx); + return ret; +} + +static errno_t +pam_null_last_online_auth_with_curr_token(struct sss_domain_info *domain, + const char *username) +{ + return pam_set_last_online_auth_with_curr_token(domain, username, 0); +} + +static errno_t +pam_get_last_online_auth_with_curr_token(struct sss_domain_info *domain, + const char *name, + uint64_t *_value) +{ + TALLOC_CTX *tmp_ctx = NULL; + const char *attrs[] = { SYSDB_LAST_ONLINE_AUTH_WITH_CURR_TOKEN, NULL }; + struct ldb_message *ldb_msg; + uint64_t value = 0; + errno_t ret; + + if (name == NULL || *name == '\0') { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing user name.\n"); + ret = EINVAL; + goto done; + } + + if (domain->sysdb == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing sysdb db context.\n"); + ret = EINVAL; + goto done; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_search_user_by_name(tmp_ctx, domain, name, attrs, &ldb_msg); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sysdb_search_user_by_name failed [%d][%s].\n", + ret, strerror(ret)); + goto done; + } + + /* Check offline_auth_cache_timeout */ + value = ldb_msg_find_attr_as_uint64(ldb_msg, + SYSDB_LAST_ONLINE_AUTH_WITH_CURR_TOKEN, + 0); + ret = EOK; + +done: + if (ret == EOK) { + *_value = value; + } + + talloc_free(tmp_ctx); + return ret; +} diff --git a/src/responder/pam/pamsrv_dp.c b/src/responder/pam/pamsrv_dp.c new file mode 100644 index 0000000..881352e --- /dev/null +++ b/src/responder/pam/pamsrv_dp.c @@ -0,0 +1,106 @@ +/* + SSSD + + NSS Responder - Data Provider Interfaces + + Copyright (C) Simo Sorce <ssorce@redhat.com> 2008 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <talloc.h> +#include <tevent.h> +#include <security/pam_modules.h> + +#include "util/util.h" +#include "util/sss_pam_data.h" +#include "responder/pam/pamsrv.h" +#include "sss_iface/sss_iface_async.h" + +static void +pam_dp_send_req_done(struct tevent_req *subreq); + +errno_t +pam_dp_send_req(struct pam_auth_req *preq) +{ + struct tevent_req *subreq; + struct be_conn *be_conn; + errno_t ret; + + ret = sss_dp_get_domain_conn(preq->cctx->rctx, preq->domain->conn_name, + &be_conn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "The Data Provider connection for %s is not " + "available! This maybe a bug, it shouldn't happen!\n", + preq->domain->conn_name); + return EIO; + } + + DEBUG(SSSDBG_CONF_SETTINGS, "Sending request with the following data:\n"); + DEBUG_PAM_DATA(SSSDBG_CONF_SETTINGS, preq->pd); + + subreq = sbus_call_dp_dp_pamHandler_send(preq, be_conn->conn, + be_conn->bus_name, SSS_BUS_PATH, preq->pd); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + return ENOMEM; + } + + tevent_req_set_callback(subreq, pam_dp_send_req_done, preq); + + return EOK; +} + +static void +pam_dp_send_req_done(struct tevent_req *subreq) +{ + struct pam_data *pam_response; + struct response_data *resp; + struct pam_auth_req *preq; + errno_t ret; + + preq = tevent_req_callback_data(subreq, struct pam_auth_req); + + ret = sbus_call_dp_dp_pamHandler_recv(preq, subreq, &pam_response); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "PAM handler failed [%d]: %s\n", + ret, sss_strerror(ret)); + preq->pd->pam_status = PAM_SYSTEM_ERR; + goto done; + } + + preq->pd->pam_status = pam_response->pam_status; + preq->pd->account_locked = pam_response->account_locked; + + DEBUG(SSSDBG_FUNC_DATA, "received: [%d (%s)][%s]\n", + pam_response->pam_status, + pam_strerror(NULL, pam_response->pam_status), + preq->pd->domain); + + for (resp = pam_response->resp_list; resp != NULL; resp = resp->next) { + talloc_steal(preq->pd, resp); + + if (resp->next == NULL) { + resp->next = preq->pd->resp_list; + preq->pd->resp_list = pam_response->resp_list; + break; + } + } + + talloc_zfree(pam_response); + +done: + preq->callback(preq); +} diff --git a/src/responder/pam/pamsrv_gssapi.c b/src/responder/pam/pamsrv_gssapi.c new file mode 100644 index 0000000..e4da4c4 --- /dev/null +++ b/src/responder/pam/pamsrv_gssapi.c @@ -0,0 +1,1043 @@ +/* + Authors: + Pavel BÅ™ezina <pbrezina@redhat.com> + + Copyright (C) 2020 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <errno.h> +#include <gssapi.h> +#include <gssapi/gssapi_ext.h> +#include <gssapi/gssapi_krb5.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <talloc.h> +#include <ldb.h> + +#include "confdb/confdb.h" +#include "db/sysdb.h" +#include "responder/common/responder_packet.h" +#include "responder/common/responder.h" +#include "responder/common/cache_req/cache_req.h" +#include "responder/pam/pamsrv.h" +#include "sss_client/sss_cli.h" +#include "util/util.h" +#include "util/sss_utf8.h" + +static errno_t read_str(size_t body_len, + uint8_t *body, + size_t *pctr, + const char **_str) +{ + size_t i; + + for (i = *pctr; i < body_len && body[i] != 0; i++) { + /* counting */ + } + + if (i >= body_len) { + return EINVAL; + } + + if (!sss_utf8_check(&body[*pctr], i - *pctr)) { + DEBUG(SSSDBG_CRIT_FAILURE, "Body is not UTF-8 string!\n"); + return EINVAL; + } + + *_str = (const char *)&body[*pctr]; + *pctr = i + 1; + + return EOK; +} + +static bool pam_gssapi_should_check_upn(struct pam_ctx *pam_ctx, + struct sss_domain_info *domain) +{ + if (domain->gssapi_check_upn != NULL) { + if (strcasecmp(domain->gssapi_check_upn, "true") == 0) { + return true; + } + + if (strcasecmp(domain->gssapi_check_upn, "false") == 0) { + return false; + } + + DEBUG(SSSDBG_MINOR_FAILURE, "Invalid value for %s: %s\n", + CONFDB_PAM_GSSAPI_CHECK_UPN, domain->gssapi_check_upn); + return false; + } + + return pam_ctx->gssapi_check_upn; +} + +static int pam_gssapi_check_indicators(TALLOC_CTX *mem_ctx, + const char *pam_service, + char **gssapi_indicators_map, + char **indicators) +{ + char *authind = NULL; + size_t pam_len = strlen(pam_service); + char **map = gssapi_indicators_map; + char **result = NULL; + int res; + + authind = talloc_strdup(mem_ctx, ""); + if (authind == NULL) { + return ENOMEM; + } + + for (int i = 0; map[i]; i++) { + if (map[i][0] == '-') { + DEBUG(SSSDBG_TRACE_FUNC, + "Indicators aren't used for [%s]\n", + pam_service); + talloc_free(authind); + return EOK; + } + if (!strchr(map[i], ':')) { + authind = talloc_asprintf_append(authind, "%s ", map[i]); + if (authind == NULL) { + /* Since we allocate on pam_ctx, caller will free it */ + return ENOMEM; + } + continue; + } + + res = strncmp(map[i], pam_service, pam_len); + if (res == 0) { + if (strlen(map[i]) > pam_len) { + if (map[i][pam_len] != ':') { + /* different PAM service, skip it */ + continue; + } + + if (map[i][pam_len + 1] == '-') { + DEBUG(SSSDBG_TRACE_FUNC, + "Indicators aren't used for [%s]\n", + pam_service); + talloc_free(authind); + return EOK; + } + + authind = talloc_asprintf_append(authind, "%s ", + map[i] + (pam_len + 1)); + if (authind == NULL) { + /* Since we allocate on pam_ctx, caller will free it */ + return ENOMEM; + } + } else { + DEBUG(SSSDBG_MINOR_FAILURE, "Invalid value for %s: [%s]\n", + CONFDB_PAM_GSSAPI_INDICATORS_MAP, map[i]); + talloc_free(authind); + return EINVAL; + } + } + } + + res = ENOENT; + map = NULL; + + if (authind[0] == '\0') { + /* empty list of per-service indicators -> skip */ + goto done; + } + + /* trim a space after the final indicator + * to prevent split_on_separator() to fail */ + authind[strlen(authind) - 1] = '\0'; + + res = split_on_separator(mem_ctx, authind, ' ', true, true, + &map, NULL); + if (res != 0) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Cannot parse list of indicators: [%s]\n", authind); + res = EINVAL; + goto done; + } + + res = diff_string_lists(mem_ctx, indicators, map, NULL, NULL, &result); + if (res != 0) { + DEBUG(SSSDBG_FATAL_FAILURE,"Cannot diff lists of indicators\n"); + res = EINVAL; + goto done; + } + + if (result && result[0] != NULL) { + for (int i = 0; result[i]; i++) { + DEBUG(SSSDBG_TRACE_FUNC, + "indicator [%s] is allowed for PAM service [%s]\n", + result[i], pam_service); + } + res = EOK; + goto done; + } + + res = EPERM; + +done: + talloc_free(result); + talloc_free(authind); + talloc_free(map); + return res; +} + +static bool pam_gssapi_allowed(struct pam_ctx *pam_ctx, + struct sss_domain_info *domain, + const char *service) +{ + char **list = pam_ctx->gssapi_services; + + if (domain->gssapi_services != NULL) { + list = domain->gssapi_services; + } + + if (strcmp(service, "-") == 0) { + /* Dash is used as a "not set" value to allow to explicitly disable + * gssapi auth for specific domain. Disallow this service to be safe. + */ + DEBUG(SSSDBG_TRACE_FUNC, "Dash - was used as a PAM service name. " + "GSSAPI authentication is not allowed.\n"); + return false; + } + + return string_in_list(service, list, true); +} + +static char *pam_gssapi_target(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain) +{ + return talloc_asprintf(mem_ctx, "host@%s", domain->hostname); +} + +static const char *pam_gssapi_get_upn(struct cache_req_result *result) +{ + if (result->count == 0) { + return NULL; + } + + /* Canonical UPN should be available if the user has kinited through SSSD. + * Use it as a hint for GSSAPI. Default to empty string so it may be + * more easily transffered over the wire. */ + return ldb_msg_find_attr_as_string(result->msgs[0], SYSDB_CANONICAL_UPN, ""); +} + +static const char *pam_gssapi_get_name(struct cache_req_result *result) +{ + if (result->count == 0) { + return NULL; + } + + /* Return username known to SSSD to make sure we authenticated as the same + * user after GSSAPI handshake. */ + return ldb_msg_find_attr_as_string(result->msgs[0], SYSDB_NAME, NULL); +} + +static errno_t pam_gssapi_init_parse(struct cli_protocol *pctx, + const char **_service, + const char **_username) +{ + size_t body_len; + size_t pctr = 0; + uint8_t *body; + errno_t ret; + + sss_packet_get_body(pctx->creq->in, &body, &body_len); + if (body == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid input\n"); + return EINVAL; + } + + ret = read_str(body_len, body, &pctr, _service); + if (ret != EOK) { + return ret; + } + + ret = read_str(body_len, body, &pctr, _username); + if (ret != EOK) { + return ret; + } + + return EOK; +} + +static errno_t pam_gssapi_init_reply(struct cli_protocol *pctx, + const char *domain, + const char *target, + const char *upn, + const char *username) +{ + size_t reply_len; + size_t body_len; + size_t pctr; + uint8_t *body; + errno_t ret; + + ret = sss_packet_new(pctx->creq, 0, sss_packet_get_cmd(pctx->creq->in), + &pctx->creq->out); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create a new packet [%d]; %s\n", + ret, sss_strerror(ret)); + return ret; + } + + reply_len = strlen(username) + 1; + reply_len += strlen(domain) + 1; + reply_len += strlen(target) + 1; + reply_len += strlen(upn) + 1; + + ret = sss_packet_grow(pctx->creq->out, reply_len); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create response: %s\n", + sss_strerror(ret)); + return ret; + } + + sss_packet_get_body(pctx->creq->out, &body, &body_len); + + pctr = 0; + SAFEALIGN_SETMEM_STRING(&body[pctr], username, strlen(username) + 1, &pctr); + SAFEALIGN_SETMEM_STRING(&body[pctr], domain, strlen(domain) + 1, &pctr); + SAFEALIGN_SETMEM_STRING(&body[pctr], target, strlen(target) + 1, &pctr); + SAFEALIGN_SETMEM_STRING(&body[pctr], upn, strlen(upn) + 1, &pctr); + + return EOK; +} + +struct gssapi_init_state { + struct cli_ctx *cli_ctx; + const char *username; + const char *service; +}; + +static void pam_cmd_gssapi_init_done(struct tevent_req *req); + +int pam_cmd_gssapi_init(struct cli_ctx *cli_ctx) +{ + struct gssapi_init_state *state; + struct cli_protocol *pctx; + struct tevent_req *req; + const char *username; + const char *service; + const char *attrs[] = { SYSDB_NAME, SYSDB_CANONICAL_UPN, NULL }; + errno_t ret; + + state = talloc_zero(cli_ctx, struct gssapi_init_state); + if (state == NULL) { + return ENOMEM; + } + + pctx = talloc_get_type(cli_ctx->protocol_ctx, struct cli_protocol); + + ret = pam_gssapi_init_parse(pctx, &service, &username); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse input [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + state->cli_ctx = cli_ctx; + state->service = service; + state->username = username; + + DEBUG(SSSDBG_TRACE_ALL, + "Requesting GSSAPI authentication of [%s] in service [%s]\n", + username, service); + + req = cache_req_user_by_name_attrs_send(cli_ctx, cli_ctx->ev, cli_ctx->rctx, + cli_ctx->rctx->ncache, 0, + NULL, username, attrs); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(req, pam_cmd_gssapi_init_done, state); + + ret = EOK; + +done: + if (ret != EOK) { + sss_cmd_send_error(cli_ctx, ret); + sss_cmd_done(cli_ctx, NULL); + } + + return EOK; +} + +static void pam_cmd_gssapi_init_done(struct tevent_req *req) +{ + struct gssapi_init_state *state; + struct cache_req_result *result; + struct cli_protocol *pctx; + struct pam_ctx *pam_ctx; + const char *username; + const char *upn; + char *target; + errno_t ret; + + state = tevent_req_callback_data(req, struct gssapi_init_state); + pctx = talloc_get_type(state->cli_ctx->protocol_ctx, struct cli_protocol); + pam_ctx = talloc_get_type(state->cli_ctx->rctx->pvt_ctx, struct pam_ctx); + + ret = cache_req_user_by_name_attrs_recv(state, req, &result); + talloc_zfree(req); + if (ret == ENOENT || ret == ERR_DOMAIN_NOT_FOUND) { + ret = ENOENT; + goto done; + } else if (ret != EOK) { + goto done; + } + + if (!pam_gssapi_allowed(pam_ctx, result->domain, state->service)) { + ret = ENOTSUP; + goto done; + } + + username = pam_gssapi_get_name(result); + if (username == NULL) { + /* User with no name? */ + ret = ERR_INTERNAL; + goto done; + } + + upn = pam_gssapi_get_upn(result); + if (upn == NULL) { + /* UPN hint may be an empty string, but not NULL. */ + ret = ERR_INTERNAL; + goto done; + } + + target = pam_gssapi_target(state, result->domain); + if (target == NULL) { + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Trying GSSAPI auth: User[%s], Domain[%s], UPN[%s], Target[%s]\n", + username, result->domain->name, upn, target); + + ret = pam_gssapi_init_reply(pctx, result->domain->name, target, upn, + username); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to construct reply [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + +done: + DEBUG(SSSDBG_TRACE_FUNC, "Returning [%d]: %s\n", ret, sss_strerror(ret)); + + if (ret == EOK) { + sss_packet_set_error(pctx->creq->out, EOK); + } else { + sss_cmd_send_error(state->cli_ctx, ret); + } + + sss_cmd_done(state->cli_ctx, state); +} + +static void gssapi_log_status(int type, OM_uint32 status_code) +{ + OM_uint32 message_context = 0; + gss_buffer_desc buf; + OM_uint32 minor; + + do { + gss_display_status(&minor, status_code, type, GSS_C_NO_OID, + &message_context, &buf); + DEBUG(SSSDBG_OP_FAILURE, "GSSAPI: %.*s\n", (int)buf.length, + (char *)buf.value); + gss_release_buffer(&minor, &buf); + } while (message_context != 0); +} + +static void gssapi_log_error(OM_uint32 major, OM_uint32 minor) +{ + gssapi_log_status(GSS_C_GSS_CODE, major); + gssapi_log_status(GSS_C_MECH_CODE, minor); +} + +static char *gssapi_get_name(TALLOC_CTX *mem_ctx, gss_name_t gss_name) +{ + gss_buffer_desc buf; + OM_uint32 major; + OM_uint32 minor; + char *exported; + + major = gss_display_name(&minor, gss_name, &buf, NULL); + if (major != GSS_S_COMPLETE) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to export name\n"); + return NULL; + } + + exported = talloc_strndup(mem_ctx, buf.value, buf.length); + gss_release_buffer(&minor, &buf); + + if (exported == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory!\n"); + return NULL; + } + + return exported; +} + +#define AUTH_INDICATORS_TAG "auth-indicators" + +static char **gssapi_get_indicators(TALLOC_CTX *mem_ctx, gss_name_t gss_name) +{ + gss_buffer_set_t attrs = GSS_C_NO_BUFFER_SET; + int is_mechname; + OM_uint32 major; + OM_uint32 minor; + gss_buffer_desc value = GSS_C_EMPTY_BUFFER; + gss_buffer_desc display_value = GSS_C_EMPTY_BUFFER; + char *exported = NULL; + char **map = NULL; + int res; + + major = gss_inquire_name(&minor, gss_name, &is_mechname, NULL, &attrs); + if (major != GSS_S_COMPLETE) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to inquire name\n"); + return NULL; + } + + if (attrs == GSS_C_NO_BUFFER_SET) { + DEBUG(SSSDBG_TRACE_FUNC, "No krb5 attributes in the ticket\n"); + return NULL; + } + + exported = talloc_strdup(mem_ctx, ""); + if (exported == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to pre-allocate indicators\n"); + goto done; + } + + for (int i = 0; i < attrs->count; i++) { + int authenticated = 0; + int complete = 0; + int more = -1; + + /* skip anything but auth-indicators */ + if (strncmp(AUTH_INDICATORS_TAG, attrs->elements[i].value, + sizeof(AUTH_INDICATORS_TAG) - 1) != 0) + continue; + + /* retrieve all indicators */ + while (more != 0) { + value.value = NULL; + display_value.value = NULL; + + major = gss_get_name_attribute(&minor, gss_name, + &attrs->elements[i], + &authenticated, + &complete, &value, + &display_value, + &more); + if (major != GSS_S_COMPLETE) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to retrieve an attribute\n"); + goto done; + } + + if ((value.value != NULL) && authenticated) { + DEBUG(SSSDBG_TRACE_FUNC, + "attribute's [%.*s] value [%.*s] authenticated\n", + (int) attrs->elements[i].length, + (char*) attrs->elements[i].value, + (int) value.length, + (char*) value.value); + exported = talloc_asprintf_append(exported, "%.*s ", + (int) value.length, + (char*) value.value); + } + + if (exported == NULL) { + /* Since we allocate on mem_ctx, caller will free + * the previous version of 'exported' */ + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to collect an attribute value\n"); + goto done; + } + (void) gss_release_buffer(&minor, &value); + (void) gss_release_buffer(&minor, &display_value); + } + } + + if (exported[0] != '\0') { + /* trim a space after the final indicator + * to prevent split_on_separator() to fail */ + exported[strlen(exported) - 1] = '\0'; + } else { + /* empty list */ + goto done; + } + + res = split_on_separator(mem_ctx, exported, ' ', true, true, + &map, NULL); + if (res != 0) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Cannot parse list of indicators: [%s]\n", exported); + goto done; + } else { + DEBUG(SSSDBG_TRACE_FUNC, "authentication indicators: [%s]\n", + exported); + } + +done: + (void) gss_release_buffer(&minor, &value); + (void) gss_release_buffer(&minor, &display_value); + (void) gss_release_buffer_set(&minor, &attrs); + + talloc_free(exported); + return map; +} + + +struct gssapi_state { + struct cli_ctx *cli_ctx; + struct sss_domain_info *domain; + const char *username; + + char *authenticated_upn; + char **auth_indicators; + bool established; + gss_ctx_id_t ctx; +}; + +int gssapi_state_destructor(struct gssapi_state *state) +{ + OM_uint32 minor; + + gss_delete_sec_context(&minor, &state->ctx, NULL); + + return 0; +} + +static struct gssapi_state *gssapi_get_state(struct cli_ctx *cli_ctx, + const char *username, + struct sss_domain_info *domain) +{ + struct gssapi_state *state; + + state = talloc_get_type(cli_ctx->state_ctx, struct gssapi_state); + if (state != NULL) { + return state; + } + + state = talloc_zero(cli_ctx, struct gssapi_state); + if (state == NULL) { + return NULL; + } + + state->username = talloc_strdup(state, username); + if (state == NULL) { + talloc_free(state); + return NULL; + } + + state->domain = domain; + state->cli_ctx = cli_ctx; + state->ctx = GSS_C_NO_CONTEXT; + talloc_set_destructor(state, gssapi_state_destructor); + + cli_ctx->state_ctx = state; + + return state; +} + +static errno_t gssapi_get_creds(const char *keytab, + const char *target, + gss_cred_id_t *_creds) +{ + gss_key_value_set_desc cstore = {0, NULL}; + gss_key_value_element_desc el; + gss_buffer_desc name_buf; + gss_name_t name = GSS_C_NO_NAME; + OM_uint32 major; + OM_uint32 minor; + errno_t ret; + + if (keytab != NULL) { + el.key = "keytab"; + el.value = keytab; + cstore.count = 1; + cstore.elements = ⪙ + } + + if (target != NULL) { + name_buf.value = discard_const(target); + name_buf.length = strlen(target); + + major = gss_import_name(&minor, &name_buf, GSS_C_NT_HOSTBASED_SERVICE, + &name); + if (GSS_ERROR(major)) { + DEBUG(SSSDBG_OP_FAILURE, "Could not import name [%s] " + "[maj:0x%x, min:0x%x]\n", target, major, minor); + + gssapi_log_error(major, minor); + + ret = EIO; + goto done; + } + } + + major = gss_acquire_cred_from(&minor, name, GSS_C_INDEFINITE, + GSS_C_NO_OID_SET, GSS_C_ACCEPT, &cstore, + _creds, NULL, NULL); + if (GSS_ERROR(major)) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to read credentials from [%s] " + "[maj:0x%x, min:0x%x]\n", keytab ? keytab : "default", + major, minor); + + gssapi_log_error(major, minor); + + ret = EIO; + goto done; + } + + ret = EOK; + +done: + gss_release_name(&minor, &name); + + return ret; +} + +static errno_t +gssapi_handshake(struct gssapi_state *state, + struct cli_protocol *pctx, + const char *keytab, + const char *target, + uint8_t *gss_data, + size_t gss_data_len) +{ + OM_uint32 flags = GSS_C_MUTUAL_FLAG; + gss_buffer_desc output = GSS_C_EMPTY_BUFFER; + gss_buffer_desc input; + gss_name_t client_name; + gss_cred_id_t creds; + OM_uint32 ret_flags; + gss_OID mech_type; + OM_uint32 major; + OM_uint32 minor; + errno_t ret; + + input.value = gss_data; + input.length = gss_data_len; + + ret = gssapi_get_creds(keytab, target, &creds); + if (ret != EOK) { + return ret; + } + + major = gss_accept_sec_context(&minor, &state->ctx, creds, + &input, NULL, &client_name, &mech_type, + &output, &ret_flags, NULL, NULL); + if (major == GSS_S_CONTINUE_NEEDED || output.length > 0) { + ret = sss_packet_set_body(pctx->creq->out, output.value, output.length); + if (ret != EOK) { + goto done; + } + } + + if (GSS_ERROR(major)) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to establish GSS context " + "[maj:0x%x, min:0x%x]\n", major, minor); + + gssapi_log_error(major, minor); + ret = EIO; + goto done; + } + + if (major == GSS_S_CONTINUE_NEEDED) { + ret = EOK; + goto done; + } else if (major != GSS_S_COMPLETE) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to establish GSS context, unexpected " + "value: 0x%x\n", major); + ret = EIO; + goto done; + } + + if ((ret_flags & flags) != flags) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Negotiated context does not support requested flags\n"); + state->established = false; + ret = EIO; + goto done; + } + + state->authenticated_upn = gssapi_get_name(state, client_name); + if (state->authenticated_upn == NULL) { + state->established = false; + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Security context established with [%s]\n", + state->authenticated_upn); + + state->auth_indicators = gssapi_get_indicators(state, client_name); + + state->established = true; + ret = EOK; + +done: + gss_release_cred(&minor, &creds); + gss_release_buffer(&minor, &output); + + return ret; +} + +static errno_t pam_cmd_gssapi_sec_ctx_parse(struct cli_protocol *pctx, + const char **_pam_service, + const char **_username, + const char **_domain, + uint8_t **_gss_data, + size_t *_gss_data_len) +{ + size_t body_len; + uint8_t *body; + size_t pctr; + errno_t ret; + + sss_packet_get_body(pctx->creq->in, &body, &body_len); + if (body == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid input\n"); + return EINVAL; + } + + pctr = 0; + ret = read_str(body_len, body, &pctr, _pam_service); + if (ret != EOK) { + return ret; + } + + ret = read_str(body_len, body, &pctr, _username); + if (ret != EOK) { + return ret; + } + + ret = read_str(body_len, body, &pctr, _domain); + if (ret != EOK) { + return ret; + } + + *_gss_data = (pctr == body_len) ? NULL : body + pctr; + *_gss_data_len = body_len - pctr; + + return EOK; +} + +static void pam_cmd_gssapi_sec_ctx_done(struct tevent_req *req); + +int +pam_cmd_gssapi_sec_ctx(struct cli_ctx *cli_ctx) +{ + struct sss_domain_info *domain; + struct gssapi_state *state; + struct cli_protocol *pctx; + struct pam_ctx *pam_ctx; + struct tevent_req *req; + const char *pam_service; + const char *domain_name; + const char *username; + char *target; + char **indicators_map = NULL; + size_t gss_data_len; + uint8_t *gss_data; + errno_t ret; + + pctx = talloc_get_type(cli_ctx->protocol_ctx, struct cli_protocol); + pam_ctx = talloc_get_type(cli_ctx->rctx->pvt_ctx, struct pam_ctx); + + ret = sss_packet_new(pctx->creq, 0, sss_packet_get_cmd(pctx->creq->in), + &pctx->creq->out); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create a new packet [%d]; %s\n", + ret, sss_strerror(ret)); + return ret; + } + + ret = pam_cmd_gssapi_sec_ctx_parse(pctx, &pam_service, &username, + &domain_name, &gss_data, &gss_data_len); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to parse input data [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + domain = find_domain_by_name(cli_ctx->rctx->domains, domain_name, false); + if (domain == NULL) { + ret = EINVAL; + goto done; + } + + if (!pam_gssapi_allowed(pam_ctx, domain, pam_service)) { + ret = ENOTSUP; + goto done; + } + + target = pam_gssapi_target(cli_ctx, domain); + if (target == NULL) { + ret = ENOMEM; + goto done; + } + + state = gssapi_get_state(cli_ctx, username, domain); + if (state == NULL) { + ret = ENOMEM; + goto done; + } + + if (strcmp(username, state->username) != 0 || state->domain != domain) { + /* This should not happen, but be paranoid. */ + DEBUG(SSSDBG_CRIT_FAILURE, "Different input user then who initiated " + "the request!\n"); + ret = EPERM; + goto done; + } + + if (state->established) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Security context is already established\n"); + ret = EPERM; + goto done; + } + + ret = gssapi_handshake(state, pctx, domain->krb5_keytab, target, gss_data, + gss_data_len); + if (ret != EOK || !state->established) { + goto done; + } + + /* Use map for auth-indicators from the domain, if defined and + * fallback to the [pam] section otherwise */ + indicators_map = domain->gssapi_indicators_map ? + domain->gssapi_indicators_map : + (pam_ctx->gssapi_indicators_map ? + pam_ctx->gssapi_indicators_map : NULL); + if (indicators_map != NULL) { + ret = pam_gssapi_check_indicators(state, + pam_service, + indicators_map, + state->auth_indicators); + DEBUG(SSSDBG_TRACE_FUNC, + "Check if acquired service ticket has req. indicators: %d\n", + ret); + if ((ret == EPERM) || (ret == ENOMEM) || (ret == EINVAL)) { + /* skip further checks if denied or no memory, + * ENOENT means the check is not applicable */ + goto done; + } + } + + if (!pam_gssapi_should_check_upn(pam_ctx, domain)) { + /* We are done. */ + goto done; + } + + /* We have established the security context. Now check the the principal + * used for authorization can be associated with the user. We have + * already done initgroups before so we could just search the sysdb + * directly, but use cache req to avoid looking up a possible expired + * object if the handshake took longer. */ + + DEBUG(SSSDBG_TRACE_FUNC, "Checking that target user matches UPN\n"); + + req = cache_req_user_by_upn_send(cli_ctx, cli_ctx->ev, cli_ctx->rctx, + cli_ctx->rctx->ncache, 0, + CACHE_REQ_POSIX_DOM, + domain->name, state->authenticated_upn); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(req, pam_cmd_gssapi_sec_ctx_done, state); + + return EOK; + +done: + DEBUG(SSSDBG_TRACE_FUNC, "Returning [%d]: %s\n", ret, sss_strerror(ret)); + + if (ret == EOK) { + sss_packet_set_error(pctx->creq->out, EOK); + } else { + sss_cmd_send_error(cli_ctx, ret); + } + + sss_cmd_done(cli_ctx, NULL); + return EOK; +} + +static void pam_cmd_gssapi_sec_ctx_done(struct tevent_req *req) +{ + struct gssapi_state *state; + struct cache_req_result *result; + struct cli_protocol *pctx; + const char *name; + errno_t ret; + + state = tevent_req_callback_data(req, struct gssapi_state); + pctx = talloc_get_type(state->cli_ctx->protocol_ctx, struct cli_protocol); + + ret = cache_req_user_by_upn_recv(state, req, &result); + talloc_zfree(req); + if (ret == ENOENT || ret == ERR_DOMAIN_NOT_FOUND) { + /* We have no match. Return failure. */ + DEBUG(SSSDBG_TRACE_FUNC, "User with UPN [%s] was not found. " + "Authentication failed.\n", state->authenticated_upn); + ret = EACCES; + goto done; + } else if (ret != EOK) { + /* Generic error. Return failure. */ + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to lookup user by UPN [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + /* Check that username match. */ + name = ldb_msg_find_attr_as_string(result->msgs[0], SYSDB_NAME, NULL); + if (name == NULL || strcmp(name, state->username) != 0) { + DEBUG(SSSDBG_TRACE_FUNC, "UPN [%s] does not match target user [%s]. " + "Authentication failed.\n", state->authenticated_upn, + state->username); + ret = EACCES; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "User [%s] match UPN [%s]. Authentication was " + "successful.\n", state->username, state->authenticated_upn); + + ret = EOK; + +done: + DEBUG(SSSDBG_TRACE_FUNC, "Returning [%d]: %s\n", ret, sss_strerror(ret)); + + if (ret == EOK) { + sss_packet_set_error(pctx->creq->out, EOK); + } else { + sss_cmd_send_error(state->cli_ctx, ret); + } + + sss_cmd_done(state->cli_ctx, state); +} diff --git a/src/responder/pam/pamsrv_p11.c b/src/responder/pam/pamsrv_p11.c new file mode 100644 index 0000000..2f973d8 --- /dev/null +++ b/src/responder/pam/pamsrv_p11.c @@ -0,0 +1,1312 @@ +/* + SSSD + + PAM Responder - certificate related requests + + Copyright (C) Sumit Bose <sbose@redhat.com> 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <time.h> + +#include "util/util.h" +#include "providers/data_provider.h" +#include "util/child_common.h" +#include "util/strtonum.h" +#include "responder/pam/pamsrv.h" +#include "responder/pam/pam_helpers.h" +#include "lib/certmap/sss_certmap.h" +#include "util/crypto/sss_crypto.h" +#include "util/sss_chain_id.h" +#include "db/sysdb.h" + + +struct cert_auth_info { + char *cert; + char *token_name; + char *module_name; + char *key_id; + char *label; + struct ldb_result *cert_user_objs; + struct cert_auth_info *prev; + struct cert_auth_info *next; +}; + +const char *sss_cai_get_cert(struct cert_auth_info *i) +{ + return i != NULL ? i->cert : NULL; +} + +const char *sss_cai_get_token_name(struct cert_auth_info *i) +{ + return i != NULL ? i->token_name : NULL; +} + +const char *sss_cai_get_module_name(struct cert_auth_info *i) +{ + return i != NULL ? i->module_name : NULL; +} + +const char *sss_cai_get_key_id(struct cert_auth_info *i) +{ + return i != NULL ? i->key_id : NULL; +} + +const char *sss_cai_get_label(struct cert_auth_info *i) +{ + return i != NULL ? i->label : NULL; +} + +struct cert_auth_info *sss_cai_get_next(struct cert_auth_info *i) +{ + return i != NULL ? i->next : NULL; +} + +struct ldb_result *sss_cai_get_cert_user_objs(struct cert_auth_info *i) +{ + return i != NULL ? i->cert_user_objs : NULL; +} + +void sss_cai_set_cert_user_objs(struct cert_auth_info *i, + struct ldb_result *cert_user_objs) +{ + if (i->cert_user_objs != NULL) { + talloc_free(i->cert_user_objs); + } + i->cert_user_objs = talloc_steal(i, cert_user_objs); +} + +void sss_cai_check_users(struct cert_auth_info **list, size_t *_cert_count, + size_t *_cert_user_count) +{ + struct cert_auth_info *c; + struct cert_auth_info *tmp; + size_t cert_count = 0; + size_t cert_user_count = 0; + struct ldb_result *user_objs; + + DLIST_FOR_EACH_SAFE(c, tmp, *list) { + user_objs = sss_cai_get_cert_user_objs(c); + if (user_objs != NULL) { + cert_count++; + cert_user_count += user_objs->count; + } else { + DLIST_REMOVE(*list, c); + } + } + + if (_cert_count != NULL) { + *_cert_count = cert_count; + } + + if (_cert_user_count != NULL) { + *_cert_user_count = cert_user_count; + } + + return; +} + +struct priv_sss_debug { + int level; +}; + +static void ext_debug(void *private, const char *file, long line, + const char *function, const char *format, ...) +{ + va_list ap; + struct priv_sss_debug *data = private; + int level = SSSDBG_OP_FAILURE; + + if (data != NULL) { + level = data->level; + } + + va_start(ap, format); + sss_vdebug_fn(file, line, function, level, APPEND_LINE_FEED, + format, ap); + va_end(ap); +} + +errno_t p11_refresh_certmap_ctx(struct pam_ctx *pctx, + struct sss_domain_info *domains) +{ + int ret; + struct sss_certmap_ctx *sss_certmap_ctx = NULL; + size_t c; + struct sss_domain_info *dom; + bool certmap_found = false; + struct certmap_info **certmap_list; + + ret = sss_certmap_init(pctx, ext_debug, NULL, &sss_certmap_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_certmap_init failed.\n"); + goto done; + } + + DLIST_FOR_EACH(dom, domains) { + certmap_list = dom->certmaps; + if (certmap_list != NULL && *certmap_list != NULL) { + certmap_found = true; + break; + } + } + + if (!certmap_found) { + /* Try to add default matching rule */ + ret = sss_certmap_add_rule(sss_certmap_ctx, SSS_CERTMAP_MIN_PRIO, + CERT_AUTH_DEFAULT_MATCHING_RULE, NULL, NULL); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to add default matching rule.\n"); + } + + goto done; + } + + DLIST_FOR_EACH(dom, domains) { + certmap_list = dom->certmaps; + if (certmap_list == NULL || *certmap_list == NULL) { + continue; + } + + for (c = 0; certmap_list[c] != NULL; c++) { + DEBUG(SSSDBG_TRACE_ALL, + "Trying to add rule [%s][%d][%s][%s].\n", + certmap_list[c]->name, certmap_list[c]->priority, + certmap_list[c]->match_rule, certmap_list[c]->map_rule); + + ret = sss_certmap_add_rule(sss_certmap_ctx, + certmap_list[c]->priority, + certmap_list[c]->match_rule, + certmap_list[c]->map_rule, + certmap_list[c]->domains); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sss_certmap_add_rule failed for rule [%s] " + "with error [%d][%s], skipping. " + "Please check for typos and if rule syntax is supported.\n", + certmap_list[c]->name, ret, sss_strerror(ret)); + continue; + } + } + } + + ret = EOK; + +done: + if (ret == EOK) { + sss_certmap_free_ctx(pctx->sss_certmap_ctx); + pctx->sss_certmap_ctx = sss_certmap_ctx; + } else { + sss_certmap_free_ctx(sss_certmap_ctx); + } + + return ret; +} + +errno_t p11_child_init(struct pam_ctx *pctx) +{ + int ret; + struct certmap_info **certmaps; + bool user_name_hint; + struct sss_domain_info *dom; + + DLIST_FOR_EACH(dom, pctx->rctx->domains) { + ret = sysdb_get_certmap(dom, dom->sysdb, &certmaps, &user_name_hint); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_get_certmap failed.\n"); + return ret; + } + + dom->user_name_hint = user_name_hint; + talloc_free(dom->certmaps); + dom->certmaps = certmaps; + } + + ret = p11_refresh_certmap_ctx(pctx, pctx->rctx->domains); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "p11_refresh_certmap_ctx failed.\n"); + return ret; + } + + return EOK; +} + +static inline bool +service_in_list(char **list, size_t nlist, const char *str) +{ + size_t i; + + for (i = 0; i < nlist; i++) { + if (strcasecmp(list[i], str) == 0) { + break; + } + } + + return (i < nlist) ? true : false; +} + +static errno_t get_sc_services(TALLOC_CTX *mem_ctx, struct pam_ctx *pctx, + char ***_sc_list) +{ + TALLOC_CTX *tmp_ctx; + errno_t ret; + char *conf_str; + char **conf_list; + int conf_list_size; + char **add_list; + char **remove_list; + int ai = 0; + int ri = 0; + int j = 0; + char **sc_list; + int expected_sc_list_size; + + const char *default_sc_services[] = { + "login", "su", "su-l", "gdm-smartcard", "gdm-password", "kdm", "sudo", + "sudo-i", "gnome-screensaver", "polkit-1", NULL, + }; + const int default_sc_services_size = + sizeof(default_sc_services) / sizeof(default_sc_services[0]); + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = confdb_get_string(pctx->rctx->cdb, tmp_ctx, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_P11_ALLOWED_SERVICES, NULL, + &conf_str); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "confdb_get_string failed %d [%s]\n", ret, sss_strerror(ret)); + goto done; + } + + if (conf_str != NULL) { + ret = split_on_separator(tmp_ctx, conf_str, ',', true, true, + &conf_list, &conf_list_size); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot parse list of service names '%s': %d [%s]\n", + conf_str, ret, sss_strerror(ret)); + goto done; + } + } else { + conf_list = talloc_zero_array(tmp_ctx, char *, 1); + conf_list_size = 0; + } + + add_list = talloc_zero_array(tmp_ctx, char *, conf_list_size + 1); + remove_list = talloc_zero_array(tmp_ctx, char *, conf_list_size + 1); + + if (add_list == NULL || remove_list == NULL) { + ret = ENOMEM; + goto done; + } + + for (int i = 0; conf_list[i] != NULL; ++i) { + switch (conf_list[i][0]) { + case '+': + add_list[ai] = conf_list[i] + 1; + ++ai; + break; + case '-': + remove_list[ri] = conf_list[i] + 1; + ++ri; + break; + default: + DEBUG(SSSDBG_OP_FAILURE, + "The option "CONFDB_PAM_P11_ALLOWED_SERVICES" must start" + "with either '+' (for adding service) or '-' (for " + "removing service) got '%s'\n", conf_list[i]); + ret = EINVAL; + goto done; + } + } + + expected_sc_list_size = default_sc_services_size + ai + 1; + + sc_list = talloc_zero_array(tmp_ctx, char *, expected_sc_list_size); + if (sc_list == NULL) { + ret = ENOMEM; + goto done; + } + + for (int i = 0; add_list[i] != NULL; ++i) { + if (service_in_list(remove_list, ri, add_list[i])) { + continue; + } + + sc_list[j] = talloc_strdup(sc_list, add_list[i]); + if (sc_list[j] == NULL) { + ret = ENOMEM; + goto done; + } + ++j; + } + + for (int i = 0; default_sc_services[i] != NULL; ++i) { + if (service_in_list(remove_list, ri, default_sc_services[i])) { + continue; + } + + sc_list[j] = talloc_strdup(sc_list, default_sc_services[i]); + if (sc_list[j] == NULL) { + ret = ENOMEM; + goto done; + } + ++j; + } + + if (_sc_list != NULL) { + *_sc_list = talloc_steal(mem_ctx, sc_list); + } + +done: + talloc_zfree(tmp_ctx); + + return ret; +} + +bool may_do_cert_auth(struct pam_ctx *pctx, struct pam_data *pd) +{ + size_t c; + errno_t ret; + + if (!pctx->cert_auth) { + return false; + } + + if (pd->cmd != SSS_PAM_PREAUTH && pd->cmd != SSS_PAM_AUTHENTICATE) { + return false; + } + + if (pd->cmd == SSS_PAM_AUTHENTICATE + && sss_authtok_get_type(pd->authtok) != SSS_AUTHTOK_TYPE_SC_PIN + && sss_authtok_get_type(pd->authtok) != SSS_AUTHTOK_TYPE_SC_KEYPAD) { + return false; + } + + if (pd->service == NULL || *pd->service == '\0') { + return false; + } + + /* Initialize smartcard allowed services just once */ + if (pctx->smartcard_services == NULL) { + ret = get_sc_services(pctx, pctx, &pctx->smartcard_services); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to get p11 allowed services %d[%s]", + ret, sss_strerror(ret)); + sss_log(SSS_LOG_ERR, + "Failed to evaluate pam_p11_allowed_services option, " + "please check for typos in the SSSD configuration"); + return false; + } + } + + for (c = 0; pctx->smartcard_services[c] != NULL; c++) { + if (strcmp(pd->service, pctx->smartcard_services[c]) == 0) { + break; + } + } + if (pctx->smartcard_services[c] == NULL) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Smartcard authentication for service [%s] not supported.\n", + pd->service); + return false; + } + + return true; +} + +static errno_t get_p11_child_write_buffer(TALLOC_CTX *mem_ctx, + struct pam_data *pd, + uint8_t **_buf, size_t *_len) +{ + int ret; + uint8_t *buf; + size_t len; + const char *pin = NULL; + + if (pd == NULL || pd->authtok == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing authtok.\n"); + return EINVAL; + } + + switch (sss_authtok_get_type(pd->authtok)) { + case SSS_AUTHTOK_TYPE_SC_PIN: + ret = sss_authtok_get_sc_pin(pd->authtok, &pin, &len); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_authtok_get_sc_pin failed.\n"); + return ret; + } + if (pin == NULL || len == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing PIN.\n"); + return EINVAL; + } + + buf = talloc_size(mem_ctx, len); + if (buf == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_size failed.\n"); + return ENOMEM; + } + + safealign_memcpy(buf, pin, len, NULL); + + break; + case SSS_AUTHTOK_TYPE_SC_KEYPAD: + /* Nothing to send */ + len = 0; + buf = NULL; + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported authtok type [%d].\n", + sss_authtok_get_type(pd->authtok)); + return EINVAL; + } + + *_len = len; + *_buf = buf; + + return EOK; +} + +static errno_t parse_p11_child_response(TALLOC_CTX *mem_ctx, uint8_t *buf, + ssize_t buf_len, + struct sss_certmap_ctx *sss_certmap_ctx, + struct cert_auth_info **_cert_list) +{ + int ret; + TALLOC_CTX *tmp_ctx = NULL; + uint8_t *p; + uint8_t *pn; + struct cert_auth_info *cert_list = NULL; + struct cert_auth_info *cert_auth_info; + unsigned char *der = NULL; + size_t der_size; + + if (buf_len <= 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Error occurred while reading data from p11_child (len = %ld)\n", + buf_len); + return EIO; + } + + p = memchr(buf, '\n', buf_len); + if ((p == NULL) || (p == buf)) { + DEBUG(SSSDBG_OP_FAILURE, "Missing status in p11_child response.\n"); + return EINVAL; + } + errno = 0; + ret = strtol((char *)buf, (char **)&pn, 10); + if ((errno != 0) || (pn == buf)) { + DEBUG(SSSDBG_OP_FAILURE, "Invalid status in p11_child response.\n"); + return EINVAL; + } + + if (ret != 0) { + if (ret == ERR_P11_PIN_LOCKED) { + DEBUG(SSSDBG_OP_FAILURE, "PIN locked\n"); + return ret; + } else { + *_cert_list = NULL; + return EOK; /* other errors are treated as "no cert found" */ + } + } + + if ((p - buf + 1) == buf_len) { + DEBUG(SSSDBG_TRACE_LIBS, "No certificate found.\n"); + *_cert_list = NULL; + return EOK; + } + + p++; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + do { + cert_auth_info = talloc_zero(tmp_ctx, struct cert_auth_info); + if (cert_auth_info == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero failed.\n"); + ret = ENOMEM; + goto done; + } + + pn = memchr(p, '\n', buf_len - (p - buf)); + if (pn == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Missing new-line in p11_child response.\n"); + ret = EINVAL; + goto done; + } + if (pn == p) { + DEBUG(SSSDBG_OP_FAILURE, + "Missing token name in p11_child response.\n"); + ret = EINVAL; + goto done; + } + + cert_auth_info->token_name = talloc_strndup(cert_auth_info, (char *)p, + (pn - p)); + if (cert_auth_info->token_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strndup failed.\n"); + ret = ENOMEM; + goto done; + } + DEBUG(SSSDBG_TRACE_ALL, "Found token name [%s].\n", + cert_auth_info->token_name); + + p = ++pn; + pn = memchr(p, '\n', buf_len - (p - buf)); + if (pn == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Missing new-line in p11_child response.\n"); + ret = EINVAL; + goto done; + } + + if (pn == p) { + DEBUG(SSSDBG_OP_FAILURE, + "Missing module name in p11_child response.\n"); + ret = EINVAL; + goto done; + } + + cert_auth_info->module_name = talloc_strndup(cert_auth_info, (char *)p, + (pn - p)); + if (cert_auth_info->module_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strndup failed.\n"); + ret = ENOMEM; + goto done; + } + DEBUG(SSSDBG_TRACE_ALL, "Found module name [%s].\n", + cert_auth_info->module_name); + + p = ++pn; + pn = memchr(p, '\n', buf_len - (p - buf)); + if (pn == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Missing new-line in p11_child response.\n"); + ret = EINVAL; + goto done; + } + + if (pn == p) { + DEBUG(SSSDBG_OP_FAILURE, + "Missing key id in p11_child response.\n"); + ret = EINVAL; + goto done; + } + + cert_auth_info->key_id = talloc_strndup(cert_auth_info, (char *)p, + (pn - p)); + if (cert_auth_info->key_id == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strndup failed.\n"); + ret = ENOMEM; + goto done; + } + DEBUG(SSSDBG_TRACE_ALL, "Found key id [%s].\n", cert_auth_info->key_id); + + p = ++pn; + pn = memchr(p, '\n', buf_len - (p - buf)); + if (pn == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Missing new-line in p11_child response.\n"); + ret = EINVAL; + goto done; + } + + if (pn == p) { + DEBUG(SSSDBG_OP_FAILURE, + "Missing label in p11_child response.\n"); + ret = EINVAL; + goto done; + } + + cert_auth_info->label = talloc_strndup(cert_auth_info, (char *) p, + (pn - p)); + if (cert_auth_info->label == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strndup failed.\n"); + ret = ENOMEM; + goto done; + } + DEBUG(SSSDBG_TRACE_ALL, "Found label [%s].\n", cert_auth_info->label); + + p = ++pn; + pn = memchr(p, '\n', buf_len - (p - buf)); + if (pn == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Missing new-line in p11_child response.\n"); + ret = EINVAL; + goto done; + } + + if (pn == p) { + DEBUG(SSSDBG_OP_FAILURE, "Missing cert in p11_child response.\n"); + ret = EINVAL; + goto done; + } + + cert_auth_info->cert = talloc_strndup(cert_auth_info, (char *)p, + (pn - p)); + if (cert_auth_info->cert == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strndup failed.\n"); + ret = ENOMEM; + goto done; + } + DEBUG(SSSDBG_TRACE_ALL, "Found cert [%s].\n", cert_auth_info->cert); + + der = sss_base64_decode(tmp_ctx, cert_auth_info->cert, &der_size); + if (der == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sss_base64_decode failed.\n"); + ret = EIO; + goto done; + } + + ret = sss_certmap_match_cert(sss_certmap_ctx, der, der_size); + if (ret == 0) { + DLIST_ADD(cert_list, cert_auth_info); + } else { + DEBUG(SSSDBG_TRACE_LIBS, + "Cert [%s] does not match matching rules and is ignored.\n", + cert_auth_info->cert); + talloc_free(cert_auth_info); + } + + p = ++pn; + } while ((pn - buf) < buf_len); + + ret = EOK; + +done: + if (ret == EOK) { + DLIST_FOR_EACH(cert_auth_info, cert_list) { + talloc_steal(mem_ctx, cert_auth_info); + } + + *_cert_list = cert_list; + } + + talloc_free(tmp_ctx); + + return ret; +} + +struct pam_check_cert_state { + int child_status; + struct sss_child_ctx_old *child_ctx; + struct tevent_timer *timeout_handler; + struct tevent_context *ev; + struct sss_certmap_ctx *sss_certmap_ctx; + + struct child_io_fds *io; + + struct cert_auth_info *cert_list; + struct pam_data *pam_data; +}; + +static void p11_child_write_done(struct tevent_req *subreq); +static void p11_child_done(struct tevent_req *subreq); +static void p11_child_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, void *pvt); + +struct tevent_req *pam_check_cert_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char *ca_db, + time_t timeout, + const char *verify_opts, + struct sss_certmap_ctx *sss_certmap_ctx, + const char *uri, + struct pam_data *pd) +{ + errno_t ret; + struct tevent_req *req; + struct tevent_req *subreq; + struct pam_check_cert_state *state; + pid_t child_pid; + struct timeval tv; + int pipefd_to_child[2] = PIPE_INIT; + int pipefd_from_child[2] = PIPE_INIT; + const char *extra_args[20] = { NULL }; + uint8_t *write_buf = NULL; + size_t write_buf_len = 0; + size_t arg_c; + uint64_t chain_id; + const char *module_name = NULL; + const char *token_name = NULL; + const char *key_id = NULL; + const char *label = NULL; + + req = tevent_req_create(mem_ctx, &state, struct pam_check_cert_state); + if (req == NULL) { + return NULL; + } + + if (ca_db == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing CA DB path.\n"); + ret = EINVAL; + goto done; + } + + if (sss_certmap_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing certificate matching context.\n"); + ret = EINVAL; + goto done; + } + + state->pam_data = pd; + + /* extra_args are added in revers order */ + arg_c = 0; + + chain_id = sss_chain_id_get(); + + extra_args[arg_c++] = talloc_asprintf(mem_ctx, "%lu", chain_id); + extra_args[arg_c++] = "--chain-id"; + + if (uri != NULL) { + DEBUG(SSSDBG_TRACE_ALL, "Adding PKCS#11 URI [%s].\n", uri); + extra_args[arg_c++] = uri; + extra_args[arg_c++] = "--uri"; + } + + if ((pd->cli_flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH) && pd->priv == 1) { + extra_args[arg_c++] = "--wait_for_card"; + } + extra_args[arg_c++] = ca_db; + extra_args[arg_c++] = "--ca_db"; + if (verify_opts != NULL) { + extra_args[arg_c++] = verify_opts; + extra_args[arg_c++] = "--verify"; + } + + if (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_SC_PIN + || sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_SC_KEYPAD) { + ret = sss_authtok_get_sc(pd->authtok, NULL, NULL, &token_name, NULL, + &module_name, NULL, &key_id, NULL, + &label, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_authtok_get_sc failed.\n"); + goto done; + } + + if (module_name != NULL && *module_name != '\0') { + extra_args[arg_c++] = module_name; + extra_args[arg_c++] = "--module_name"; + } + if (token_name != NULL && *token_name != '\0') { + extra_args[arg_c++] = token_name; + extra_args[arg_c++] = "--token_name"; + } + if (key_id != NULL && *key_id != '\0') { + extra_args[arg_c++] = key_id; + extra_args[arg_c++] = "--key_id"; + } + if (label != NULL && *label != '\0') { + extra_args[arg_c++] = label; + extra_args[arg_c++] = "--label"; + } + } + + if (pd->cmd == SSS_PAM_AUTHENTICATE) { + extra_args[arg_c++] = "--auth"; + switch (sss_authtok_get_type(pd->authtok)) { + case SSS_AUTHTOK_TYPE_SC_PIN: + extra_args[arg_c++] = "--pin"; + break; + case SSS_AUTHTOK_TYPE_SC_KEYPAD: + extra_args[arg_c++] = "--keypad"; + break; + default: + DEBUG(SSSDBG_OP_FAILURE, "Unsupported authtok type.\n"); + ret = EINVAL; + goto done; + } + + } else if (pd->cmd == SSS_PAM_PREAUTH) { + extra_args[arg_c++] = "--pre"; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected PAM command [%d].\n", pd->cmd); + ret = EINVAL; + goto done; + } + + state->ev = ev; + state->sss_certmap_ctx = sss_certmap_ctx; + state->child_status = EFAULT; + state->io = talloc(state, struct child_io_fds); + if (state->io == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc failed.\n"); + ret = ENOMEM; + goto done; + } + state->io->write_to_child_fd = -1; + state->io->read_from_child_fd = -1; + talloc_set_destructor((void *) state->io, child_io_destructor); + + ret = pipe(pipefd_from_child); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "pipe failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + ret = pipe(pipefd_to_child); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "pipe failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + + child_pid = fork(); + if (child_pid == 0) { /* child */ + exec_child_ex(state, pipefd_to_child, pipefd_from_child, + P11_CHILD_PATH, P11_CHILD_LOG_FILE, extra_args, false, + STDIN_FILENO, STDOUT_FILENO); + + /* We should never get here */ + DEBUG(SSSDBG_CRIT_FAILURE, "BUG: Could not exec p11 child\n"); + } else if (child_pid > 0) { /* parent */ + + state->io->read_from_child_fd = pipefd_from_child[0]; + PIPE_FD_CLOSE(pipefd_from_child[1]); + sss_fd_nonblocking(state->io->read_from_child_fd); + + state->io->write_to_child_fd = pipefd_to_child[1]; + PIPE_FD_CLOSE(pipefd_to_child[0]); + sss_fd_nonblocking(state->io->write_to_child_fd); + + /* Set up SIGCHLD handler */ + ret = child_handler_setup(ev, child_pid, NULL, NULL, &state->child_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not set up child handlers [%d]: %s\n", + ret, sss_strerror(ret)); + ret = ERR_P11_CHILD; + goto done; + } + + /* Set up timeout handler */ + tv = sss_tevent_timeval_current_ofs_time_t(timeout); + state->timeout_handler = tevent_add_timer(ev, req, tv, + p11_child_timeout, req); + if(state->timeout_handler == NULL) { + ret = ERR_P11_CHILD; + goto done; + } + + if (pd->cmd == SSS_PAM_AUTHENTICATE) { + ret = get_p11_child_write_buffer(state, pd, &write_buf, + &write_buf_len); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "get_p11_child_write_buffer failed.\n"); + goto done; + } + } + + if (write_buf_len != 0) { + subreq = write_pipe_send(state, ev, write_buf, write_buf_len, + state->io->write_to_child_fd); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "write_pipe_send failed.\n"); + ret = ERR_P11_CHILD; + goto done; + } + tevent_req_set_callback(subreq, p11_child_write_done, req); + } else { + subreq = read_pipe_send(state, ev, state->io->read_from_child_fd); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "read_pipe_send failed.\n"); + ret = ERR_P11_CHILD; + goto done; + } + tevent_req_set_callback(subreq, p11_child_done, req); + } + + /* Now either wait for the timeout to fire or the child + * to finish + */ + } else { /* error */ + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "fork failed [%d][%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = EOK; + +done: + if (ret != EOK) { + PIPE_CLOSE(pipefd_from_child); + PIPE_CLOSE(pipefd_to_child); + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + return req; +} + +static void p11_child_write_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct pam_check_cert_state *state = tevent_req_data(req, + struct pam_check_cert_state); + int ret; + + ret = write_pipe_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + PIPE_FD_CLOSE(state->io->write_to_child_fd); + + subreq = read_pipe_send(state, state->ev, state->io->read_from_child_fd); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, p11_child_done, req); +} + +static void p11_child_done(struct tevent_req *subreq) +{ + uint8_t *buf; + ssize_t buf_len; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct pam_check_cert_state *state = tevent_req_data(req, + struct pam_check_cert_state); + uint32_t user_info_type; + int ret; + + talloc_zfree(state->timeout_handler); + + ret = read_pipe_recv(subreq, state, &buf, &buf_len); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + PIPE_FD_CLOSE(state->io->read_from_child_fd); + + ret = parse_p11_child_response(state, buf, buf_len, state->sss_certmap_ctx, + &state->cert_list); + if (ret != EOK) { + if (ret == ERR_P11_PIN_LOCKED) { + DEBUG(SSSDBG_MINOR_FAILURE, "PIN locked\n"); + user_info_type = SSS_PAM_USER_INFO_PIN_LOCKED; + pam_add_response(state->pam_data, SSS_PAM_USER_INFO, + sizeof(uint32_t), (const uint8_t *) &user_info_type); + } else { + DEBUG(SSSDBG_OP_FAILURE, "parse_p11_child_response failed.\n"); + } + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +static void p11_child_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, void *pvt) +{ + struct tevent_req *req = talloc_get_type(pvt, struct tevent_req); + struct pam_check_cert_state *state = + tevent_req_data(req, struct pam_check_cert_state); + + DEBUG(SSSDBG_CRIT_FAILURE, + "Timeout reached for p11_child, " + "consider increasing p11_child_timeout.\n"); + child_handler_destroy(state->child_ctx); + state->child_ctx = NULL; + state->child_status = ETIMEDOUT; + tevent_req_error(req, ERR_P11_CHILD_TIMEOUT); +} + +errno_t pam_check_cert_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + struct cert_auth_info **cert_list) +{ + struct cert_auth_info *tmp_cert_auth_info; + struct pam_check_cert_state *state = + tevent_req_data(req, struct pam_check_cert_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (cert_list != NULL) { + DLIST_FOR_EACH(tmp_cert_auth_info, state->cert_list) { + talloc_steal(mem_ctx, tmp_cert_auth_info); + } + + *cert_list = state->cert_list; + } + + return EOK; +} + +static char *get_cert_prompt(TALLOC_CTX *mem_ctx, + struct cert_auth_info *cert_info) +{ + int ret; + struct sss_certmap_ctx *ctx = NULL; + unsigned char *der = NULL; + size_t der_size; + char *prompt = NULL; + char *filter = NULL; + char **domains = NULL; + + ret = sss_certmap_init(mem_ctx, NULL, NULL, &ctx); + if (ret != 0) { + DEBUG(SSSDBG_OP_FAILURE, "sss_certmap_init failed.\n"); + return NULL; + } + + ret = sss_certmap_add_rule(ctx, 10, "KRB5:<ISSUER>.*", + "LDAP:{subject_dn!nss}", NULL); + if (ret != 0) { + DEBUG(SSSDBG_OP_FAILURE, "sss_certmap_add_rule failed.\n"); + goto done; + } + + der = sss_base64_decode(mem_ctx, sss_cai_get_cert(cert_info), &der_size); + if (der == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sss_base64_decode failed.\n"); + goto done; + } + + ret = sss_certmap_expand_mapping_rule(ctx, der, der_size, + &filter, &domains); + if (ret != 0) { + DEBUG(SSSDBG_OP_FAILURE, "sss_certmap_expand_mapping_rule failed.\n"); + goto done; + } + + prompt = talloc_asprintf(mem_ctx, "%s\n%s", sss_cai_get_label(cert_info), + filter); + if (prompt == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + } + +done: + sss_certmap_free_filter_and_domains(filter, domains); + sss_certmap_free_ctx(ctx); + talloc_free(der); + + return prompt; +} + +static errno_t pack_cert_data(TALLOC_CTX *mem_ctx, const char *sysdb_username, + struct cert_auth_info *cert_info, + const char *nss_name, + uint8_t **_msg, size_t *_msg_len) +{ + uint8_t *msg = NULL; + size_t msg_len; + const char *token_name; + const char *module_name; + const char *key_id; + const char *label; + char *prompt; + size_t user_len; + size_t token_len; + size_t module_len; + size_t key_id_len; + size_t label_len; + size_t prompt_len; + size_t nss_name_len; + const char *username = ""; + const char *nss_username = ""; + + if (sysdb_username != NULL) { + username = sysdb_username; + } + + if (nss_name != NULL) { + nss_username = nss_name; + } + + prompt = get_cert_prompt(mem_ctx, cert_info); + if (prompt == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "get_cert_prompt failed.\n"); + return EIO; + } + + token_name = sss_cai_get_token_name(cert_info); + module_name = sss_cai_get_module_name(cert_info); + key_id = sss_cai_get_key_id(cert_info); + label = sss_cai_get_label(cert_info); + + user_len = strlen(username) + 1; + token_len = strlen(token_name) + 1; + module_len = strlen(module_name) + 1; + key_id_len = strlen(key_id) + 1; + label_len = strlen(label) + 1; + prompt_len = strlen(prompt) + 1; + nss_name_len = strlen(nss_username) +1; + + msg_len = user_len + token_len + module_len + key_id_len + label_len + + prompt_len + nss_name_len; + + msg = talloc_zero_size(mem_ctx, msg_len); + if (msg == NULL) { + talloc_free(prompt); + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_size failed.\n"); + return ENOMEM; + } + + memcpy(msg, username, user_len); + memcpy(msg + user_len, token_name, token_len); + memcpy(msg + user_len + token_len, module_name, module_len); + memcpy(msg + user_len + token_len + module_len, key_id, key_id_len); + memcpy(msg + user_len + token_len + module_len + key_id_len, + label, label_len); + memcpy(msg + user_len + token_len + module_len + key_id_len + label_len, + prompt, prompt_len); + memcpy(msg + user_len + token_len + module_len + key_id_len + label_len + + prompt_len, + nss_username, nss_name_len); + talloc_free(prompt); + + if (_msg != NULL) { + *_msg = msg; + } + + if (_msg_len != NULL) { + *_msg_len = msg_len; + } + + return EOK; +} + +/* The PKCS11_LOGIN_TOKEN_NAME environment variable is e.g. used by the Gnome + * Settings Daemon to determine the name of the token used for login but it + * should be only set if SSSD is called by gdm-smartcard. Otherwise desktop + * components might assume that gdm-smartcard PAM stack is configured + * correctly which might not be the case e.g. if Smartcard authentication was + * used when running gdm-password. */ +#define PKCS11_LOGIN_TOKEN_ENV_NAME "PKCS11_LOGIN_TOKEN_NAME" + +errno_t add_pam_cert_response(struct pam_data *pd, struct sss_domain_info *dom, + const char *sysdb_username, + struct cert_auth_info *cert_info, + enum response_type type) +{ + uint8_t *msg = NULL; + char *env = NULL; + size_t msg_len; + int ret; + char *short_name = NULL; + char *domain_name = NULL; + const char *cert_info_name = sysdb_username; + struct sss_domain_info *user_dom; + char *nss_name = NULL; + + + if (type != SSS_PAM_CERT_INFO && type != SSS_PAM_CERT_INFO_WITH_HINT) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid response type [%d].\n", type); + return EINVAL; + } + + if ((type == SSS_PAM_CERT_INFO && sysdb_username == NULL) + || cert_info == NULL + || sss_cai_get_token_name(cert_info) == NULL + || sss_cai_get_module_name(cert_info) == NULL + || sss_cai_get_key_id(cert_info) == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing mandatory user or slot name.\n"); + return EINVAL; + } + + /* sysdb_username is a fully-qualified name which is used by pam_sss when + * prompting the user for the PIN and as login name if it wasn't set by + * the PAM caller but has to be determined based on the inserted + * Smartcard. If this type of name is irritating at the PIN prompt or the + * re_expression config option was set in a way that user@domain cannot be + * handled anymore some more logic has to be added here. But for the time + * being I think using sysdb_username is fine. + */ + + if (sysdb_username != NULL) { + ret = sss_parse_internal_fqname(pd, sysdb_username, + &short_name, &domain_name); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse name '%s' [%d]: %s, " + "using full name.\n", + sysdb_username, ret, sss_strerror(ret)); + } else { + if (domain_name != NULL) { + user_dom = find_domain_by_name(dom, domain_name, false); + + if (user_dom != NULL) { + ret = sss_output_fqname(short_name, user_dom, + sysdb_username, false, &nss_name); + if (ret != EOK) { + nss_name = NULL; + } + } + } + + } + } + + ret = pack_cert_data(pd, cert_info_name, cert_info, + nss_name != NULL ? nss_name : sysdb_username, + &msg, &msg_len); + talloc_free(short_name); + talloc_free(domain_name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "pack_cert_data failed.\n"); + return ret; + } + + ret = pam_add_response(pd, type, msg_len, msg); + talloc_free(msg); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "pam_add_response failed to add certificate info.\n"); + return ret; + } + + if (strcmp(pd->service, "gdm-smartcard") == 0) { + env = talloc_asprintf(pd, "%s=%s", PKCS11_LOGIN_TOKEN_ENV_NAME, + sss_cai_get_token_name(cert_info)); + if (env == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + return ENOMEM; + } + + ret = pam_add_response(pd, SSS_PAM_ENV_ITEM, strlen(env) + 1, + (uint8_t *)env); + talloc_free(env); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "pam_add_response failed to add environment variable.\n"); + return ret; + } + } + + return ret; +} diff --git a/src/responder/pam/pamsrv_passkey.c b/src/responder/pam/pamsrv_passkey.c new file mode 100644 index 0000000..1125840 --- /dev/null +++ b/src/responder/pam/pamsrv_passkey.c @@ -0,0 +1,1438 @@ +/* + SSSD + + PAM Responder - passkey related requests + + Copyright (C) Justin Stephenson <jstephen@redhat.com> 2022 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "util/child_common.h" +#include "util/authtok.h" +#include "db/sysdb.h" +#include "db/sysdb_passkey_user_verification.h" +#include "responder/pam/pamsrv.h" + +#include "responder/pam/pamsrv_passkey.h" + +struct pam_passkey_verification_enum_str { + enum passkey_user_verification verification; + const char *option; +}; + +struct pam_passkey_table_data { + hash_table_t *table; + char *key; + struct pk_child_user_data *data; +}; + +struct pam_passkey_verification_enum_str pam_passkey_verification_enum_str[] = { + { PAM_PASSKEY_VERIFICATION_ON, "on" }, + { PAM_PASSKEY_VERIFICATION_OFF, "off" }, + { PAM_PASSKEY_VERIFICATION_OMIT, "unset" }, + { PAM_PASSKEY_VERIFICATION_INVALID, NULL } +}; + +#define PASSKEY_PREFIX "passkey:" +#define USER_VERIFICATION "user_verification=" +#define USER_VERIFICATION_LEN (sizeof(USER_VERIFICATION) -1) + +const char *pam_passkey_verification_enum_to_string(enum passkey_user_verification verification) +{ + size_t c; + + for (c = 0 ; pam_passkey_verification_enum_str[c].option != NULL; c++) { + if (pam_passkey_verification_enum_str[c].verification == verification) { + return pam_passkey_verification_enum_str[c].option; + } + } + + return "(NULL)"; +} + +struct passkey_ctx { + struct pam_ctx *pam_ctx; + struct tevent_context *ev; + struct pam_data *pd; + struct pam_auth_req *preq; +}; + +void pam_forwarder_passkey_cb(struct tevent_req *req); + +errno_t pam_passkey_concatenate_keys(TALLOC_CTX *mem_ctx, + struct pk_child_user_data *pk_data, + bool kerberos_pa, + char **_result_kh, + char **_result_ph); + +struct tevent_req *pam_passkey_get_mapping_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct passkey_ctx *pctx); +void pam_passkey_get_user_done(struct tevent_req *req); +void pam_passkey_get_mapping_done(struct tevent_req *req); +errno_t pam_passkey_get_mapping_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct cache_req_result **_result); + +struct passkey_get_mapping_state { + struct pam_data *pd; + struct cache_req_result *result; +}; + +void passkey_kerberos_cb(struct tevent_req *req) +{ + struct pam_auth_req *preq = tevent_req_callback_data(req, + struct pam_auth_req); + errno_t ret = EOK; + int child_status; + + ret = pam_passkey_auth_recv(req, &child_status); + talloc_free(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "PAM passkey auth failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "passkey child finished with status [%d]\n", child_status); + + pam_check_user_search(preq); + +done: + pam_check_user_done(preq, ret); +} + +errno_t passkey_kerberos(struct pam_ctx *pctx, + struct pam_data *pd, + struct pam_auth_req *preq) +{ + errno_t ret; + const char *prompt; + const char *key; + const char *pin; + size_t pin_len; + struct pk_child_user_data *data; + struct tevent_req *req; + int timeout; + char *verify_opts; + bool debug_libfido2; + enum passkey_user_verification verification; + + ret = sss_authtok_get_passkey(preq, preq->pd->authtok, + &prompt, &key, &pin, &pin_len); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failure to get passkey authtok\n"); + return EIO; + } + + if (prompt == NULL || key == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Passkey prompt and key are missing or invalid.\n"); + return EIO; + } + + data = sss_ptr_hash_lookup(pctx->pk_table_data->table, key, + struct pk_child_user_data); + if (data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to lookup passkey authtok\n"); + return EIO; + } + + ret = confdb_get_int(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_PASSKEY_CHILD_TIMEOUT, PASSKEY_CHILD_TIMEOUT_DEFAULT, + &timeout); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read passkey_child_timeout from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = confdb_get_string(pctx->rctx->cdb, preq, CONFDB_MONITOR_CONF_ENTRY, + CONFDB_MONITOR_PASSKEY_VERIFICATION, NULL, + &verify_opts); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read '"CONFDB_MONITOR_PASSKEY_VERIFICATION"' from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + /* Always use verification sent from passkey krb5 plugin */ + if (strcasecmp(data->user_verification, "false") == 0) { + verification = PAM_PASSKEY_VERIFICATION_OFF; + } else { + verification = PAM_PASSKEY_VERIFICATION_ON; + } + + ret = confdb_get_bool(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_PASSKEY_DEBUG_LIBFIDO2, false, + &debug_libfido2); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read '"CONFDB_PAM_PASSKEY_DEBUG_LIBFIDO2"' from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + req = pam_passkey_auth_send(preq->cctx, preq->cctx->ev, timeout, debug_libfido2, + verification, pd, data, true); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "passkey auth send failed [%d]: [%s]\n", + ret, sss_strerror(ret)); + goto done; + } + + tevent_req_set_callback(req, passkey_kerberos_cb, preq); + + ret = EAGAIN; + +done: + + return ret; + +} + + +errno_t passkey_local(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct pam_ctx *pam_ctx, + struct pam_auth_req *preq, + struct pam_data *pd) +{ + struct tevent_req *req; + struct passkey_ctx *pctx; + errno_t ret; + + pctx = talloc_zero(mem_ctx, struct passkey_ctx); + if (pctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "pctx == NULL\n"); + return ENOMEM; + } + + pctx->pd = pd; + pctx->pam_ctx = pam_ctx; + pctx->ev = ev; + pctx->preq = preq; + + DEBUG(SSSDBG_TRACE_FUNC, "Checking for passkey authentication data\n"); + + req = pam_passkey_get_mapping_send(mem_ctx, pctx->ev, pctx); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "pam_passkey_get_mapping_send failed.\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(req, pam_passkey_get_user_done, pctx); + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + talloc_free(pctx); + } + + return ret; +} + +struct tevent_req *pam_passkey_get_mapping_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct passkey_ctx *pk_ctx) +{ + + struct passkey_get_mapping_state *state; + struct tevent_req *req; + struct tevent_req *subreq; + int ret; + static const char *attrs[] = { SYSDB_NAME, SYSDB_USER_PASSKEY, NULL }; + + req = tevent_req_create(mem_ctx, &state, struct passkey_get_mapping_state); + if (req == NULL) { + ret = ENOMEM; + goto done; + } + + subreq = cache_req_user_by_name_attrs_send(state, pk_ctx->ev, + pk_ctx->pam_ctx->rctx, + pk_ctx->pam_ctx->rctx->ncache, 0, + pk_ctx->pd->domain, + pk_ctx->pd->user, attrs); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, pam_passkey_get_mapping_done, req); + + return req; + +done: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + + return req; +} + +void pam_passkey_get_mapping_done(struct tevent_req *subreq) +{ + struct cache_req_result *result; + struct tevent_req *req; + struct passkey_get_mapping_state *state; + + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct passkey_get_mapping_state); + + ret = cache_req_user_by_name_attrs_recv(state, subreq, &result); + state->result = result; + + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +errno_t pam_passkey_get_mapping_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct cache_req_result **_result) +{ + struct passkey_get_mapping_state *state = NULL; + + state = tevent_req_data(req, struct passkey_get_mapping_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_result = talloc_steal(mem_ctx, state->result); + return EOK; +} + +errno_t read_passkey_conf_verification(TALLOC_CTX *mem_ctx, + const char *verify_opts, + enum passkey_user_verification *_user_verification) +{ + int ret; + TALLOC_CTX *tmp_ctx; + char **opts; + size_t c; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + if (verify_opts == NULL) { + ret = EOK; + goto done; + } + + ret = split_on_separator(tmp_ctx, verify_opts, ',', true, true, &opts, + NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "split_on_separator failed [%d], %s.\n", + ret, sss_strerror(ret)); + goto done; + } + + for (c = 0; opts[c] != NULL; c++) { + if (strncasecmp(opts[c], USER_VERIFICATION, USER_VERIFICATION_LEN) == 0) { + if (strcasecmp("true", &opts[c][USER_VERIFICATION_LEN]) == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "user_verification set to true.\n"); + *_user_verification = PAM_PASSKEY_VERIFICATION_ON; + } else if (strcasecmp("false", &opts[c][USER_VERIFICATION_LEN]) == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "user_verification set to false.\n"); + *_user_verification = PAM_PASSKEY_VERIFICATION_OFF; + } + } else { + DEBUG(SSSDBG_MINOR_FAILURE, + "Unsupported passkey verification option [%s], " \ + "skipping.\n", opts[c]); + } + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t passkey_local_verification(TALLOC_CTX *mem_ctx, + struct passkey_ctx *pctx, + struct confdb_ctx *cdb, + struct sysdb_ctx *sysdb, + const char *domain_name, + struct pk_child_user_data *pk_data, + enum passkey_user_verification *_user_verification, + bool *_debug_libfido2) +{ + TALLOC_CTX *tmp_ctx; + errno_t ret; + const char *verification_from_ldap; + char *verify_opts = NULL; + bool debug_libfido2 = false; + enum passkey_user_verification verification = PAM_PASSKEY_VERIFICATION_OMIT; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sysdb_domain_get_passkey_user_verification(tmp_ctx, sysdb, domain_name, + &verification_from_ldap); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read passkeyUserVerification from sysdb: [%d]: %s\n", + ret, sss_strerror(ret)); + /* This is expected for AD and LDAP */ + ret = EOK; + goto done; + } + + ret = confdb_get_bool(pctx->pam_ctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_PASSKEY_DEBUG_LIBFIDO2, false, + &debug_libfido2); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read '"CONFDB_PAM_PASSKEY_DEBUG_LIBFIDO2"' from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + /* If require user verification setting is set in LDAP, use it */ + if (verification_from_ldap != NULL) { + if (strcasecmp(verification_from_ldap, "true") == 0) { + verification = PAM_PASSKEY_VERIFICATION_ON; + } else if (strcasecmp(verification_from_ldap, "false") == 0) { + verification = PAM_PASSKEY_VERIFICATION_OFF; + } + DEBUG(SSSDBG_TRACE_FUNC, "Passkey verification is being enforced from LDAP\n"); + } else { + /* No verification set in LDAP, fallback to local sssd.conf setting */ + ret = confdb_get_string(pctx->pam_ctx->rctx->cdb, tmp_ctx, CONFDB_MONITOR_CONF_ENTRY, + CONFDB_MONITOR_PASSKEY_VERIFICATION, NULL, + &verify_opts); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read '"CONFDB_MONITOR_PASSKEY_VERIFICATION"' from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + + ret = read_passkey_conf_verification(tmp_ctx, verify_opts, &verification); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Unable to parse passkey verificaton options.\n"); + /* Continue anyway */ + } + DEBUG(SSSDBG_TRACE_FUNC, "Passkey verification is being enforced from local configuration\n"); + } + DEBUG(SSSDBG_TRACE_FUNC, "Passkey verification setting [%s]\n", + pam_passkey_verification_enum_to_string(verification)); + + *_user_verification = verification; + *_debug_libfido2 = debug_libfido2; + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t process_passkey_data(TALLOC_CTX *mem_ctx, + struct ldb_message *user_mesg, + const char *domain, + struct pk_child_user_data *_data) +{ + struct ldb_message_element *el; + TALLOC_CTX *tmp_ctx; + int ret; + char **mappings; + const char **kh_mappings; + const char **public_keys; + const char *domain_name; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ERROR("talloc_new() failed\n"); + return ENOMEM; + } + + el = ldb_msg_find_element(user_mesg, SYSDB_USER_PASSKEY); + if (el == NULL) { + DEBUG(SSSDBG_TRACE_FUNC, "No passkey data found\n"); + ret = ENOENT; + goto done; + } + + /* This attribute may contain other mapping data unrelated to passkey. In that case + * let's omit it. For example, AD user altSecurityIdentities may store ssh public key + * or smart card mapping data */ + ret = split_on_separator(tmp_ctx, (const char *) el->values[0].data, ':', true, true, + &mappings, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Incorrectly formatted passkey data [%d]: %s\n", + ret, sss_strerror(ret)); + ret = ENOENT; + goto done; + } else if (strcasecmp(mappings[0], "passkey") != 0) { + DEBUG(SSSDBG_TRACE_FUNC, "Mapping data found is not passkey related\n"); + ret = ENOENT; + goto done; + } + + kh_mappings = talloc_zero_array(tmp_ctx, const char *, el->num_values + 1); + if (kh_mappings == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n"); + ret = ENOMEM; + goto done; + } + + public_keys = talloc_zero_array(tmp_ctx, const char *, el->num_values + 1); + if (public_keys == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n"); + ret = ENOMEM; + goto done; + } + + for (int i = 0; i < el->num_values; i++) { + ret = split_on_separator(tmp_ctx, (const char *) el->values[i].data, ',', true, true, + &mappings, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Incorrectly formatted passkey data [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + kh_mappings[i] = talloc_strdup(kh_mappings, mappings[0] + strlen(PASSKEY_PREFIX)); + if (kh_mappings[i] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup key handle failed.\n"); + ret = ENOMEM; + goto done; + } + + public_keys[i] = talloc_strdup(public_keys, mappings[1]); + if (public_keys[i] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup public key failed.\n"); + ret = ENOMEM; + goto done; + } + } + + domain_name = talloc_strdup(tmp_ctx, domain); + if (domain_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup domain failed.\n"); + ret = ENOMEM; + goto done; + } + + _data->domain = talloc_steal(mem_ctx, domain_name); + _data->key_handles = talloc_steal(mem_ctx, kh_mappings); + _data->public_keys = talloc_steal(mem_ctx, public_keys); + _data->num_credentials = el->num_values; + + ret = EOK; +done: + talloc_free(tmp_ctx); + + return ret; +} + +void pam_forwarder_passkey_cb(struct tevent_req *req) +{ + struct pam_auth_req *preq = tevent_req_callback_data(req, + struct pam_auth_req); + errno_t ret = EOK; + int child_status; + + ret = pam_passkey_auth_recv(req, &child_status); + talloc_free(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "PAM passkey auth failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + preq->pd->passkey_local_done = true; + + DEBUG(SSSDBG_TRACE_FUNC, "passkey child finished with status [%d]\n", child_status); + preq->pd->pam_status = PAM_SUCCESS; + pam_reply(preq); + + return; + +done: + pam_check_user_done(preq, ret); +} + +void pam_passkey_get_user_done(struct tevent_req *req) +{ + int ret; + struct passkey_ctx *pctx; + bool debug_libfido2 = false; + char *domain_name = NULL; + int timeout; + struct cache_req_result *result = NULL; + struct pk_child_user_data *pk_data = NULL; + enum passkey_user_verification verification = PAM_PASSKEY_VERIFICATION_OMIT; + + pctx = tevent_req_callback_data(req, struct passkey_ctx); + + ret = pam_passkey_get_mapping_recv(pctx, req, &result); + talloc_zfree(req); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "cache_req_user_by_name_attrs_recv failed [%d]: %s.\n", + ret, sss_strerror(ret)); + goto done; + } + + if (result == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "cache req result == NULL\n"); + ret = ENOMEM; + goto done; + } + + pk_data = talloc_zero(pctx, struct pk_child_user_data); + if (!pk_data) { + DEBUG(SSSDBG_CRIT_FAILURE, "pk_data == NULL\n"); + ret = ENOMEM; + goto done; + } + + /* Use dns_name for AD/IPA - for LDAP fallback to domain->name */ + if (result->domain != NULL) { + domain_name = result->domain->dns_name; + if (domain_name == NULL) { + domain_name = result->domain->name; + } + } + + if (domain_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Invalid or missing domain name\n"); + ret = EIO; + goto done; + } + + /* Get passkey data */ + DEBUG(SSSDBG_TRACE_ALL, "Processing passkey data\n"); + ret = process_passkey_data(pk_data, result->msgs[0], domain_name, pk_data); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "process_passkey_data failed: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + /* timeout */ + ret = confdb_get_int(pctx->pam_ctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_PASSKEY_CHILD_TIMEOUT, PASSKEY_CHILD_TIMEOUT_DEFAULT, + &timeout); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read passkey_child_timeout from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = passkey_local_verification(pctx, pctx, pctx->pam_ctx->rctx->cdb, + result->domain->sysdb, result->domain->dns_name, + pk_data, &verification, &debug_libfido2); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to check passkey verification [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + /* Preauth respond with prompt_pin true or false based on user verification */ + if (pctx->pd->cmd == SSS_PAM_PREAUTH) { + const char *prompt_pin = verification == PAM_PASSKEY_VERIFICATION_OFF ? "false" : "true"; + + ret = pam_add_response(pctx->pd, SSS_PAM_PASSKEY_INFO, strlen(prompt_pin) + 1, + (const uint8_t *) prompt_pin); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed. [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + pctx->pd->pam_status = PAM_SUCCESS; + pam_reply(pctx->preq); + talloc_free(pk_data); + return; + } + + req = pam_passkey_auth_send(pctx, pctx->ev, timeout, debug_libfido2, + verification, pctx->pd, pk_data, false); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "pam_passkey_auth_send failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + tevent_req_set_callback(req, pam_forwarder_passkey_cb, pctx->preq); + +done: + if (pk_data != NULL) { + talloc_free(pk_data); + } + + if (ret == ENOENT) { + /* No passkey data, continue through to typical auth flow */ + DEBUG(SSSDBG_TRACE_FUNC, "No passkey data found, skipping passkey auth\n"); + pctx->preq->passkey_data_exists = false; + pam_check_user_search(pctx->preq); + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unexpected passkey error [%d]: %s." + " Skipping passkey auth\n", + ret, sss_strerror(ret)); + pam_check_user_search(pctx->preq); + } + + return; +} + + +struct pam_passkey_auth_send_state { + struct pam_data *pd; + struct tevent_context *ev; + struct tevent_timer *timeout_handler; + struct sss_child_ctx_old *child_ctx; + struct child_io_fds *io; + const char *logfile; + const char **extra_args; + char *verify_opts; + int timeout; + int child_status; + bool kerberos_pa; +}; + +static errno_t passkey_child_exec(struct tevent_req *req); +static void pam_passkey_auth_done(int child_status, + struct tevent_signal *sige, + void *pvt); + +static int pin_destructor(void *ptr) +{ + uint8_t *pin = talloc_get_type(ptr, uint8_t); + if (pin == NULL) return EOK; + + sss_erase_talloc_mem_securely(pin); + + return EOK; +} + +errno_t get_passkey_child_write_buffer(TALLOC_CTX *mem_ctx, + struct pam_data *pd, + uint8_t **_buf, size_t *_len) +{ + int ret; + uint8_t *buf; + size_t len; + const char *pin = NULL; + + if (pd == NULL || pd->authtok == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing authtok.\n"); + return EINVAL; + } + + if (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY || + sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY_KRB) { + ret = sss_authtok_get_passkey_pin(pd->authtok, &pin, &len); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_authtok_get_passkey_pin failed [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + if (pin == NULL || len == 0) { + DEBUG(SSSDBG_OP_FAILURE, "Missing PIN.\n"); + return EINVAL; + } + + buf = talloc_size(mem_ctx, len); + if (buf == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_size failed.\n"); + return ENOMEM; + } + + talloc_set_destructor((void *) buf, pin_destructor); + + safealign_memcpy(buf, pin, len, NULL); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported authtok type [%d].\n", + sss_authtok_get_type(pd->authtok)); + return EINVAL; + } + + *_len = len; + *_buf = buf; + + return EOK; +} + +static void pam_passkey_child_read_data(struct tevent_req *subreq) +{ + uint8_t *buf; + ssize_t buf_len; + char *str; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct pam_passkey_auth_send_state *state = tevent_req_data(req, struct pam_passkey_auth_send_state); + int ret; + + ret = read_pipe_recv(subreq, state, &buf, &buf_len); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + str = malloc(sizeof(char) * buf_len); + if (str == NULL) { + return; + } + + snprintf(str, buf_len, "%s", buf); + + sss_authtok_set_passkey_reply(state->pd->authtok, str, 0); + + free(str); + + tevent_req_done(req); + return; +} + +static void passkey_child_write_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct pam_passkey_auth_send_state *state = tevent_req_data(req, struct pam_passkey_auth_send_state); + + int ret; + + DEBUG(SSSDBG_TRACE_LIBS, "Sending passkey data complete\n"); + + ret = write_pipe_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + PIPE_FD_CLOSE(state->io->write_to_child_fd); + + if (state->kerberos_pa) { + /* Read data back from passkey child */ + subreq = read_pipe_send(state, state->ev, state->io->read_from_child_fd); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "read_pipe_send failed.\n"); + return; + } + + tevent_req_set_callback(subreq, pam_passkey_child_read_data, req); + } +} + +errno_t pam_passkey_concatenate_keys(TALLOC_CTX *mem_ctx, + struct pk_child_user_data *pk_data, + bool kerberos_pa, + char **_result_kh, + char **_result_pk) +{ + errno_t ret; + char *result_kh = NULL; + char *result_pk = NULL; + + result_kh = talloc_strdup(mem_ctx, pk_data->key_handles[0]); + if (!kerberos_pa) { + result_pk = talloc_strdup(mem_ctx, pk_data->public_keys[0]); + } + + for (int i = 1; i < pk_data->num_credentials; i++) { + result_kh = talloc_strdup_append(result_kh, ","); + if (result_kh == NULL) { + ret = ENOMEM; + goto done; + } + + result_kh = talloc_strdup_append(result_kh, pk_data->key_handles[i]); + if (result_kh == NULL) { + ret = ENOMEM; + goto done; + } + + if (!kerberos_pa) { + result_pk = talloc_strdup_append(result_pk, ","); + if (result_pk == NULL) { + ret = ENOMEM; + goto done; + } + + result_pk = talloc_strdup_append(result_pk, pk_data->public_keys[i]); + if (result_kh == NULL || result_pk == NULL) { + ret = ENOMEM; + goto done; + } + } + } + + *_result_kh = result_kh; + *_result_pk = result_pk; + + ret = EOK; +done: + return ret; +} + +struct tevent_req * +pam_passkey_auth_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + int timeout, + bool debug_libfido2, + enum passkey_user_verification verification, + struct pam_data *pd, + struct pk_child_user_data *pk_data, + bool kerberos_pa) +{ + struct tevent_req *req; + struct pam_passkey_auth_send_state *state; + size_t arg_c = 0; + char *result_kh; + char *result_pk; + int num_args; + int ret; + + req = tevent_req_create(mem_ctx, &state, struct pam_passkey_auth_send_state); + if (req == NULL) { + return NULL; + } + + state->pd = pd; + state->ev = ev; + + state->timeout = timeout; + state->kerberos_pa = kerberos_pa; + state->logfile = PASSKEY_CHILD_LOG_FILE; + state->io = talloc(state, struct child_io_fds); + if (state->io == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc child fds failed.\n"); + ret = ENOMEM; + goto done; + } + state->io->write_to_child_fd = -1; + state->io->read_from_child_fd = -1; + talloc_set_destructor((void *) state->io, child_io_destructor); + + num_args = 11; + state->extra_args = talloc_zero_array(state, const char *, num_args + 1); + if (state->extra_args == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n"); + ret = ENOMEM; + goto done; + } + + switch (verification) { + case PAM_PASSKEY_VERIFICATION_ON: + state->extra_args[arg_c++] = "--user-verification=true"; + DEBUG(SSSDBG_TRACE_FUNC, "Calling child with user-verification true\n"); + break; + case PAM_PASSKEY_VERIFICATION_OFF: + state->extra_args[arg_c++] = "--user-verification=false"; + DEBUG(SSSDBG_TRACE_FUNC, "Calling child with user-verification false\n"); + break; + default: + DEBUG(SSSDBG_TRACE_FUNC, "Calling child with user-verification unset\n"); + break; + } + + ret = pam_passkey_concatenate_keys(state, pk_data, state->kerberos_pa, + &result_kh, &result_pk); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "pam_passkey_concatenate keys failed - [%d]: [%s]\n", + ret, sss_strerror(ret)); + goto done; + } + + + if (state->kerberos_pa) { + state->extra_args[arg_c++] = pk_data->crypto_challenge; + state->extra_args[arg_c++] = "--cryptographic-challenge"; + state->extra_args[arg_c++] = result_kh; + state->extra_args[arg_c++] = "--key-handle"; + state->extra_args[arg_c++] = pk_data->domain; + state->extra_args[arg_c++] = "--domain"; + state->extra_args[arg_c++] = "--get-assert"; + } else { + state->extra_args[arg_c++] = result_pk; + state->extra_args[arg_c++] = "--public-key"; + state->extra_args[arg_c++] = result_kh; + state->extra_args[arg_c++] = "--key-handle"; + state->extra_args[arg_c++] = pk_data->domain; + state->extra_args[arg_c++] = "--domain"; + state->extra_args[arg_c++] = state->pd->user; + state->extra_args[arg_c++] = "--username"; + state->extra_args[arg_c++] = "--authenticate"; + } + + ret = passkey_child_exec(req); + +done: + if (ret == EOK) { + tevent_req_done(req); + tevent_req_post(req, ev); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void +passkey_child_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, void *pvt) +{ + struct tevent_req *req = + talloc_get_type(pvt, struct tevent_req); + struct pam_passkey_auth_send_state *state = + tevent_req_data(req, struct pam_passkey_auth_send_state); + + DEBUG(SSSDBG_CRIT_FAILURE, "Timeout reached for passkey child, " + "consider increasing passkey_child_timeout\n"); + child_handler_destroy(state->child_ctx); + state->child_ctx = NULL; + state->child_status = ETIMEDOUT; + tevent_req_error(req, ERR_PASSKEY_CHILD_TIMEOUT); +} + +static errno_t passkey_child_exec(struct tevent_req *req) +{ + struct pam_passkey_auth_send_state *state; + struct tevent_req *subreq; + int pipefd_from_child[2] = PIPE_INIT; + int pipefd_to_child[2] = PIPE_INIT; + pid_t child_pid; + uint8_t *write_buf = NULL; + size_t write_buf_len = 0; + struct timeval tv; + int ret; + + state = tevent_req_data(req, struct pam_passkey_auth_send_state); + + ret = pipe(pipefd_from_child); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "pipe failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + ret = pipe(pipefd_to_child); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "pipe failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + + child_pid = fork(); + if (child_pid == 0) { /* child */ + exec_child_ex(state, pipefd_to_child, pipefd_from_child, + PASSKEY_CHILD_PATH, state->logfile, state->extra_args, + false, STDIN_FILENO, STDOUT_FILENO); + /* We should never get here */ + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "BUG: Could not exec passkey child\n"); + goto done; + } else if (child_pid > 0) { /* parent */ + state->io->read_from_child_fd = pipefd_from_child[0]; + PIPE_FD_CLOSE(pipefd_from_child[1]); + sss_fd_nonblocking(state->io->read_from_child_fd); + + state->io->write_to_child_fd = pipefd_to_child[1]; + PIPE_FD_CLOSE(pipefd_to_child[0]); + sss_fd_nonblocking(state->io->write_to_child_fd); + + /* Set up SIGCHLD handler */ + if (state->kerberos_pa) { + ret = child_handler_setup(state->ev, child_pid, NULL, req, &state->child_ctx); + } else { + ret = child_handler_setup(state->ev, child_pid, + pam_passkey_auth_done, req, + &state->child_ctx); + } + + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not set up child handlers [%d]: %s\n", + ret, sss_strerror(ret)); + ret = ERR_PASSKEY_CHILD; + goto done; + } + + /* Set up timeout handler */ + tv = tevent_timeval_current_ofs(state->timeout, 0); + state->timeout_handler = tevent_add_timer(state->ev, req, tv, + passkey_child_timeout, req); + if (state->timeout_handler == NULL) { + ret = ERR_PASSKEY_CHILD; + goto done; + } + + /* PIN is needed */ + if (sss_authtok_get_type(state->pd->authtok) != SSS_AUTHTOK_TYPE_EMPTY) { + ret = get_passkey_child_write_buffer(state, state->pd, &write_buf, + &write_buf_len); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "get_passkey_child_write_buffer failed [%d]: %s.\n", + ret, sss_strerror(ret)); + goto done; + } + } + + if (write_buf_len != 0) { + subreq = write_pipe_send(state, state->ev, write_buf, write_buf_len, + state->io->write_to_child_fd); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "write_pipe_send failed.\n"); + ret = ERR_PASSKEY_CHILD; + goto done; + } + tevent_req_set_callback(subreq, passkey_child_write_done, req); + } + /* Now either wait for the timeout to fire or the child to finish */ + } else { /* error */ + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "fork failed [%d][%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + return EAGAIN; + +done: + if (ret != EOK) { + PIPE_CLOSE(pipefd_from_child); + PIPE_CLOSE(pipefd_to_child); + } + + return ret; +} + +errno_t pam_passkey_auth_recv(struct tevent_req *req, + int *child_status) +{ + struct pam_passkey_auth_send_state *state = + tevent_req_data(req, struct pam_passkey_auth_send_state); + + *child_status = state->child_status; + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +errno_t decode_pam_passkey_msg(TALLOC_CTX *mem_ctx, + uint8_t *buf, + size_t len, + struct pk_child_user_data **_data) +{ + + size_t p = 0; + size_t pctr = 0; + errno_t ret; + size_t offset; + struct pk_child_user_data *data = NULL; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + data = talloc_zero(tmp_ctx, struct pk_child_user_data); + if (data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to talloc passkey data.\n"); + ret = ENOMEM; + goto done; + } + + data->user_verification = talloc_strdup(data, (char *) &buf[p]); + if (data->user_verification == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to strdup passkey prompt.\n"); + ret = ENOMEM; + goto done; + } + + offset = strlen(data->user_verification) + 1; + if (offset >= len) { + DEBUG(SSSDBG_OP_FAILURE, "passkey prompt offset failure.\n"); + ret = EIO; + goto done; + } + + data->crypto_challenge = talloc_strdup(data, (char *) &buf[p + offset]); + if (data->crypto_challenge == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to strdup passkey challenge.\n"); + ret = ENOMEM; + goto done; + } + + offset += strlen(data->crypto_challenge) + 1; + if (offset >= len) { + DEBUG(SSSDBG_OP_FAILURE, "passkey challenge offset failure.\n"); + ret = EIO; + goto done; + } + + + data->domain = talloc_strdup(data, (char *) &buf[p] + offset); + if (data->domain == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to strdup passkey domain.\n"); + ret = ENOMEM; + goto done; + } + + offset += strlen(data->domain) + 1; + if (offset >= len) { + DEBUG(SSSDBG_OP_FAILURE, "passkey domain offset failure.\n"); + ret = EIO; + goto done; + } + + SAFEALIGN_COPY_UINT32(&data->num_credentials, &buf[p + offset], &pctr); + size_t list_sz = (size_t) data->num_credentials; + + offset += sizeof(uint32_t); + + data->key_handles = talloc_zero_array(data, const char *, list_sz); + + for (int i = 0; i < list_sz; i++) { + data->key_handles[i] = talloc_strdup(data->key_handles, (char *) &buf[p + offset]); + if (data->key_handles[i] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to strdup passkey list.\n"); + ret = ENOMEM; + goto done; + } + + offset += strlen(data->key_handles[i]) + 1; + } + + *_data = talloc_steal(mem_ctx, data); + + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t save_passkey_data(TALLOC_CTX *mem_ctx, + struct pam_ctx *pctx, + struct pk_child_user_data *data, + struct pam_auth_req *preq) +{ + char *pk_key; + errno_t ret; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + /* Passkey data (pk_table_data) is stolen onto client ctx, it will + * be freed when the client closes, and the sss_ptr_hash interface + * takes care of automatically removing it from the hash table then */ + pctx->pk_table_data = talloc_zero(tmp_ctx, struct pam_passkey_table_data); + if (pctx->pk_table_data == NULL) { + return ENOMEM; + } + + if (pctx->pk_table_data->table == NULL) { + pctx->pk_table_data->table = sss_ptr_hash_create(pctx->pk_table_data, + NULL, NULL); + if (pctx->pk_table_data->table == NULL) { + ret = ENOMEM; + goto done; + } + } + + pk_key = talloc_asprintf(tmp_ctx, "%s", data->crypto_challenge); + if (pk_key == NULL) { + ret = ENOMEM; + goto done; + } + + pctx->pk_table_data->key = talloc_strdup(pctx->pk_table_data, pk_key); + if (pctx->pk_table_data->key == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sss_ptr_hash_add(pctx->pk_table_data->table, pk_key, data, + struct pk_child_user_data); + if (ret == EEXIST) { + DEBUG(SSSDBG_TRACE_FUNC, "pk_table key [%s] already exists\n", + pk_key); + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to add pk data to hash table " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + talloc_steal(mem_ctx, pctx->pk_table_data); + pctx->pk_table_data->data = talloc_steal(mem_ctx, data); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t pam_eval_passkey_response(struct pam_ctx *pctx, + struct pam_data *pd, + struct pam_auth_req *preq, + bool *_pk_preauth_done) +{ + struct response_data *pk_resp; + struct pk_child_user_data *pk_data; + errno_t ret; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + pk_resp = pd->resp_list; + + while (pk_resp != NULL) { + switch (pk_resp->type) { + case SSS_PAM_PASSKEY_KRB_INFO: + if (!pctx->passkey_auth) { + /* Passkey auth is disabled. To avoid passkey prompts appearing, + * don't send SSS_PAM_PASSKEY_KRB_INFO to the client and + * add a dummy response to fallback to normal auth */ + pk_resp->do_not_send_to_client = true; + ret = pam_add_response(pd, SSS_OTP, 0, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + goto done; + } + break; + } + ret = decode_pam_passkey_msg(tmp_ctx, pk_resp->data, pk_resp->len, &pk_data); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to decode passkey msg\n"); + ret = EIO; + goto done; + } + + ret = save_passkey_data(preq->cctx, pctx, pk_data, preq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to save passkey msg\n"); + ret = EIO; + goto done; + } + break; + /* Passkey non-kerberos preauth has already run */ + case SSS_PAM_PASSKEY_INFO: + *_pk_preauth_done = true; + default: + break; + } + pk_resp = pk_resp->next; + } + + ret = EOK; +done: + talloc_free(tmp_ctx); + + return ret; +} + + +static void +pam_passkey_auth_done(int child_status, + struct tevent_signal *sige, + void *pvt) +{ + struct tevent_req *req = talloc_get_type(pvt, struct tevent_req); + + struct pam_passkey_auth_send_state *state = + tevent_req_data(req, struct pam_passkey_auth_send_state); + state->child_status = WEXITSTATUS(child_status); + if (WIFEXITED(child_status)) { + if (WEXITSTATUS(child_status) != 0) { + DEBUG(SSSDBG_OP_FAILURE, + PASSKEY_CHILD_PATH " failed with status [%d]. Check passkey_child" + " logs for more information.\n", + WEXITSTATUS(child_status)); + tevent_req_error(req, ERR_PASSKEY_CHILD); + return; + } + } else if (WIFSIGNALED(child_status)) { + DEBUG(SSSDBG_OP_FAILURE, + PASSKEY_CHILD_PATH " was terminated by signal [%d]. Check passkey_child" + " logs for more information.\n", + WTERMSIG(child_status)); + tevent_req_error(req, ECHILD); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "passkey data is valid. Mark done\n"); + + tevent_req_done(req); + return; +} + +bool may_do_passkey_auth(struct pam_ctx *pctx, + struct pam_data *pd) +{ +#ifndef BUILD_PASSKEY + DEBUG(SSSDBG_TRACE_FUNC, "Passkey auth not possible, SSSD built without passkey support!\n"); + return false; +#else + if (!pctx->passkey_auth) { + return false; + } + + if (pd->cmd != SSS_PAM_PREAUTH && pd->cmd != SSS_PAM_AUTHENTICATE) { + return false; + } + + if (pd->service == NULL || *pd->service == '\0') { + return false; + } + + return true; +#endif /* BUILD_PASSKEY */ +} diff --git a/src/responder/pam/pamsrv_passkey.h b/src/responder/pam/pamsrv_passkey.h new file mode 100644 index 0000000..7c5a532 --- /dev/null +++ b/src/responder/pam/pamsrv_passkey.h @@ -0,0 +1,83 @@ +/* + Authors: + Justin Stephenson <jstephen@redhat.com> + + Copyright (C) 2022 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __PAMSRV_PASSKEY_H__ +#define __PAMSRV_PASSKEY_H__ + +#include <security/pam_appl.h> +#include "util/util.h" +#include "util/sss_ptr_hash.h" +#include "responder/common/responder.h" +#include "responder/common/cache_req/cache_req.h" +#include "responder/pam/pamsrv.h" +#include "lib/certmap/sss_certmap.h" + +enum passkey_user_verification { + PAM_PASSKEY_VERIFICATION_ON, + PAM_PASSKEY_VERIFICATION_OFF, + PAM_PASSKEY_VERIFICATION_OMIT, + PAM_PASSKEY_VERIFICATION_INVALID +}; + +errno_t passkey_local(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct pam_ctx *pam_ctx, + struct pam_auth_req *preq, + struct pam_data *pd); +errno_t passkey_kerberos(struct pam_ctx *pctx, + struct pam_data *pd, + struct pam_auth_req *preq); + +struct pk_child_user_data { + /* Both Kerberos and non-kerberos */ + const char *domain; + size_t num_credentials; + const char *user_verification; + const char **key_handles; + /* Kerberos PA only */ + const char *crypto_challenge; + /* Non-kerberos only */ + const char *user; + const char **public_keys; +}; + +errno_t read_passkey_conf_verification(TALLOC_CTX *mem_ctx, + const char *verify_opts, + enum passkey_user_verification *_user_verification); + +void pam_forwarder_passkey_cb(struct tevent_req *req); +struct tevent_req *pam_passkey_auth_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + int timeout, + bool debug_libfido2, + enum passkey_user_verification verification, + struct pam_data *pd, + struct pk_child_user_data *pk_data, + bool kerberos_pa); +errno_t pam_passkey_auth_recv(struct tevent_req *req, + int *child_status); +errno_t pam_eval_passkey_response(struct pam_ctx *pctx, + struct pam_data *pd, + struct pam_auth_req *preq, + bool *_pk_preauth_done); +bool may_do_passkey_auth(struct pam_ctx *pctx, + struct pam_data *pd); + +#endif /* __PAMSRV_PASSKEY_H__ */ |