summaryrefslogtreecommitdiffstats
path: root/src/responder/pam/pamsrv_p11.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/responder/pam/pamsrv_p11.c')
-rw-r--r--src/responder/pam/pamsrv_p11.c1312
1 files changed, 1312 insertions, 0 deletions
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;
+}