summaryrefslogtreecommitdiffstats
path: root/src/responder
diff options
context:
space:
mode:
Diffstat (limited to 'src/responder')
-rw-r--r--src/responder/pam/pamsrv.c34
-rw-r--r--src/responder/pam/pamsrv.h22
-rw-r--r--src/responder/pam/pamsrv_cmd.c312
-rw-r--r--src/responder/pam/pamsrv_p11.c527
4 files changed, 858 insertions, 37 deletions
diff --git a/src/responder/pam/pamsrv.c b/src/responder/pam/pamsrv.c
index aa0d2796b..3fe467c3c 100644
--- a/src/responder/pam/pamsrv.c
+++ b/src/responder/pam/pamsrv.c
@@ -50,6 +50,8 @@
#define ALL_DOMAIMS_ARE_PUBLIC "all"
#define NO_DOMAIMS_ARE_PUBLIC "none"
#define DEFAULT_ALLOWED_UIDS ALL_UIDS_ALLOWED
+#define DEFAULT_PAM_CERT_AUTH false
+#define DEFAULT_PAM_CERT_DB_PATH SYSCONFDIR"/pki/nssdb"
struct mon_cli_iface monitor_pam_methods = {
{ &mon_cli_iface_meta, 0 },
@@ -302,6 +304,38 @@ static int pam_process_init(TALLOC_CTX *mem_ctx,
goto done;
}
+ /* 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;
+ }
+
+ pctx->p11_child_debug_fd = -1;
+ 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->nss_db);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_FATAL_FAILURE,
+ "Failed to determine if certificate based authentication is " \
+ "enabled or not.\n");
+ goto done;
+ }
+ }
+
ret = EOK;
done:
diff --git a/src/responder/pam/pamsrv.h b/src/responder/pam/pamsrv.h
index 027800646..59831f2e7 100644
--- a/src/responder/pam/pamsrv.h
+++ b/src/responder/pam/pamsrv.h
@@ -43,6 +43,10 @@ struct pam_ctx {
/* List of domains that are accessible even for untrusted users. */
char **public_domains;
int public_domains_count;
+
+ bool cert_auth;
+ int p11_child_debug_fd;
+ char *nss_db;
};
struct pam_auth_dp_req {
@@ -65,6 +69,9 @@ struct pam_auth_req {
bool cached_auth_failed;
struct pam_auth_dp_req *dpreq_spy;
+
+ struct ldb_message *cert_user_obj;
+ char *token_name;
};
struct sss_cmd_table *get_pam_cmds(void);
@@ -73,4 +80,19 @@ int pam_dp_send_req(struct pam_auth_req *preq, int timeout);
int LOCAL_pam_handler(struct pam_auth_req *preq);
+errno_t p11_child_init(struct pam_ctx *pctx);
+
+struct tevent_req *pam_check_cert_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ int child_debug_fd,
+ const char *nss_db,
+ time_t timeout,
+ struct pam_data *pd);
+errno_t pam_check_cert_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ char **cert, char **token_name);
+
+errno_t add_pam_cert_response(struct pam_data *pd, const char *user,
+ const char *token_name);
+
+bool may_do_cert_auth(struct pam_ctx *pctx, struct pam_data *pd);
#endif /* __PAMSRV_H__ */
diff --git a/src/responder/pam/pamsrv_cmd.c b/src/responder/pam/pamsrv_cmd.c
index 9c32f40ff..3b84fb864 100644
--- a/src/responder/pam/pamsrv_cmd.c
+++ b/src/responder/pam/pamsrv_cmd.c
@@ -31,6 +31,7 @@
#include "providers/data_provider.h"
#include "responder/pam/pamsrv.h"
#include "responder/pam/pam_helpers.h"
+#include "responder/common/responder_cache_req.h"
#include "db/sysdb.h"
enum pam_verbosity {
@@ -49,6 +50,7 @@ static errno_t
pam_get_last_online_auth_with_curr_token(struct sss_domain_info *domain,
const char *name,
uint64_t *_value);
+
static void pam_reply(struct pam_auth_req *preq);
static errno_t pack_user_info_account_expired(TALLOC_CTX *mem_ctx,
@@ -154,6 +156,13 @@ static int extract_authtok_v2(struct sss_auth_token *tok,
ret = sss_authtok_set(tok, SSS_AUTHTOK_TYPE_2FA,
auth_token_data, auth_token_length);
break;
+ case SSS_AUTHTOK_TYPE_SC_PIN:
+ ret = sss_authtok_set_sc_pin(tok, (const char *) auth_token_data,
+ auth_token_length);
+ break;
+ case SSS_AUTHTOK_TYPE_SC_KEYPAD:
+ sss_authtok_set_sc_keypad(tok);
+ break;
default:
return EINVAL;
}
@@ -892,6 +901,7 @@ static void pam_handle_cached_login(struct pam_auth_req *preq, int ret,
}
static void pam_forwarder_cb(struct tevent_req *req);
+static void pam_forwarder_cert_cb(struct tevent_req *req);
static void pam_check_user_dp_callback(uint16_t err_maj, uint32_t err_min,
const char *err_msg, void *ptr);
static int pam_check_user_search(struct pam_auth_req *preq);
@@ -939,9 +949,22 @@ static errno_t pam_forwarder_parse_data(struct cli_ctx *cctx, struct pam_data *p
goto done;
}
- ret = sss_parse_name_for_domains(pd, cctx->rctx->domains,
- cctx->rctx->default_domain, pd->logon_name,
- &pd->domain, &pd->user);
+ 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 {
+ /* Only SSS_PAM_PREAUTH request may have a missing name, e.g. if the
+ * name is determined with the help of a certificate */
+ if (pd->cmd == SSS_PAM_PREAUTH) {
+ ret = EOK;
+ } else {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Missing logon name in PAM request.\n");
+ ret = EINVAL;
+ goto done;
+ }
+ }
DEBUG_PAM_DATA(SSSDBG_CONF_SETTINGS, pd);
@@ -1052,49 +1075,66 @@ static int pam_forwarder(struct cli_ctx *cctx, int pam_cmd)
goto done;
}
- /* now check user is valid */
- if (pd->domain) {
- preq->domain = responder_get_domain(cctx->rctx, pd->domain);
- if (!preq->domain) {
- ret = ENOENT;
- goto done;
- }
-
- ncret = sss_ncache_check_user(pctx->ncache, pctx->neg_timeout,
- preq->domain, pd->user);
- if (ncret == EEXIST) {
- /* User found in the negative cache */
- ret = ENOENT;
- goto done;
- }
- } else {
- for (dom = preq->cctx->rctx->domains;
- dom;
- dom = get_next_domain(dom, false)) {
- if (dom->fqnames) continue;
+ if (pd->user != NULL) {
+ /* now check user is valid */
+ if (pd->domain) {
+ preq->domain = responder_get_domain(cctx->rctx, pd->domain);
+ if (!preq->domain) {
+ ret = ENOENT;
+ goto done;
+ }
ncret = sss_ncache_check_user(pctx->ncache, pctx->neg_timeout,
- dom, pd->user);
- if (ncret == ENOENT) {
- /* User not found in the negative cache
- * Proceed with PAM actions
- */
- break;
+ preq->domain, pd->user);
+ if (ncret == EEXIST) {
+ /* User found in the negative cache */
+ ret = ENOENT;
+ goto done;
+ }
+ } else {
+ for (dom = preq->cctx->rctx->domains;
+ dom;
+ dom = get_next_domain(dom, false)) {
+ if (dom->fqnames) continue;
+
+ ncret = sss_ncache_check_user(pctx->ncache, pctx->neg_timeout,
+ dom, pd->user);
+ if (ncret == ENOENT) {
+ /* User not found in the negative cache
+ * Proceed with PAM actions
+ */
+ break;
+ }
+
+ /* Try the next domain */
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "User [%s@%s] filtered out (negative cache). "
+ "Trying next domain.\n", pd->user, dom->name);
}
- /* Try the next domain */
- DEBUG(SSSDBG_TRACE_FUNC,
- "User [%s@%s] filtered out (negative cache). "
- "Trying next domain.\n", pd->user, dom->name);
+ if (!dom) {
+ ret = ENOENT;
+ goto done;
+ }
+ preq->domain = dom;
}
+ }
- if (!dom) {
- ret = ENOENT;
- goto done;
+ if (may_do_cert_auth(pctx, pd)) {
+ req = pam_check_cert_send(cctx, cctx->ev, pctx->p11_child_debug_fd,
+ pctx->nss_db, 10, pd);
+ if (req == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "pam_check_cert_send failed.\n");
+ ret = ENOMEM;
+ } else {
+ tevent_req_set_callback(req, pam_forwarder_cert_cb, preq);
+ ret = EAGAIN;
}
- preq->domain = dom;
+
+ goto done;
}
+
if (preq->domain->provider == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE,
"Domain [%s] has no auth provider.\n", preq->domain->name);
@@ -1113,6 +1153,142 @@ done:
return pam_check_user_done(preq, ret);
}
+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 cli_ctx *cctx = preq->cctx;
+ struct pam_data *pd;
+ errno_t ret = EOK;
+ char *cert;
+ struct pam_ctx *pctx =
+ talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx);
+
+ ret = pam_check_cert_recv(req, preq, &cert, &preq->token_name);
+ talloc_free(req);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "get_cert request failed.\n");
+ goto done;
+ }
+
+ pd = preq->pd;
+
+ 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_AUTHENTICATE) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "No certificate returned, authentication failed.\n");
+ ret = ENOENT;
+ } else {
+ ret = pam_check_user_search(preq);
+ if (ret == EOK) {
+ pam_dom_forwarder(preq);
+ }
+ }
+
+ }
+ goto done;
+ }
+
+
+ req = cache_req_user_by_cert_send(preq, cctx->ev, cctx->rctx,
+ pctx->ncache, pctx->neg_timeout,
+ 0, NULL, cert);
+ if (req == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "cache_req_user_by_cert_send failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+ tevent_req_set_callback(req, pam_forwarder_lookup_by_cert_done, preq);
+ return;
+
+done:
+ pam_check_user_done(preq, ret);
+}
+
+static void pam_forwarder_lookup_by_cert_done(struct tevent_req *req)
+{
+ int ret;
+ struct ldb_result *res;
+ struct sss_domain_info *domain;
+ struct pam_auth_req *preq = tevent_req_callback_data(req,
+ struct pam_auth_req);
+ const char *cert_user;
+
+
+ ret = cache_req_user_by_cert_recv(preq, req, &res, &domain, NULL);
+ 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 && res->count > 1) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Search by certificate returned more than one result.\n");
+ ret = EINVAL;
+ goto done;
+ }
+
+ if (ret == EOK) {
+ if (preq->domain == NULL) {
+ preq->domain = domain;
+ }
+
+ preq->cert_user_obj = talloc_steal(preq, res->msgs[0]);
+
+ if (preq->pd->logon_name == NULL) {
+ cert_user = ldb_msg_find_attr_as_string(preq->cert_user_obj,
+ 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 = add_pam_cert_response(preq->pd, cert_user, preq->token_name);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "add_pam_cert_response failed.\n");
+ }
+
+ preq->pd->domain = talloc_strdup(preq->pd, domain->name);
+ if (preq->pd->domain == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+ preq->pd->pam_status = PAM_SUCCESS;
+ pam_reply(preq);
+ return;
+ }
+ } else {
+ if (preq->pd->logon_name == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Missing logon name and no certificate user found.\n");
+ ret = ENOENT;
+ goto done;
+ }
+ }
+
+ ret = pam_check_user_search(preq);
+ 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,
@@ -1120,6 +1296,8 @@ static void pam_forwarder_cb(struct tevent_req *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);
@@ -1158,6 +1336,20 @@ static void pam_forwarder_cb(struct tevent_req *req)
}
}
+ if (may_do_cert_auth(pctx, pd)) {
+ req = pam_check_cert_send(cctx, cctx->ev, pctx->p11_child_debug_fd,
+ pctx->nss_db, 10, pd);
+ if (req == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "pam_check_cert_send failed.\n");
+ ret = ENOMEM;
+ } else {
+ tevent_req_set_callback(req, pam_forwarder_cert_cb, preq);
+ ret = EAGAIN;
+ }
+
+ goto done;
+ }
+
ret = pam_check_user_search(preq);
if (ret == EOK) {
pam_dom_forwarder(preq);
@@ -1542,6 +1734,7 @@ static void pam_dom_forwarder(struct pam_auth_req *preq)
int ret;
struct pam_ctx *pctx =
talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx);
+ const char *cert_user;
if (!preq->pd->domain) {
preq->pd->domain = preq->domain->name;
@@ -1579,6 +1772,51 @@ static void pam_dom_forwarder(struct pam_auth_req *preq)
return;
}
+ if (may_do_cert_auth(pctx, preq->pd) && preq->cert_user_obj != NULL) {
+ /* Check if user matches certificate user */
+ cert_user = ldb_msg_find_attr_as_string(preq->cert_user_obj, SYSDB_NAME,
+ NULL);
+ if (cert_user == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Certificate user object has not name.\n");
+ preq->pd->pam_status = PAM_USER_UNKNOWN;
+ pam_reply(preq);
+ return;
+ }
+
+ /* pam_check_user_search() calls pd_set_primary_name() is the search
+ * was successful, so pd->user contains the canonical name as well */
+ if (strcmp(cert_user, preq->pd->user) == 0) {
+
+ preq->pd->pam_status = PAM_SUCCESS;
+
+ if (preq->pd->cmd == SSS_PAM_PREAUTH) {
+ ret = add_pam_cert_response(preq->pd, cert_user,
+ preq->token_name);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "add_pam_cert_response failed.\n");
+ preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL;
+ }
+ }
+
+ 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 (!NEED_CHECK_PROVIDER(preq->domain->provider) ) {
preq->callback = pam_reply;
ret = LOCAL_pam_handler(preq);
diff --git a/src/responder/pam/pamsrv_p11.c b/src/responder/pam/pamsrv_p11.c
new file mode 100644
index 000000000..afb28fd52
--- /dev/null
+++ b/src/responder/pam/pamsrv_p11.c
@@ -0,0 +1,527 @@
+/*
+ SSSD
+
+ PAM Responder - certificate realted 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"
+
+
+#ifndef SSSD_LIBEXEC_PATH
+#error "SSSD_LIBEXEC_PATH not defined"
+#endif /* SSSD_LIBEXEC_PATH */
+
+#define P11_CHILD_LOG_FILE "p11_child"
+#define P11_CHILD_PATH SSSD_LIBEXEC_PATH"/p11_child"
+
+errno_t p11_child_init(struct pam_ctx *pctx)
+{
+ return child_debug_init(P11_CHILD_LOG_FILE, &pctx->p11_child_debug_fd);
+}
+
+bool may_do_cert_auth(struct pam_ctx *pctx, struct pam_data *pd)
+{
+ size_t c;
+ const char *sc_services[] = { "login", "su", "su-l", "gdm-smartcard",
+ "gdm-password", "kdm", "sudo", "sudo-i",
+ NULL };
+ 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;
+ }
+
+ /* TODO: make services configurable */
+ if (pd->service == NULL || *pd->service == '\0') {
+ return false;
+ }
+ for (c = 0; sc_services[c] != NULL; c++) {
+ if (strcmp(pd->service, sc_services[c]) == 0) {
+ break;
+ }
+ }
+ if (sc_services[c] == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "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, char **_cert,
+ char **_token_name)
+{
+ int ret;
+ TALLOC_CTX *tmp_ctx = NULL;
+ uint8_t *p;
+ uint8_t *pn;
+ char *cert = NULL;
+ char *token_name = NULL;
+
+ if (buf_len < 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Error occured while reading data from p11_child.\n");
+ return EIO;
+ }
+
+ if (buf_len == 0) {
+ DEBUG(SSSDBG_TRACE_LIBS, "No certificate found.\n");
+ ret = EOK;
+ goto done;
+ }
+
+ p = memchr(buf, '\n', buf_len);
+ if (p == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "Missing new-line in p11_child response.\n");
+ return EINVAL;
+ }
+ if (p == buf) {
+ DEBUG(SSSDBG_OP_FAILURE, "Missing counter in p11_child response.\n");
+ return EINVAL;
+ }
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n");
+ return ENOMEM;
+ }
+
+ token_name = talloc_strndup(tmp_ctx, (char*) buf, (p - buf));
+ if (token_name == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_strndup failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ p++;
+ 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 = talloc_strndup(tmp_ctx, (char *) p, (pn - p));
+ if(cert == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_strndup failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+ DEBUG(SSSDBG_TRACE_ALL, "Found cert [%s].\n", cert);
+
+ ret = EOK;
+
+done:
+ if (ret == EOK) {
+ *_token_name = talloc_steal(mem_ctx, token_name);
+ *_cert = talloc_steal(mem_ctx, cert);
+ }
+
+ 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;
+
+ int write_to_child_fd;
+ int read_from_child_fd;
+ char *cert;
+ char *token_name;
+};
+
+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,
+ int child_debug_fd,
+ const char *nss_db,
+ time_t timeout,
+ 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];
+ int pipefd_from_child[2];
+ const char *extra_args[5] = {NULL, NULL, NULL, NULL, NULL};
+ uint8_t *write_buf = NULL;
+ size_t write_buf_len = 0;
+
+ req = tevent_req_create(mem_ctx, &state, struct pam_check_cert_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (nss_db == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Missing NSS DB.\n");
+ ret = EINVAL;
+ goto done;
+ }
+
+ /* extra_args are added in revers order */
+ extra_args[1] = "--nssdb";
+ extra_args[0] = nss_db;
+ if (pd->cmd == SSS_PAM_AUTHENTICATE) {
+ extra_args[2] = "--auth";
+ switch (sss_authtok_get_type(pd->authtok)) {
+ case SSS_AUTHTOK_TYPE_SC_PIN:
+ extra_args[3] = "--pin";
+ break;
+ case SSS_AUTHTOK_TYPE_SC_KEYPAD:
+ extra_args[3] = "--keypad";
+ break;
+ default:
+ DEBUG(SSSDBG_OP_FAILURE, "Unsupported authtok type.\n");
+ ret = EINVAL;
+ goto done;
+ }
+ } else if (pd->cmd == SSS_PAM_PREAUTH) {
+ extra_args[2] = "--pre";
+ } else {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected PAM command [%d}.\n", pd->cmd);
+ ret = EINVAL;
+ goto done;
+ }
+
+ state->ev = ev;
+ state->child_status = EFAULT;
+ state->read_from_child_fd = -1;
+ state->write_to_child_fd = -1;
+ state->cert = NULL;
+ state->token_name = NULL;
+
+ 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;
+ }
+
+ if (child_debug_fd == -1) {
+ child_debug_fd = STDERR_FILENO;
+ }
+
+ child_pid = fork();
+ if (child_pid == 0) { /* child */
+ ret = exec_child_ex(state, pipefd_to_child, pipefd_from_child,
+ P11_CHILD_PATH, child_debug_fd, extra_args,
+ STDIN_FILENO, STDOUT_FILENO);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Could not exec p11 child: [%d][%s].\n",
+ ret, strerror(ret));
+ goto done;
+ }
+ } else if (child_pid > 0) { /* parent */
+
+ state->read_from_child_fd = pipefd_from_child[0];
+ close(pipefd_from_child[1]);
+ sss_fd_nonblocking(state->read_from_child_fd);
+
+ state->write_to_child_fd = pipefd_to_child[1];
+ close(pipefd_to_child[0]);
+ sss_fd_nonblocking(state->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 = tevent_timeval_current_ofs(timeout, 0);
+ 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->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->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) {
+ 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;
+ }
+
+ close(state->write_to_child_fd);
+ state->write_to_child_fd = -1;
+
+ subreq = read_pipe_send(state, state->ev, state->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);
+ 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;
+ }
+
+ close(state->read_from_child_fd);
+ state->read_from_child_fd = -1;
+
+ ret = parse_p11_child_response(state, buf, buf_len, &state->cert,
+ &state->token_name);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "parse_p11_child_respose 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.\n");
+ child_handler_destroy(state->child_ctx);
+ state->child_ctx = NULL;
+ state->child_status = ETIMEDOUT;
+ tevent_req_error(req, ERR_P11_CHILD);
+}
+
+errno_t pam_check_cert_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ char **cert, char **token_name)
+{
+ struct pam_check_cert_state *state =
+ tevent_req_data(req, struct pam_check_cert_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ if (cert != NULL) {
+ *cert = talloc_steal(mem_ctx, state->cert);
+ }
+
+ if (token_name != NULL) {
+ *token_name = talloc_steal(mem_ctx, state->token_name);
+ }
+
+ return EOK;
+}
+
+errno_t add_pam_cert_response(struct pam_data *pd, const char *user,
+ const char *token_name)
+{
+ uint8_t *msg = NULL;
+ size_t user_len;
+ size_t msg_len;
+ size_t slot_len;
+ int ret;
+
+ if (user == NULL || token_name == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Missing mandatory user or slot name.\n");
+ return EINVAL;
+ }
+
+ user_len = strlen(user) + 1;
+ slot_len = strlen(token_name) + 1;
+ msg_len = user_len + slot_len;
+
+ msg = talloc_zero_size(pd, msg_len);
+ if (msg == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_size failed.\n");
+ return ENOMEM;
+ }
+
+ memcpy(msg, user, user_len);
+ memcpy(msg + user_len, token_name, slot_len);
+
+ ret = pam_add_response(pd, SSS_PAM_CERT_INFO, msg_len, msg);
+ talloc_free(msg);
+
+ return ret;
+}