summaryrefslogtreecommitdiffstats
path: root/src/responder/pam
diff options
context:
space:
mode:
Diffstat (limited to 'src/responder/pam')
-rw-r--r--src/responder/pam/pam_helpers.c162
-rw-r--r--src/responder/pam/pam_helpers.h42
-rw-r--r--src/responder/pam/pam_prompting_config.c309
-rw-r--r--src/responder/pam/pamsrv.c529
-rw-r--r--src/responder/pam/pamsrv.h173
-rw-r--r--src/responder/pam/pamsrv_cmd.c3085
-rw-r--r--src/responder/pam/pamsrv_dp.c106
-rw-r--r--src/responder/pam/pamsrv_gssapi.c1043
-rw-r--r--src/responder/pam/pamsrv_p11.c1312
-rw-r--r--src/responder/pam/pamsrv_passkey.c1438
-rw-r--r--src/responder/pam/pamsrv_passkey.h83
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 = &el;
+ }
+
+ 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__ */