summaryrefslogtreecommitdiffstats
path: root/src/providers/krb5
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 05:31:45 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 05:31:45 +0000
commit74aa0bc6779af38018a03fd2cf4419fe85917904 (patch)
tree9cb0681aac9a94a49c153d5823e7a55d1513d91f /src/providers/krb5
parentInitial commit. (diff)
downloadsssd-74aa0bc6779af38018a03fd2cf4419fe85917904.tar.xz
sssd-74aa0bc6779af38018a03fd2cf4419fe85917904.zip
Adding upstream version 2.9.4.upstream/2.9.4
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/providers/krb5')
-rw-r--r--src/providers/krb5/krb5_access.c220
-rw-r--r--src/providers/krb5/krb5_auth.c1356
-rw-r--r--src/providers/krb5/krb5_auth.h158
-rw-r--r--src/providers/krb5/krb5_ccache.c796
-rw-r--r--src/providers/krb5/krb5_ccache.h73
-rw-r--r--src/providers/krb5/krb5_child.c4247
-rw-r--r--src/providers/krb5/krb5_child_handler.c1022
-rw-r--r--src/providers/krb5/krb5_common.c1261
-rw-r--r--src/providers/krb5/krb5_common.h249
-rw-r--r--src/providers/krb5/krb5_delayed_online_authentication.c386
-rw-r--r--src/providers/krb5/krb5_init.c229
-rw-r--r--src/providers/krb5/krb5_init_shared.c105
-rw-r--r--src/providers/krb5/krb5_init_shared.h29
-rw-r--r--src/providers/krb5/krb5_keytab.c231
-rw-r--r--src/providers/krb5/krb5_opts.c50
-rw-r--r--src/providers/krb5/krb5_opts.h30
-rw-r--r--src/providers/krb5/krb5_renew_tgt.c631
-rw-r--r--src/providers/krb5/krb5_utils.c605
-rw-r--r--src/providers/krb5/krb5_utils.h59
-rw-r--r--src/providers/krb5/krb5_wait_queue.c373
20 files changed, 12110 insertions, 0 deletions
diff --git a/src/providers/krb5/krb5_access.c b/src/providers/krb5/krb5_access.c
new file mode 100644
index 0000000..2ae5abe
--- /dev/null
+++ b/src/providers/krb5/krb5_access.c
@@ -0,0 +1,220 @@
+/*
+ SSSD
+
+ Kerberos 5 Backend Module - access control
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2010 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 "util/util.h"
+#include "providers/krb5/krb5_auth.h"
+#include "providers/krb5/krb5_common.h"
+#include "providers/krb5/krb5_utils.h"
+
+struct krb5_access_state {
+ struct tevent_context *ev;
+ struct be_ctx *be_ctx;
+
+ struct pam_data *pd;
+ struct krb5_ctx *krb5_ctx;
+ struct krb5child_req *kr;
+
+ bool access_allowed;
+};
+
+static void krb5_access_done(struct tevent_req *subreq);
+struct tevent_req *krb5_access_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct be_ctx *be_ctx,
+ struct pam_data *pd,
+ struct krb5_ctx *krb5_ctx)
+{
+ struct krb5_access_state *state;
+ struct tevent_req *req;
+ struct tevent_req *subreq;
+ int ret;
+ const char **attrs;
+ struct ldb_result *res;
+ struct sss_domain_info *dom;
+
+ req = tevent_req_create(mem_ctx, &state, struct krb5_access_state);
+ if (req == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create failed.\n");
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->be_ctx = be_ctx;
+ state->pd = pd;
+ state->krb5_ctx = krb5_ctx;
+ state->access_allowed = false;
+
+ ret = get_domain_or_subdomain(be_ctx, pd->domain, &dom);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "get_domain_or_subdomain failed.\n");
+ goto done;
+ }
+
+ ret = krb5_setup(state, pd, dom, krb5_ctx, &state->kr);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "krb5_setup failed.\n");
+ goto done;
+ }
+
+ if (pd->cmd != SSS_PAM_ACCT_MGMT) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Unexpected pam task %d.\n", pd->cmd);
+ ret = EINVAL;
+ goto done;
+ }
+
+ attrs = talloc_array(state, const char *, 5);
+ if (attrs == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_array failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ attrs[0] = SYSDB_UPN;
+ attrs[1] = SYSDB_UIDNUM;
+ attrs[2] = SYSDB_GIDNUM;
+ attrs[3] = SYSDB_CANONICAL_UPN;
+ attrs[4] = NULL;
+
+ ret = sysdb_get_user_attr(state, be_ctx->domain, state->pd->user, attrs,
+ &res);
+ if (ret) {
+ DEBUG(SSSDBG_FUNC_DATA,
+ "sysdb search for upn of user [%s] failed.\n", pd->user);
+ goto done;
+ }
+
+ switch (res->count) {
+ case 0:
+ DEBUG(SSSDBG_FUNC_DATA,
+ "No attributes for user [%s] found.\n", pd->user);
+ ret = ENOENT;
+ goto done;
+ break;
+ case 1:
+ ret = find_or_guess_upn(state, res->msgs[0], krb5_ctx, be_ctx->domain,
+ state->kr->user, pd->domain, &state->kr->upn);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "find_or_guess_upn failed.\n");
+ goto done;
+ }
+
+ state->kr->uid = ldb_msg_find_attr_as_uint64(res->msgs[0], SYSDB_UIDNUM,
+ 0);
+ if (state->kr->uid == 0) {
+ DEBUG(SSSDBG_CONF_SETTINGS,
+ "UID for user [%s] not known.\n", pd->user);
+ ret = ENOENT;
+ goto done;
+ }
+
+ state->kr->gid = ldb_msg_find_attr_as_uint64(res->msgs[0], SYSDB_GIDNUM,
+ 0);
+ if (state->kr->gid == 0) {
+ DEBUG(SSSDBG_CONF_SETTINGS,
+ "GID for user [%s] not known.\n", pd->user);
+ ret = ENOENT;
+ goto done;
+ }
+
+ break;
+ default:
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "User search for [%s] returned > 1 results!\n", pd->user);
+ ret = EINVAL;
+ goto done;
+ break;
+ }
+
+ subreq = handle_child_send(state, state->ev, state->kr);
+ if (subreq == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "handle_child_send failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ tevent_req_set_callback(subreq, krb5_access_done, req);
+ return req;
+
+done:
+ if (ret == EOK) {
+ tevent_req_done(req);
+ } else {
+ tevent_req_error(req, ret);
+ }
+ tevent_req_post(req, state->ev);
+ return req;
+}
+
+static void krb5_access_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req);
+ struct krb5_access_state *state = tevent_req_data(req,
+ struct krb5_access_state);
+ int ret;
+ uint8_t *buf = NULL;
+ ssize_t len = -1;
+ int32_t msg_status;
+
+ ret = handle_child_recv(subreq, state, &buf, &len);
+ talloc_free(subreq);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "child failed [%d][%s].\n", ret, strerror(ret));
+ goto fail;
+ }
+
+ if ((size_t) len != sizeof(int32_t)) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "message has the wrong size.\n");
+ ret = EINVAL;
+ goto fail;
+ }
+
+ SAFEALIGN_COPY_INT32(&msg_status, buf, NULL);
+
+ if (msg_status == EOK) {
+ state->access_allowed = true;
+ } else {
+ state->access_allowed = false;
+ }
+
+ tevent_req_done(req);
+ return;
+
+fail:
+ tevent_req_error(req, ret);
+ return;
+}
+
+int krb5_access_recv(struct tevent_req *req, bool *access_allowed)
+{
+ struct krb5_access_state *state = tevent_req_data(req,
+ struct krb5_access_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ *access_allowed = state->access_allowed;
+
+ return EOK;
+}
diff --git a/src/providers/krb5/krb5_auth.c b/src/providers/krb5/krb5_auth.c
new file mode 100644
index 0000000..be34880
--- /dev/null
+++ b/src/providers/krb5/krb5_auth.c
@@ -0,0 +1,1356 @@
+/*
+ SSSD
+
+ Kerberos 5 Backend Module
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2009-2010 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 <sys/time.h>
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <pwd.h>
+#include <sys/stat.h>
+
+#include <security/pam_modules.h>
+
+#include "util/util.h"
+#include "util/crypto/sss_crypto.h"
+#include "util/find_uid.h"
+#include "util/auth_utils.h"
+#include "db/sysdb.h"
+#include "util/sss_utf8.h"
+#include "util/child_common.h"
+#include "providers/krb5/krb5_auth.h"
+#include "providers/krb5/krb5_utils.h"
+#include "providers/krb5/krb5_ccache.h"
+
+#define NON_POSIX_CCNAME_FMT "MEMORY:sssd_nonposix_dummy_%u"
+
+static int krb5_mod_ccname(TALLOC_CTX *mem_ctx,
+ struct sysdb_ctx *sysdb,
+ struct sss_domain_info *domain,
+ const char *name,
+ const char *ccname,
+ int mod_op)
+{
+ TALLOC_CTX *tmpctx;
+ struct sysdb_attrs *attrs;
+ int ret;
+ errno_t sret;
+ bool in_transaction = false;
+
+ if (name == NULL || ccname == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Missing user or ccache name.\n");
+ return EINVAL;
+ }
+
+ if (mod_op != SYSDB_MOD_REP && mod_op != SYSDB_MOD_DEL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported operation [%d].\n", mod_op);
+ return EINVAL;
+ }
+
+ DEBUG(SSSDBG_TRACE_ALL, "%s ccname [%s] for user [%s].\n",
+ mod_op == SYSDB_MOD_REP ? "Save" : "Delete", ccname, name);
+
+ tmpctx = talloc_new(mem_ctx);
+ if (!tmpctx) {
+ return ENOMEM;
+ }
+
+ attrs = sysdb_new_attrs(tmpctx);
+ if (!attrs) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = sysdb_attrs_add_string(attrs, SYSDB_CCACHE_FILE, ccname);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_attrs_add_string failed.\n");
+ goto done;
+ }
+
+ ret = sysdb_transaction_start(sysdb);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Error %d starting transaction (%s)\n", ret, strerror(ret));
+ goto done;
+ }
+ in_transaction = true;
+
+ ret = sysdb_set_user_attr(domain, name, attrs, mod_op);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_TRACE_FUNC, "Error: %d (%s)\n", ret, strerror(ret));
+ goto done;
+ }
+
+ ret = sysdb_transaction_commit(sysdb);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction!\n");
+ goto done;
+ }
+ in_transaction = false;
+
+done:
+ if (in_transaction) {
+ sret = sysdb_transaction_cancel(sysdb);
+ if (sret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Failed to cancel transaction\n");
+ }
+ }
+ talloc_zfree(tmpctx);
+ return ret;
+}
+
+static int krb5_save_ccname(TALLOC_CTX *mem_ctx,
+ struct sysdb_ctx *sysdb,
+ struct sss_domain_info *domain,
+ const char *name,
+ const char *ccname)
+{
+ return krb5_mod_ccname(mem_ctx, sysdb, domain, name, ccname,
+ SYSDB_MOD_REP);
+}
+
+static int krb5_delete_ccname(TALLOC_CTX *mem_ctx,
+ struct sysdb_ctx *sysdb,
+ struct sss_domain_info *domain,
+ const char *name,
+ const char *ccname)
+{
+ return krb5_mod_ccname(mem_ctx, sysdb, domain, name, ccname,
+ SYSDB_MOD_DEL);
+}
+
+static int krb5_cleanup(void *ptr)
+{
+ struct krb5child_req *kr = talloc_get_type(ptr, struct krb5child_req);
+
+ if (kr == NULL) return EOK;
+
+ memset(kr, 0, sizeof(struct krb5child_req));
+
+ return EOK;
+}
+
+static errno_t
+get_krb_primary(struct map_id_name_to_krb_primary *name_to_primary,
+ char *id_prov_name, bool cs, const char **_krb_primary)
+{
+ errno_t ret;
+ int i = 0;
+
+ while(name_to_primary != NULL &&
+ name_to_primary[i].id_name != NULL &&
+ name_to_primary[i].krb_primary != NULL) {
+
+ if (sss_string_equal(cs, name_to_primary[i].id_name, id_prov_name)) {
+ *_krb_primary = name_to_primary[i].krb_primary;
+ ret = EOK;
+ goto done;
+ }
+ i++;
+ }
+
+ /* Handle also the case of name_to_primary being NULL */
+ ret = ENOENT;
+
+done:
+ return ret;
+}
+
+errno_t krb5_setup(TALLOC_CTX *mem_ctx,
+ struct pam_data *pd,
+ struct sss_domain_info *dom,
+ struct krb5_ctx *krb5_ctx,
+ struct krb5child_req **_krb5_req)
+{
+ struct krb5child_req *kr;
+ const char *mapped_name;
+ TALLOC_CTX *tmp_ctx;
+ errno_t ret;
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ return ENOMEM;
+ }
+
+ kr = talloc_zero(tmp_ctx, struct krb5child_req);
+ if (kr == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+ kr->is_offline = false;
+ talloc_set_destructor((TALLOC_CTX *) kr, krb5_cleanup);
+
+ kr->pd = pd;
+ kr->dom = dom;
+ kr->krb5_ctx = krb5_ctx;
+
+ ret = get_krb_primary(krb5_ctx->name_to_primary,
+ pd->user, dom->case_sensitive, &mapped_name);
+ if (ret == EOK) {
+ DEBUG(SSSDBG_TRACE_FUNC, "Setting mapped name to: %s\n", mapped_name);
+ kr->user = mapped_name;
+
+ kr->kuserok_user = sss_output_name(kr, kr->user,
+ dom->case_sensitive, 0);
+ if (kr->kuserok_user == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ } else if (ret == ENOENT) {
+ DEBUG(SSSDBG_TRACE_ALL, "No mapping for: %s\n", pd->user);
+ kr->user = pd->user;
+
+ kr->kuserok_user = sss_output_name(kr, kr->user,
+ dom->case_sensitive, 0);
+ if (kr->kuserok_user == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ } else {
+ DEBUG(SSSDBG_CRIT_FAILURE, "get_krb_primary failed - %s:[%d]\n",
+ sss_strerror(ret), ret);
+ goto done;
+ }
+
+ ret = EOK;
+
+done:
+ if (ret == EOK) {
+ *_krb5_req = talloc_steal(mem_ctx, kr);
+ }
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+
+static void krb5_auth_cache_creds(struct krb5_ctx *krb5_ctx,
+ struct sss_domain_info *domain,
+ struct confdb_ctx *cdb,
+ struct pam_data *pd, uid_t uid,
+ int *pam_status, int *dp_err)
+{
+ const char *password = NULL;
+ errno_t ret;
+
+ ret = sss_authtok_get_password(pd->authtok, &password, NULL);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_FATAL_FAILURE,
+ "Failed to get password [%d] %s. Delayed authentication is only "
+ "available for password authentication (single factor).\n",
+ ret, strerror(ret));
+ *pam_status = PAM_SYSTEM_ERR;
+ *dp_err = DP_ERR_OK;
+ return;
+ }
+
+ ret = sysdb_cache_auth(domain, pd->user,
+ password, cdb, true, NULL, NULL);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Offline authentication failed\n");
+ *pam_status = cached_login_pam_status(ret);
+ *dp_err = DP_ERR_OK;
+ return;
+ }
+
+ ret = add_user_to_delayed_online_authentication(krb5_ctx, domain, pd, uid);
+ if (ret == ENOTSUP) {
+ /* This error is not fatal */
+ DEBUG(SSSDBG_MINOR_FAILURE, "Delayed authentication not supported\n");
+ } else if (ret != EOK) {
+ /* This error is not fatal */
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "add_user_to_delayed_online_authentication failed.\n");
+ }
+ *pam_status = PAM_AUTHINFO_UNAVAIL;
+ *dp_err = DP_ERR_OFFLINE;
+}
+
+static errno_t krb5_auth_prepare_ccache_name(struct krb5child_req *kr,
+ struct ldb_message *user_msg,
+ struct be_ctx *be_ctx)
+{
+ const char *ccname_template;
+
+ switch (kr->dom->type) {
+ case DOM_TYPE_POSIX:
+ ccname_template = dp_opt_get_cstring(kr->krb5_ctx->opts, KRB5_CCNAME_TMPL);
+
+ kr->ccname = expand_ccname_template(kr, kr, ccname_template,
+ kr->krb5_ctx->illegal_path_re, true,
+ be_ctx->domain->case_sensitive);
+ if (kr->ccname == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "expand_ccname_template failed.\n");
+ return ENOMEM;
+ }
+
+ kr->old_ccname = ldb_msg_find_attr_as_string(user_msg,
+ SYSDB_CCACHE_FILE, NULL);
+ if (kr->old_ccname == NULL) {
+ DEBUG(SSSDBG_TRACE_LIBS,
+ "No ccache file for user [%s] found.\n", kr->pd->user);
+ }
+ break;
+ case DOM_TYPE_APPLICATION:
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "Domain type application, will use in-memory ccache\n");
+ kr->ccname = talloc_asprintf(kr,
+ NON_POSIX_CCNAME_FMT,
+ sss_rand() % UINT_MAX);
+ if (kr->ccname == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n");
+ return ENOMEM;
+ }
+
+ break;
+ default:
+ DEBUG(SSSDBG_FATAL_FAILURE, "Unsupported domain type\n");
+ return EINVAL;
+ }
+
+ return EOK;
+}
+
+static void krb5_auth_store_creds(struct sss_domain_info *domain,
+ struct pam_data *pd)
+{
+ const char *password = NULL;
+ const char *fa2;
+ size_t password_len;
+ size_t fa2_len = 0;
+ int ret = EOK;
+
+ switch(pd->cmd) {
+ case SSS_CMD_RENEW:
+ /* The authtok is set to the credential cache
+ * during renewal. We don't want to save this
+ * as the cached password.
+ */
+ break;
+ case SSS_PAM_PREAUTH:
+ /* There are no credentials available during pre-authentication,
+ * nothing to do. */
+ break;
+ case SSS_PAM_AUTHENTICATE:
+ case SSS_PAM_CHAUTHTOK_PRELIM:
+ if (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_2FA) {
+ ret = sss_authtok_get_2fa(pd->authtok, &password, &password_len,
+ &fa2, &fa2_len);
+ if (ret == EOK && password_len <
+ domain->cache_credentials_min_ff_length) {
+ DEBUG(SSSDBG_FATAL_FAILURE,
+ "First factor is too short to be cache, "
+ "minimum length is [%u].\n",
+ domain->cache_credentials_min_ff_length);
+ ret = EINVAL;
+ }
+ } else if (sss_authtok_get_type(pd->authtok) ==
+ SSS_AUTHTOK_TYPE_PASSWORD) {
+ ret = sss_authtok_get_password(pd->authtok, &password, NULL);
+ } else {
+ DEBUG(SSSDBG_MINOR_FAILURE, "Cannot cache authtok type [%d].\n",
+ sss_authtok_get_type(pd->authtok));
+ ret = EINVAL;
+ }
+ break;
+ case SSS_PAM_CHAUTHTOK:
+ ret = sss_authtok_get_password(pd->newauthtok, &password, NULL);
+ break;
+ default:
+ DEBUG(SSSDBG_FATAL_FAILURE,
+ "unsupported PAM command [%d].\n", pd->cmd);
+ }
+
+ if (ret != EOK) {
+ DEBUG(SSSDBG_FATAL_FAILURE,
+ "Failed to get password [%d] %s\n", ret, strerror(ret));
+ /* password caching failures are not fatal errors */
+ return;
+ }
+
+ if (password == NULL) {
+ if (pd->cmd != SSS_CMD_RENEW && pd->cmd != SSS_PAM_PREAUTH) {
+ DEBUG(SSSDBG_FATAL_FAILURE,
+ "password not available, offline auth may not work.\n");
+ /* password caching failures are not fatal errors */
+ }
+ return;
+ }
+
+ ret = sysdb_cache_password_ex(domain, pd->user, password,
+ sss_authtok_get_type(pd->authtok), fa2_len);
+ if (ret) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Failed to cache password, offline auth may not work."
+ " (%d)[%s]!?\n", ret, strerror(ret));
+ /* password caching failures are not fatal errors */
+ }
+}
+
+static bool is_otp_enabled(struct ldb_message *user_msg)
+{
+ struct ldb_message_element *el;
+ size_t i;
+
+ el = ldb_msg_find_element(user_msg, SYSDB_AUTH_TYPE);
+ if (el == NULL) {
+ return false;
+ }
+
+ for (i = 0; i < el->num_values; i++) {
+ if (strcmp((const char * )el->values[i].data, "otp") == 0) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/* krb5_auth request */
+
+struct krb5_auth_state {
+ struct tevent_context *ev;
+ struct be_ctx *be_ctx;
+ struct pam_data *pd;
+ struct sysdb_ctx *sysdb;
+ struct sss_domain_info *domain;
+ struct krb5_ctx *krb5_ctx;
+ struct krb5child_req *kr;
+
+ bool search_kpasswd;
+
+ int pam_status;
+ int dp_err;
+};
+
+static void krb5_auth_resolve_done(struct tevent_req *subreq);
+static void krb5_auth_done(struct tevent_req *subreq);
+
+struct tevent_req *krb5_auth_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct be_ctx *be_ctx,
+ struct pam_data *pd,
+ struct krb5_ctx *krb5_ctx)
+{
+ const char **attrs;
+ struct krb5_auth_state *state;
+ struct ldb_result *res;
+ struct krb5child_req *kr = NULL;
+ const char *realm;
+ struct tevent_req *req;
+ struct tevent_req *subreq;
+ enum sss_authtok_type authtok_type;
+ int ret;
+ bool otp;
+
+ req = tevent_req_create(mem_ctx, &state, struct krb5_auth_state);
+ if (req == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create failed.\n");
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->be_ctx = be_ctx;
+ state->pd = pd;
+ state->krb5_ctx = krb5_ctx;
+ state->kr = NULL;
+ state->pam_status = PAM_SYSTEM_ERR;
+ state->dp_err = DP_ERR_FATAL;
+
+ ret = get_domain_or_subdomain(be_ctx, pd->domain, &state->domain);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "get_domain_or_subdomain failed.\n");
+ goto done;
+ }
+
+ state->sysdb = state->domain->sysdb;
+
+ authtok_type = sss_authtok_get_type(pd->authtok);
+
+ switch (pd->cmd) {
+ case SSS_PAM_AUTHENTICATE:
+ case SSS_PAM_CHAUTHTOK:
+ if (authtok_type != SSS_AUTHTOK_TYPE_PASSWORD
+ && authtok_type != SSS_AUTHTOK_TYPE_2FA
+ && authtok_type != SSS_AUTHTOK_TYPE_2FA_SINGLE
+ && authtok_type != SSS_AUTHTOK_TYPE_SC_PIN
+ && authtok_type != SSS_AUTHTOK_TYPE_SC_KEYPAD
+ && authtok_type != SSS_AUTHTOK_TYPE_OAUTH2
+ && authtok_type != SSS_AUTHTOK_TYPE_PASSKEY
+ && authtok_type != SSS_AUTHTOK_TYPE_PASSKEY_KRB
+ && authtok_type != SSS_AUTHTOK_TYPE_PASSKEY_REPLY) {
+ /* handle empty password gracefully */
+ if (authtok_type == SSS_AUTHTOK_TYPE_EMPTY) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Illegal empty authtok for user [%s]\n",
+ pd->user);
+ state->pam_status = PAM_AUTH_ERR;
+ state->dp_err = DP_ERR_OK;
+ ret = EOK;
+ goto done;
+ }
+
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Wrong authtok type for user [%s]. " \
+ "Expected [%d], got [%d]\n", pd->user,
+ SSS_AUTHTOK_TYPE_PASSWORD,
+ authtok_type);
+ state->pam_status = PAM_SYSTEM_ERR;
+ state->dp_err = DP_ERR_FATAL;
+ ret = EINVAL;
+ goto done;
+ }
+ break;
+ case SSS_PAM_CHAUTHTOK_PRELIM:
+ if (pd->priv == 1 &&
+ authtok_type != SSS_AUTHTOK_TYPE_PASSWORD) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "Password reset by root is not supported.\n");
+ state->pam_status = PAM_PERM_DENIED;
+ state->dp_err = DP_ERR_OK;
+ ret = EOK;
+ goto done;
+ }
+ break;
+ case SSS_CMD_RENEW:
+ if (authtok_type != SSS_AUTHTOK_TYPE_CCFILE) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Wrong authtok type for user [%s]. " \
+ "Expected [%d], got [%d]\n", pd->user,
+ SSS_AUTHTOK_TYPE_CCFILE,
+ authtok_type);
+ state->pam_status = PAM_SYSTEM_ERR;
+ state->dp_err = DP_ERR_FATAL;
+ ret = EINVAL;
+ goto done;
+ }
+ break;
+ case SSS_PAM_PREAUTH:
+ break;
+ default:
+ DEBUG(SSSDBG_CONF_SETTINGS, "Unexpected pam task %d.\n", pd->cmd);
+ state->pam_status = PAM_SYSTEM_ERR;
+ state->dp_err = DP_ERR_FATAL;
+ ret = EINVAL;
+ goto done;
+ }
+
+ if (be_is_offline(be_ctx) &&
+ (pd->cmd == SSS_PAM_CHAUTHTOK || pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM ||
+ pd->cmd == SSS_CMD_RENEW)) {
+ DEBUG(SSSDBG_TRACE_ALL,
+ "Password changes and ticket renewal are not possible "
+ "while offline.\n");
+ state->pam_status = PAM_AUTHINFO_UNAVAIL;
+ state->dp_err = DP_ERR_OFFLINE;
+ ret = EOK;
+ goto done;
+ }
+
+ attrs = talloc_array(state, const char *, 8);
+ if (attrs == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ attrs[0] = SYSDB_UPN;
+ attrs[1] = SYSDB_HOMEDIR;
+ attrs[2] = SYSDB_CCACHE_FILE;
+ attrs[3] = SYSDB_UIDNUM;
+ attrs[4] = SYSDB_GIDNUM;
+ attrs[5] = SYSDB_CANONICAL_UPN;
+ attrs[6] = SYSDB_AUTH_TYPE;
+ attrs[7] = NULL;
+
+ ret = krb5_setup(state, pd, state->domain, krb5_ctx,
+ &state->kr);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "krb5_setup failed.\n");
+ goto done;
+ }
+ kr = state->kr;
+
+ ret = sysdb_get_user_attr_with_views(state, state->domain, state->pd->user,
+ attrs, &res);
+ if (ret) {
+ DEBUG(SSSDBG_FUNC_DATA,
+ "sysdb search for upn of user [%s] failed.\n", pd->user);
+ state->pam_status = PAM_SYSTEM_ERR;
+ state->dp_err = DP_ERR_OK;
+ goto done;
+ }
+
+ realm = dp_opt_get_cstring(krb5_ctx->opts, KRB5_REALM);
+ if (realm == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Missing Kerberos realm.\n");
+ ret = ENOENT;
+ goto done;
+ }
+
+ switch (res->count) {
+ case 0:
+ DEBUG(SSSDBG_FUNC_DATA,
+ "No attributes for user [%s] found.\n", pd->user);
+ ret = ENOENT;
+ goto done;
+ break;
+
+ case 1:
+ ret = find_or_guess_upn(state, res->msgs[0], krb5_ctx, be_ctx->domain,
+ kr->user, pd->domain, &kr->upn);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "find_or_guess_upn failed.\n");
+ goto done;
+ }
+
+ ret = compare_principal_realm(kr->upn, realm,
+ &kr->upn_from_different_realm);
+ if (ret != 0) {
+ DEBUG(SSSDBG_OP_FAILURE, "compare_principal_realm failed.\n");
+ goto done;
+ }
+
+ kr->homedir = sss_view_ldb_msg_find_attr_as_string(state->domain,
+ res->msgs[0],
+ SYSDB_HOMEDIR,
+ NULL);
+ if (kr->homedir == NULL) {
+ DEBUG(SSSDBG_CONF_SETTINGS,
+ "Home directory for user [%s] not known.\n", pd->user);
+ }
+
+ kr->uid = sss_view_ldb_msg_find_attr_as_uint64(state->domain,
+ res->msgs[0],
+ SYSDB_UIDNUM, 0);
+ if (kr->uid == 0 && state->domain->type == DOM_TYPE_POSIX) {
+ DEBUG(SSSDBG_CONF_SETTINGS,
+ "UID for user [%s] not known.\n", pd->user);
+ ret = ENOENT;
+ goto done;
+ }
+
+ kr->gid = sss_view_ldb_msg_find_attr_as_uint64(state->domain,
+ res->msgs[0],
+ SYSDB_GIDNUM, 0);
+ if (kr->gid == 0 && state->domain->type == DOM_TYPE_POSIX) {
+ DEBUG(SSSDBG_CONF_SETTINGS,
+ "GID for user [%s] not known.\n", pd->user);
+ ret = ENOENT;
+ goto done;
+ }
+
+ ret = krb5_auth_prepare_ccache_name(kr, res->msgs[0], state->be_ctx);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Cannot prepare ccache names!\n");
+ goto done;
+ }
+ break;
+
+ default:
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "User search for (%s) returned > 1 results!\n", pd->user);
+ ret = EINVAL;
+ goto done;
+ break;
+ }
+
+ otp = is_otp_enabled(res->msgs[0]);
+ if (pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM && otp == true) {
+ /* To avoid consuming the OTP */
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "Skipping password checks for OTP-enabled user\n");
+ state->pam_status = PAM_SUCCESS;
+ state->dp_err = DP_ERR_OK;
+ ret = EOK;
+ goto done;
+ }
+
+ kr->srv = NULL;
+ kr->kpasswd_srv = NULL;
+
+ state->search_kpasswd = false;
+ subreq = be_resolve_server_send(state, state->ev, state->be_ctx,
+ state->krb5_ctx->service->name,
+ state->kr->srv == NULL ? true : false);
+ if (!subreq) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Failed resolver request.\n");
+ ret = EIO;
+ goto done;
+ }
+ tevent_req_set_callback(subreq, krb5_auth_resolve_done, req);
+
+ return req;
+
+done:
+ if (ret == EOK) {
+ tevent_req_done(req);
+ } else {
+ tevent_req_error(req, ret);
+ }
+ tevent_req_post(req, state->ev);
+ return req;
+}
+
+static void krb5_auth_resolve_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req);
+ struct krb5_auth_state *state = tevent_req_data(req, struct krb5_auth_state);
+ struct krb5child_req *kr = state->kr;
+ int ret;
+
+ if (!state->search_kpasswd) {
+ ret = be_resolve_server_recv(subreq, kr, &kr->srv);
+ } else {
+ ret = be_resolve_server_recv(subreq, kr, &kr->kpasswd_srv);
+ }
+ talloc_zfree(subreq);
+
+ if (state->search_kpasswd) {
+ if ((ret != EOK) &&
+ (kr->pd->cmd == SSS_PAM_CHAUTHTOK ||
+ kr->pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM)) {
+ /* all kpasswd servers have been tried and none was found good,
+ * but the kdc seems ok. Password changes are not possible but
+ * authentication is. We return an PAM error here, but do not
+ * mark the backend offline. */
+ state->pam_status = PAM_AUTHTOK_LOCK_BUSY;
+ state->dp_err = DP_ERR_OK;
+ ret = EOK;
+ goto done;
+ }
+ } else {
+ if (ret != EOK) {
+ /* all servers have been tried and none
+ * was found good, setting offline,
+ * but we still have to call the child to setup
+ * the ccache file if we are performing auth */
+ be_mark_dom_offline(state->domain, state->be_ctx);
+ kr->is_offline = true;
+
+ if (kr->pd->cmd == SSS_PAM_CHAUTHTOK ||
+ kr->pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM) {
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "No KDC suitable for password change is available\n");
+ state->pam_status = PAM_AUTHTOK_LOCK_BUSY;
+ state->dp_err = DP_ERR_OK;
+ ret = EOK;
+ goto done;
+ }
+ } else {
+ if (kr->krb5_ctx->kpasswd_service != NULL) {
+ state->search_kpasswd = true;
+ subreq = be_resolve_server_send(state,
+ state->ev, state->be_ctx,
+ state->krb5_ctx->kpasswd_service->name,
+ kr->kpasswd_srv == NULL ? true : false);
+ if (subreq == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Resolver request failed.\n");
+ ret = EIO;
+ goto done;
+ }
+ tevent_req_set_callback(subreq, krb5_auth_resolve_done, req);
+ return;
+ }
+ }
+ }
+
+ if (!kr->is_offline) {
+ kr->is_offline = be_is_offline(state->be_ctx);
+ }
+
+ if (!kr->is_offline
+ && sss_domain_get_state(state->domain) == DOM_INACTIVE) {
+ DEBUG(SSSDBG_TRACE_INTERNAL,
+ "Subdomain %s is inactive, will proceed offline\n",
+ state->domain->name);
+ kr->is_offline = true;
+ }
+
+ if (kr->is_offline
+ && sss_krb5_realm_has_proxy(dp_opt_get_cstring(kr->krb5_ctx->opts,
+ KRB5_REALM))) {
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "Resetting offline status, KDC proxy is in use\n");
+ kr->is_offline = false;
+ }
+
+ subreq = handle_child_send(state, state->ev, kr);
+ if (subreq == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "handle_child_send failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+ tevent_req_set_callback(subreq, krb5_auth_done, req);
+ return;
+
+done:
+ if (ret == EOK) {
+ tevent_req_done(req);
+ } else {
+ tevent_req_error(req, ret);
+ }
+}
+
+static void krb5_auth_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req);
+ struct krb5_auth_state *state = tevent_req_data(req, struct krb5_auth_state);
+ struct krb5child_req *kr = state->kr;
+ struct pam_data *pd = state->pd;
+ int ret;
+ uint8_t *buf = NULL;
+ ssize_t len = -1;
+ struct krb5_child_response *res;
+ struct fo_server *search_srv;
+ krb5_deltat renew_interval_delta;
+ char *renew_interval_str;
+ time_t renew_interval_time = 0;
+ bool use_enterprise_principal;
+ bool canonicalize;
+
+ ret = handle_child_recv(subreq, pd, &buf, &len);
+ talloc_zfree(subreq);
+ if (ret == ETIMEDOUT) {
+
+ DEBUG(SSSDBG_CRIT_FAILURE, "child timed out!\n");
+
+ switch (pd->cmd) {
+ case SSS_PAM_AUTHENTICATE:
+ case SSS_CMD_RENEW:
+ state->search_kpasswd = false;
+ search_srv = kr->srv;
+ break;
+ case SSS_PAM_CHAUTHTOK:
+ case SSS_PAM_CHAUTHTOK_PRELIM:
+ if (state->kr->kpasswd_srv) {
+ state->search_kpasswd = true;
+ search_srv = kr->kpasswd_srv;
+ break;
+ } else {
+ state->search_kpasswd = false;
+ search_srv = kr->srv;
+ break;
+ }
+ case SSS_PAM_PREAUTH:
+ state->pam_status = PAM_CRED_UNAVAIL;
+ state->dp_err = DP_ERR_OK;
+ ret = EOK;
+ goto done;
+ default:
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected PAM task %d\n", pd->cmd);
+ ret = EINVAL;
+ goto done;
+ }
+
+ be_fo_set_port_status(state->be_ctx, state->krb5_ctx->service->name,
+ search_srv, PORT_NOT_WORKING);
+ subreq = be_resolve_server_send(state, state->ev, state->be_ctx,
+ state->krb5_ctx->service->name,
+ search_srv == NULL ? true : false);
+ if (subreq == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Failed resolved request.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+ tevent_req_set_callback(subreq, krb5_auth_resolve_done, req);
+ return;
+
+ } else if (ret != EOK) {
+
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "child failed (%d [%s])\n", ret, strerror(ret));
+ goto done;
+ }
+
+ /* EOK */
+
+ ret = parse_krb5_child_response(state, buf, len, pd,
+ state->be_ctx->domain->pwd_expiration_warning,
+ &res);
+ if (ret) {
+ DEBUG(SSSDBG_IMPORTANT_INFO,
+ "The krb5_child process returned an error. Please inspect the "
+ "krb5_child.log file or the journal for more information\n");
+ DEBUG(SSSDBG_OP_FAILURE, "Could not parse child response [%d]: %s\n",
+ ret, strerror(ret));
+ goto done;
+ }
+
+ if (res->ccname) {
+ kr->ccname = talloc_strdup(kr, res->ccname);
+ if (!kr->ccname) {
+ ret = ENOMEM;
+ goto done;
+ }
+ }
+
+ use_enterprise_principal = dp_opt_get_bool(kr->krb5_ctx->opts,
+ KRB5_USE_ENTERPRISE_PRINCIPAL);
+ canonicalize = dp_opt_get_bool(kr->krb5_ctx->opts, KRB5_CANONICALIZE);
+
+ /* Check if the cases of our upn are correct and update it if needed.
+ * Fail if the upn differs by more than just the case for non-enterprise
+ * principals. */
+ if (res->correct_upn != NULL &&
+ strcmp(kr->upn, res->correct_upn) != 0) {
+ if (strcasecmp(kr->upn, res->correct_upn) == 0 ||
+ canonicalize == true ||
+ use_enterprise_principal == true) {
+ talloc_free(kr->upn);
+ kr->upn = talloc_strdup(kr, res->correct_upn);
+ if (kr->upn == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = check_if_cached_upn_needs_update(state->sysdb, state->domain,
+ pd->user, res->correct_upn);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "check_if_cached_upn_needs_update failed.\n");
+ goto done;
+ }
+ } else {
+ DEBUG(SSSDBG_CRIT_FAILURE, "UPN used in the request [%s] and " \
+ "returned UPN [%s] differ by more " \
+ "than just the case.\n",
+ kr->upn, res->correct_upn);
+ ret = EINVAL;
+ goto done;
+ }
+ }
+
+ /* If the child request failed, but did not return an offline error code,
+ * return with the status */
+ switch (res->msg_status) {
+ case ERR_OK:
+ /* If the child request was successful and we run the first pass of the
+ * change password request just return success. */
+ if (pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM) {
+ state->pam_status = PAM_SUCCESS;
+ state->dp_err = DP_ERR_OK;
+ ret = EOK;
+ goto done;
+ }
+ break;
+
+ case ERR_NETWORK_IO:
+ if (kr->kpasswd_srv != NULL &&
+ (pd->cmd == SSS_PAM_CHAUTHTOK ||
+ pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM)) {
+ /* if using a dedicated kpasswd server for a chpass operation... */
+
+ be_fo_set_port_status(state->be_ctx,
+ state->krb5_ctx->kpasswd_service->name,
+ kr->kpasswd_srv, PORT_NOT_WORKING);
+ /* ..try to resolve next kpasswd server */
+ state->search_kpasswd = true;
+ subreq = be_resolve_server_send(state, state->ev, state->be_ctx,
+ state->krb5_ctx->kpasswd_service->name,
+ state->kr->kpasswd_srv == NULL ? true : false);
+ if (subreq == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Resolver request failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+ tevent_req_set_callback(subreq, krb5_auth_resolve_done, req);
+ return;
+ } else if (kr->srv != NULL) {
+ /* failed to use the KDC... */
+ be_fo_set_port_status(state->be_ctx,
+ state->krb5_ctx->service->name,
+ kr->srv, PORT_NOT_WORKING);
+ /* ..try to resolve next KDC */
+ state->search_kpasswd = false;
+ subreq = be_resolve_server_send(state, state->ev, state->be_ctx,
+ state->krb5_ctx->service->name,
+ kr->srv == NULL ? true : false);
+ if (subreq == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Resolver request failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+ tevent_req_set_callback(subreq, krb5_auth_resolve_done, req);
+ return;
+ }
+ break;
+
+ case ERR_CREDS_EXPIRED_CCACHE:
+ ret = krb5_delete_ccname(state, state->sysdb, state->domain,
+ pd->user, kr->old_ccname);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "krb5_delete_ccname failed.\n");
+ }
+ /* FALLTHROUGH */
+ SSS_ATTRIBUTE_FALLTHROUGH;
+
+ case ERR_CREDS_EXPIRED:
+ /* If the password is expired we can safely remove the ccache from the
+ * cache and disk if it is not actively used anymore. This will allow
+ * to create a new random ccache if sshd with privilege separation is
+ * used. */
+ if (pd->cmd == SSS_PAM_AUTHENTICATE && !kr->active_ccache) {
+ if (kr->old_ccname != NULL) {
+ ret = krb5_delete_ccname(state, state->sysdb, state->domain,
+ pd->user, kr->old_ccname);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "krb5_delete_ccname failed.\n");
+ }
+ }
+ }
+
+ state->pam_status = PAM_NEW_AUTHTOK_REQD;
+ state->dp_err = DP_ERR_OK;
+ ret = EOK;
+ goto done;
+
+ case ERR_CREDS_INVALID:
+ state->pam_status = PAM_CRED_ERR;
+ state->dp_err = DP_ERR_OK;
+ ret = EOK;
+ goto done;
+
+ case ERR_ACCOUNT_EXPIRED:
+ state->pam_status = PAM_ACCT_EXPIRED;
+ state->dp_err = DP_ERR_OK;
+ ret = EOK;
+ goto done;
+
+ case ERR_ACCOUNT_LOCKED:
+ state->pam_status = PAM_PERM_DENIED;
+ state->dp_err = DP_ERR_OK;
+ ret = EOK;
+ goto done;
+
+ case ERR_NO_CREDS:
+ state->pam_status = PAM_CRED_UNAVAIL;
+ state->dp_err = DP_ERR_OK;
+ ret = EOK;
+ goto done;
+
+ case ERR_AUTH_FAILED:
+ state->pam_status = PAM_AUTH_ERR;
+ state->dp_err = DP_ERR_OK;
+ ret = EOK;
+ goto done;
+
+ case ERR_CHPASS_FAILED:
+ state->pam_status = PAM_AUTHTOK_ERR;
+ state->dp_err = DP_ERR_OK;
+ ret = EOK;
+ goto done;
+
+ case ERR_NO_AUTH_METHOD_AVAILABLE:
+ state->pam_status = PAM_NO_MODULE_DATA;
+ state->dp_err = DP_ERR_OK;
+ ret = EOK;
+ goto done;
+
+ default:
+ DEBUG(SSSDBG_IMPORTANT_INFO,
+ "The krb5_child process returned an error. Please inspect the "
+ "krb5_child.log file or the journal for more information\n");
+ state->pam_status = PAM_SYSTEM_ERR;
+ state->dp_err = DP_ERR_OK;
+ ret = EOK;
+ goto done;
+ }
+
+ if (kr->kpasswd_srv != NULL &&
+ (pd->cmd == SSS_PAM_CHAUTHTOK ||
+ pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM)) {
+ /* found a dedicated kpasswd server for a chpass operation */
+ be_fo_set_port_status(state->be_ctx,
+ state->krb5_ctx->service->name,
+ kr->kpasswd_srv, PORT_WORKING);
+ } else if (kr->srv != NULL) {
+ /* found a KDC */
+ be_fo_set_port_status(state->be_ctx, state->krb5_ctx->service->name,
+ kr->srv, PORT_WORKING);
+ }
+
+ if (pd->cmd == SSS_PAM_PREAUTH) {
+ state->pam_status = PAM_SUCCESS;
+ state->dp_err = DP_ERR_OK;
+ ret = EOK;
+ goto done;
+ }
+
+ /* Now only a successful authentication or password change is left.
+ *
+ * We expect that one of the messages in the received buffer contains
+ * the name of the credential cache file. */
+ if (kr->ccname == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Missing ccache name in child response.\n");
+ ret = EINVAL;
+ goto done;
+ }
+
+ ret = krb5_save_ccname(state, state->sysdb, state->domain,
+ pd->user, kr->ccname);
+ if (ret) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "krb5_save_ccname failed.\n");
+ goto done;
+ }
+ renew_interval_str = dp_opt_get_string(kr->krb5_ctx->opts,
+ KRB5_RENEW_INTERVAL);
+ if (renew_interval_str != NULL) {
+ ret = krb5_string_to_deltat(renew_interval_str, &renew_interval_delta);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "Reading krb5_renew_interval failed.\n");
+ renew_interval_delta = 0;
+ }
+ renew_interval_time = renew_interval_delta;
+ }
+ if (res->msg_status == ERR_OK && renew_interval_time > 0 &&
+ (pd->cmd == SSS_PAM_AUTHENTICATE ||
+ pd->cmd == SSS_CMD_RENEW ||
+ pd->cmd == SSS_PAM_CHAUTHTOK) &&
+ (res->tgtt.renew_till > res->tgtt.endtime) &&
+ (kr->ccname != NULL)) {
+ DEBUG(SSSDBG_TRACE_LIBS,
+ "Adding [%s] for automatic renewal.\n", kr->ccname);
+ ret = add_tgt_to_renew_table(kr->krb5_ctx, kr->ccname, &(res->tgtt),
+ pd, kr->upn);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "add_tgt_to_renew_table failed, "
+ "automatic renewal not possible.\n");
+ }
+ }
+
+ if (kr->is_offline) {
+ if (dp_opt_get_bool(kr->krb5_ctx->opts,
+ KRB5_STORE_PASSWORD_IF_OFFLINE)
+ && sss_authtok_get_type(pd->authtok)
+ == SSS_AUTHTOK_TYPE_PASSWORD) {
+ krb5_auth_cache_creds(state->kr->krb5_ctx,
+ state->domain,
+ state->be_ctx->cdb,
+ state->pd, state->kr->uid,
+ &state->pam_status, &state->dp_err);
+ } else {
+ DEBUG(SSSDBG_CONF_SETTINGS,
+ "Backend is marked offline, retry later!\n");
+ state->pam_status = PAM_AUTHINFO_UNAVAIL;
+ state->dp_err = DP_ERR_OFFLINE;
+ }
+ ret = EOK;
+ goto done;
+ }
+
+ if (state->be_ctx->domain->cache_credentials == TRUE
+ && (!res->otp
+ || (res->otp && sss_authtok_get_type(pd->authtok) ==
+ SSS_AUTHTOK_TYPE_2FA))) {
+ krb5_auth_store_creds(state->domain, pd);
+ }
+
+ /* The SSS_OTP message will prevent pam_sss from putting the entered
+ * password on the PAM stack for other modules to use. This is not needed
+ * when both factors were entered separately because here the first factor
+ * (long term password) can be passed to the other modules. */
+ if (res->otp == true && pd->cmd == SSS_PAM_AUTHENTICATE
+ && sss_authtok_get_type(pd->authtok) != SSS_AUTHTOK_TYPE_2FA) {
+ uint32_t otp_flag = 1;
+ ret = pam_add_response(pd, SSS_OTP, sizeof(uint32_t),
+ (const uint8_t *) &otp_flag);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "pam_add_response failed: %d (%s).\n",
+ ret, sss_strerror(ret));
+ state->pam_status = PAM_SYSTEM_ERR;
+ state->dp_err = DP_ERR_OK;
+ goto done;
+ }
+ }
+
+ state->pam_status = PAM_SUCCESS;
+ state->dp_err = DP_ERR_OK;
+ ret = EOK;
+
+done:
+ if (ret == EOK) {
+ tevent_req_done(req);
+ } else {
+ tevent_req_error(req, ret);
+ }
+
+}
+
+int krb5_auth_recv(struct tevent_req *req, int *pam_status, int *dp_err)
+{
+ struct krb5_auth_state *state = tevent_req_data(req, struct krb5_auth_state);
+ *pam_status = state->pam_status;
+ *dp_err = state->dp_err;
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}
+
+struct krb5_pam_handler_state {
+ struct pam_data *pd;
+};
+
+static void krb5_pam_handler_auth_done(struct tevent_req *subreq);
+static void krb5_pam_handler_access_done(struct tevent_req *subreq);
+
+struct tevent_req *
+krb5_pam_handler_send(TALLOC_CTX *mem_ctx,
+ struct krb5_ctx *krb5_ctx,
+ struct pam_data *pd,
+ struct dp_req_params *params)
+{
+ struct krb5_pam_handler_state *state;
+ struct tevent_req *subreq;
+ struct tevent_req *req;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct krb5_pam_handler_state);
+ if (req == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n");
+ return NULL;
+ }
+
+ state->pd = pd;
+
+ switch (pd->cmd) {
+ case SSS_PAM_AUTHENTICATE:
+ case SSS_PAM_PREAUTH:
+ case SSS_CMD_RENEW:
+ case SSS_PAM_CHAUTHTOK_PRELIM:
+ case SSS_PAM_CHAUTHTOK:
+ subreq = krb5_auth_queue_send(state, params->ev, params->be_ctx,
+ pd, krb5_ctx);
+ if (subreq == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "krb5_auth_send failed.\n");
+ pd->pam_status = PAM_SYSTEM_ERR;
+ goto immediately;
+ }
+
+ tevent_req_set_callback(subreq, krb5_pam_handler_auth_done, req);
+ break;
+ case SSS_PAM_ACCT_MGMT:
+ subreq = krb5_access_send(state, params->ev, params->be_ctx,
+ pd, krb5_ctx);
+ if (subreq == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "krb5_access_send failed.\n");
+ pd->pam_status = PAM_SYSTEM_ERR;
+ goto immediately;
+ }
+
+ tevent_req_set_callback(subreq, krb5_pam_handler_access_done, req);
+ break;
+ case SSS_PAM_SETCRED:
+ case SSS_PAM_OPEN_SESSION:
+ case SSS_PAM_CLOSE_SESSION:
+ pd->pam_status = PAM_SUCCESS;
+ goto immediately;
+ break;
+ default:
+ DEBUG(SSSDBG_CONF_SETTINGS,
+ "krb5 does not handles pam task %d.\n", pd->cmd);
+ pd->pam_status = PAM_MODULE_UNKNOWN;
+ goto immediately;
+ }
+
+ return req;
+
+immediately:
+ /* TODO For backward compatibility we always return EOK to DP now. */
+ tevent_req_done(req);
+ tevent_req_post(req, params->ev);
+
+ return req;
+}
+
+static void krb5_pam_handler_auth_done(struct tevent_req *subreq)
+{
+ struct krb5_pam_handler_state *state;
+ struct tevent_req *req;
+ errno_t ret;
+
+ req = tevent_req_callback_data(subreq, struct tevent_req);
+ state = tevent_req_data(req, struct krb5_pam_handler_state);
+
+ ret = krb5_auth_queue_recv(subreq, &state->pd->pam_status, NULL);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ state->pd->pam_status = PAM_SYSTEM_ERR;
+ }
+
+ /* PAM_CRED_ERR is used to indicate to the IPA provider that trying
+ * password migration would make sense. From this point on it isn't
+ * necessary to keep this status, so it can be translated to PAM_AUTH_ERR.
+ */
+ if (state->pd->pam_status == PAM_CRED_ERR) {
+ state->pd->pam_status = PAM_AUTH_ERR;
+ }
+
+ /* TODO For backward compatibility we always return EOK to DP now. */
+ tevent_req_done(req);
+}
+
+static void krb5_pam_handler_access_done(struct tevent_req *subreq)
+{
+ struct krb5_pam_handler_state *state;
+ struct tevent_req *req;
+ bool access_allowed;
+ errno_t ret;
+
+ req = tevent_req_callback_data(subreq, struct tevent_req);
+ state = tevent_req_data(req, struct krb5_pam_handler_state);
+
+ ret = krb5_access_recv(subreq, &access_allowed);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ state->pd->pam_status = PAM_SYSTEM_ERR;
+ }
+
+
+ DEBUG(SSSDBG_TRACE_LIBS, "Access %s for user [%s].\n",
+ access_allowed ? "allowed" : "denied", state->pd->user);
+ state->pd->pam_status = access_allowed ? PAM_SUCCESS : PAM_PERM_DENIED;
+
+ /* TODO For backward compatibility we always return EOK to DP now. */
+ tevent_req_done(req);
+}
+
+errno_t
+krb5_pam_handler_recv(TALLOC_CTX *mem_ctx,
+ struct tevent_req *req,
+ struct pam_data **_data)
+{
+ struct krb5_pam_handler_state *state = NULL;
+
+ state = tevent_req_data(req, struct krb5_pam_handler_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ *_data = talloc_steal(mem_ctx, state->pd);
+
+ return EOK;
+}
diff --git a/src/providers/krb5/krb5_auth.h b/src/providers/krb5/krb5_auth.h
new file mode 100644
index 0000000..bbdbf61
--- /dev/null
+++ b/src/providers/krb5/krb5_auth.h
@@ -0,0 +1,158 @@
+/*
+ SSSD
+
+ Kerberos Backend, private header file
+
+ Authors:
+ 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 __KRB5_AUTH_H__
+#define __KRB5_AUTH_H__
+
+
+#include "util/sss_regexp.h"
+#include "util/sss_krb5.h"
+#include "providers/backend.h"
+#include "util/child_common.h"
+#include "providers/krb5/krb5_common.h"
+#include "providers/krb5/krb5_ccache.h"
+
+#define CCACHE_ENV_NAME "KRB5CCNAME"
+
+#define ILLEGAL_PATH_PATTERN "//|/\\./|/\\.\\./"
+
+#define CHILD_OPT_FAST_CCACHE_UID "fast-ccache-uid"
+#define CHILD_OPT_FAST_CCACHE_GID "fast-ccache-gid"
+#define CHILD_OPT_FAST_USE_ANONYMOUS_PKINIT "fast-use-anonymous-pkinit"
+#define CHILD_OPT_REALM "realm"
+#define CHILD_OPT_LIFETIME "lifetime"
+#define CHILD_OPT_RENEWABLE_LIFETIME "renewable-lifetime"
+#define CHILD_OPT_USE_FAST "use-fast"
+#define CHILD_OPT_FAST_PRINCIPAL "fast-principal"
+#define CHILD_OPT_CANONICALIZE "canonicalize"
+#define CHILD_OPT_SSS_CREDS_PASSWORD "sss-creds-password"
+#define CHILD_OPT_CHAIN_ID "chain-id"
+#define CHILD_OPT_CHECK_PAC "check-pac"
+
+struct krb5child_req {
+ struct pam_data *pd;
+ struct krb5_ctx *krb5_ctx;
+ struct sss_domain_info *dom;
+
+ const char *ccname;
+ const char *old_ccname;
+ const char *homedir;
+ char *upn;
+ uid_t uid;
+ gid_t gid;
+ bool is_offline;
+ struct fo_server *srv;
+ struct fo_server *kpasswd_srv;
+ bool active_ccache;
+ bool valid_tgt;
+ bool upn_from_different_realm;
+ bool send_pac;
+
+ const char *user;
+ const char *kuserok_user;
+};
+
+errno_t krb5_setup(TALLOC_CTX *mem_ctx,
+ struct pam_data *pd,
+ struct sss_domain_info *dom,
+ struct krb5_ctx *krb5_ctx,
+ struct krb5child_req **_krb5_req);
+
+struct tevent_req *
+krb5_pam_handler_send(TALLOC_CTX *mem_ctx,
+ struct krb5_ctx *krb5_ctx,
+ struct pam_data *pd,
+ struct dp_req_params *params);
+
+errno_t
+krb5_pam_handler_recv(TALLOC_CTX *mem_ctx,
+ struct tevent_req *req,
+ struct pam_data **_data);
+
+/* Please use krb5_auth_send/recv *only* if you're certain there can't
+ * be concurrent logins happening. With some ccache back ends, the ccache
+ * files might clobber one another. Please use krb5_auth_queue_send()
+ * instead that queues the requests
+ */
+struct tevent_req *krb5_auth_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct be_ctx *be_ctx,
+ struct pam_data *pd,
+ struct krb5_ctx *krb5_ctx);
+int krb5_auth_recv(struct tevent_req *req, int *pam_status, int *dp_err);
+
+struct tevent_req *handle_child_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct krb5child_req *kr);
+int handle_child_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ uint8_t **buf, ssize_t *len);
+
+struct krb5_child_response {
+ int32_t msg_status;
+ struct tgt_times tgtt;
+ char *ccname;
+ char *correct_upn;
+ bool otp;
+};
+
+errno_t
+parse_krb5_child_response(TALLOC_CTX *mem_ctx, uint8_t *buf, ssize_t len,
+ struct pam_data *pd, int pwd_exp_warning,
+ struct krb5_child_response **_res);
+
+errno_t add_user_to_delayed_online_authentication(struct krb5_ctx *krb5_ctx,
+ struct sss_domain_info *domain,
+ struct pam_data *pd,
+ uid_t uid);
+errno_t init_delayed_online_authentication(struct krb5_ctx *krb5_ctx,
+ struct be_ctx *be_ctx,
+ struct tevent_context *ev);
+
+errno_t init_renew_tgt(struct krb5_ctx *krb5_ctx, struct be_ctx *be_ctx,
+ struct tevent_context *ev, time_t renew_intv);
+errno_t add_tgt_to_renew_table(struct krb5_ctx *krb5_ctx, const char *ccfile,
+ struct tgt_times *tgtt, struct pam_data *pd,
+ const char *upn);
+
+/* krb5_access.c */
+struct tevent_req *krb5_access_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct be_ctx *be_ctx,
+ struct pam_data *pd,
+ struct krb5_ctx *krb5_ctx);
+int krb5_access_recv(struct tevent_req *req, bool *access_allowed);
+
+/* krb5_wait_queue.c */
+struct tevent_req *krb5_auth_queue_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct be_ctx *be_ctx,
+ struct pam_data *pd,
+ struct krb5_ctx *krb5_ctx);
+
+int krb5_auth_queue_recv(struct tevent_req *req,
+ int *_pam_status,
+ int *_dp_err);
+
+#endif /* __KRB5_AUTH_H__ */
diff --git a/src/providers/krb5/krb5_ccache.c b/src/providers/krb5/krb5_ccache.c
new file mode 100644
index 0000000..88f75a8
--- /dev/null
+++ b/src/providers/krb5/krb5_ccache.c
@@ -0,0 +1,796 @@
+/*
+ SSSD
+
+ Kerberos 5 Backend Module -- ccache related utilities
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+ Jakub Hrozek <jhrozek@redhat.com>
+
+ Copyright (C) 2014 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/>.
+*/
+
+#ifdef HAVE_KRB5_KRB5_H
+#include <krb5/krb5.h>
+#else
+#include <krb5.h>
+#endif
+
+#include "providers/krb5/krb5_ccache.h"
+#include "util/sss_krb5.h"
+#include "util/util.h"
+
+struct string_list {
+ struct string_list *next;
+ struct string_list *prev;
+ char *s;
+};
+
+static errno_t find_ccdir_parent_data(TALLOC_CTX *mem_ctx,
+ const char *ccdirname,
+ struct stat *parent_stat,
+ struct string_list **missing_parents)
+{
+ int ret = EFAULT;
+ char *parent = NULL;
+ char *end;
+ struct string_list *li;
+
+ ret = stat(ccdirname, parent_stat);
+ if (ret == EOK) {
+ if ( !S_ISDIR(parent_stat->st_mode) ) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "[%s] is not a directory.\n", ccdirname);
+ return EINVAL;
+ }
+ return EOK;
+ } else {
+ if (errno != ENOENT) {
+ ret = errno;
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "stat for [%s] failed: [%d][%s].\n", ccdirname, ret,
+ strerror(ret));
+ return ret;
+ }
+ }
+
+ li = talloc_zero(mem_ctx, struct string_list);
+ if (li == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "talloc_zero failed.\n");
+ return ENOMEM;
+ }
+
+ li->s = talloc_strdup(li, ccdirname);
+ if (li->s == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "talloc_strdup failed.\n");
+ return ENOMEM;
+ }
+
+ DLIST_ADD(*missing_parents, li);
+
+ parent = talloc_strdup(mem_ctx, ccdirname);
+ if (parent == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "talloc_strdup failed.\n");
+ return ENOMEM;
+ }
+
+ /* We'll remove all trailing slashes from the back so that
+ * we only pass /some/path to find_ccdir_parent_data, not
+ * /some/path */
+ do {
+ end = strrchr(parent, '/');
+ if (end == NULL || end == parent) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "Cannot find parent directory of [%s], / is not allowed.\n",
+ ccdirname);
+ ret = EINVAL;
+ goto done;
+ }
+ *end = '\0';
+ } while (*(end+1) == '\0');
+
+ ret = find_ccdir_parent_data(mem_ctx, parent, parent_stat, missing_parents);
+
+done:
+ talloc_free(parent);
+ return ret;
+}
+
+static errno_t check_parent_stat(struct stat *parent_stat, uid_t uid)
+{
+ if (parent_stat->st_uid != 0 && parent_stat->st_uid != uid) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Private directory can only be created below a directory "
+ "belonging to root or to [%"SPRIuid"].\n", uid);
+ return EINVAL;
+ }
+
+ if (parent_stat->st_uid == uid) {
+ if (!(parent_stat->st_mode & S_IXUSR)) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Parent directory does not have the search bit set for "
+ "the owner.\n");
+ return EINVAL;
+ }
+ } else {
+ if (!(parent_stat->st_mode & S_IXOTH)) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Parent directory does not have the search bit set for "
+ "others.\n");
+ return EINVAL;
+ }
+ }
+
+ return EOK;
+}
+
+static errno_t create_ccache_dir(const char *ccdirname, uid_t uid, gid_t gid)
+{
+ int ret = EFAULT;
+ struct stat parent_stat;
+ struct string_list *missing_parents = NULL;
+ struct string_list *li = NULL;
+ mode_t old_umask;
+ mode_t new_dir_mode;
+ TALLOC_CTX *tmp_ctx = NULL;
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "talloc_new failed.\n");
+ return ENOMEM;
+ }
+
+ if (*ccdirname != '/') {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "Only absolute paths are allowed, not [%s] .\n", ccdirname);
+ ret = EINVAL;
+ goto done;
+ }
+
+ ret = find_ccdir_parent_data(tmp_ctx, ccdirname, &parent_stat,
+ &missing_parents);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "find_ccdir_parent_data failed.\n");
+ goto done;
+ }
+
+ ret = check_parent_stat(&parent_stat, uid);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_FATAL_FAILURE,
+ "Check the ownership and permissions of krb5_ccachedir: [%s].\n",
+ ccdirname);
+ goto done;
+ }
+
+ DLIST_FOR_EACH(li, missing_parents) {
+ DEBUG(SSSDBG_TRACE_INTERNAL,
+ "Creating directory [%s].\n", li->s);
+ new_dir_mode = 0700;
+
+ old_umask = umask(0000);
+ ret = mkdir(li->s, new_dir_mode);
+ umask(old_umask);
+ if (ret != EOK) {
+ ret = errno;
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "mkdir [%s] failed: [%d][%s].\n", li->s, ret,
+ strerror(ret));
+ goto done;
+ }
+ ret = chown(li->s, uid, gid);
+ if (ret != EOK) {
+ ret = errno;
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "chown failed [%d][%s].\n", ret, strerror(ret));
+ goto done;
+ }
+ }
+
+ ret = EOK;
+
+done:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+errno_t sss_krb5_precreate_ccache(const char *ccname, uid_t uid, gid_t gid)
+{
+ TALLOC_CTX *tmp_ctx = NULL;
+ const char *filename;
+ char *ccdirname;
+ char *end;
+ errno_t ret;
+
+ if (ccname[0] == '/') {
+ filename = ccname;
+ } else if (strncmp(ccname, "FILE:", 5) == 0) {
+ filename = ccname + 5;
+ } else if (strncmp(ccname, "DIR:", 4) == 0) {
+ filename = ccname + 4;
+ } else {
+ /* only FILE and DIR types need precreation so far, we ignore any
+ * other type */
+ return EOK;
+ }
+
+ tmp_ctx = talloc_new(NULL);
+ if (!tmp_ctx) return ENOMEM;
+
+ ccdirname = talloc_strdup(tmp_ctx, filename);
+ if (ccdirname == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ /* We'll remove all trailing slashes from the back so that
+ * we only pass /some/path to find_ccdir_parent_data, not
+ * /some/path/ */
+ do {
+ end = strrchr(ccdirname, '/');
+ if (end == NULL || end == ccdirname) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Cannot find parent directory of [%s], "
+ "/ is not allowed.\n", ccdirname);
+ ret = EINVAL;
+ goto done;
+ }
+ *end = '\0';
+ } while (*(end+1) == '\0');
+
+ ret = create_ccache_dir(ccdirname, uid, gid);
+done:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+struct sss_krb5_ccache {
+ struct sss_creds *creds;
+ krb5_context context;
+ krb5_ccache ccache;
+};
+
+static int sss_free_krb5_ccache(void *mem)
+{
+ struct sss_krb5_ccache *cc = talloc_get_type(mem, struct sss_krb5_ccache);
+
+ if (cc->ccache) {
+ krb5_cc_close(cc->context, cc->ccache);
+ }
+ krb5_free_context(cc->context);
+ restore_creds(cc->creds);
+ return 0;
+}
+
+static errno_t sss_open_ccache_as_user(TALLOC_CTX *mem_ctx,
+ const char *ccname,
+ uid_t uid, gid_t gid,
+ struct sss_krb5_ccache **ccache)
+{
+ struct sss_krb5_ccache *cc;
+ krb5_error_code kerr;
+ errno_t ret;
+
+ cc = talloc_zero(mem_ctx, struct sss_krb5_ccache);
+ if (!cc) {
+ return ENOMEM;
+ }
+ talloc_set_destructor((TALLOC_CTX *)cc, sss_free_krb5_ccache);
+
+ ret = switch_creds(cc, uid, gid, 0, NULL, &cc->creds);
+ if (ret) {
+ goto done;
+ }
+
+ kerr = sss_krb5_init_context(&cc->context);
+ if (kerr) {
+ ret = EIO;
+ goto done;
+ }
+
+ kerr = krb5_cc_resolve(cc->context, ccname, &cc->ccache);
+ if (kerr == KRB5_FCC_NOFILE || cc->ccache == NULL) {
+ DEBUG(SSSDBG_TRACE_FUNC, "ccache %s is missing or empty\n", ccname);
+ ret = ERR_NOT_FOUND;
+ goto done;
+ } else if (kerr != 0) {
+ KRB5_DEBUG(SSSDBG_OP_FAILURE, cc->context, kerr);
+ DEBUG(SSSDBG_CRIT_FAILURE, "krb5_cc_resolve failed.\n");
+ ret = ERR_INTERNAL;
+ goto done;
+ }
+
+ ret = EOK;
+
+done:
+ if (ret) {
+ talloc_free(cc);
+ } else {
+ *ccache = cc;
+ }
+ return ret;
+}
+
+static errno_t sss_destroy_ccache(struct sss_krb5_ccache *cc)
+{
+ krb5_error_code kerr;
+ errno_t ret;
+
+ kerr = krb5_cc_destroy(cc->context, cc->ccache);
+ if (kerr) {
+ KRB5_DEBUG(SSSDBG_OP_FAILURE, cc->context, kerr);
+ DEBUG(SSSDBG_CRIT_FAILURE, "krb5_cc_destroy failed.\n");
+ ret = EIO;
+ } else {
+ ret = EOK;
+ }
+
+ /* krb5_cc_destroy frees cc->ccache in all events */
+ cc->ccache = NULL;
+
+ return ret;
+}
+
+errno_t sss_krb5_cc_destroy(const char *ccname, uid_t uid, gid_t gid)
+{
+ struct sss_krb5_ccache *cc = NULL;
+ TALLOC_CTX *tmp_ctx;
+ errno_t ret;
+
+ if (ccname == NULL) {
+ /* nothing to remove */
+ return EOK;
+ }
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n");
+ return ENOMEM;
+ }
+
+ ret = sss_open_ccache_as_user(tmp_ctx, ccname, uid, gid, &cc);
+ if (ret) {
+ goto done;
+ }
+
+ ret = sss_destroy_ccache(cc);
+
+done:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+/* This function is called only as a way to validate that we have the
+ * right cache */
+errno_t sss_krb5_check_ccache_princ(krb5_context kctx,
+ const char *ccname,
+ krb5_principal user_princ)
+{
+ krb5_ccache kcc = NULL;
+ krb5_principal ccprinc = NULL;
+ krb5_error_code kerr;
+ const char *cc_type;
+ errno_t ret;
+
+ kerr = krb5_cc_resolve(kctx, ccname, &kcc);
+ if (kerr) {
+ ret = ERR_INTERNAL;
+ goto done;
+ }
+
+ cc_type = krb5_cc_get_type(kctx, kcc);
+
+ kerr = krb5_cc_get_principal(kctx, kcc, &ccprinc);
+ if (kerr != 0) {
+ KRB5_DEBUG(SSSDBG_OP_FAILURE, kctx, kerr);
+ DEBUG(SSSDBG_CRIT_FAILURE, "krb5_cc_get_principal failed.\n");
+ }
+
+ if (ccprinc) {
+ if (krb5_principal_compare(kctx, user_princ, ccprinc) == TRUE) {
+ /* found in the primary ccache */
+ ret = EOK;
+ goto done;
+ }
+ }
+
+#ifdef HAVE_KRB5_CC_COLLECTION
+
+ if (krb5_cc_support_switch(kctx, cc_type)) {
+
+ krb5_cc_close(kctx, kcc);
+ kcc = NULL;
+
+ kerr = krb5_cc_set_default_name(kctx, ccname);
+ if (kerr != 0) {
+ KRB5_DEBUG(SSSDBG_MINOR_FAILURE, kctx, kerr);
+ /* try to continue despite failure */
+ }
+
+ kerr = krb5_cc_cache_match(kctx, user_princ, &kcc);
+ if (kerr == 0) {
+ ret = EOK;
+ goto done;
+ }
+ KRB5_DEBUG(SSSDBG_TRACE_INTERNAL, kctx, kerr);
+ }
+
+#endif /* HAVE_KRB5_CC_COLLECTION */
+
+ ret = ERR_NOT_FOUND;
+
+done:
+ if (ccprinc) {
+ krb5_free_principal(kctx, ccprinc);
+ }
+ if (kcc) {
+ krb5_cc_close(kctx, kcc);
+ }
+ return ret;
+}
+
+static errno_t sss_low_level_path_check(const char *ccname)
+{
+ const char *filename;
+ struct stat buf;
+ int ret;
+
+ if (ccname[0] == '/') {
+ filename = ccname;
+ } else if (strncmp(ccname, "FILE:", 5) == 0) {
+ filename = ccname + 5;
+ } else if (strncmp(ccname, "DIR:", 4) == 0) {
+ filename = ccname + 4;
+ if (filename[0] == ':') filename += 1;
+ } else {
+ /* only FILE and DIR types need file checks so far, we ignore any
+ * other type */
+ return EOK;
+ }
+
+ ret = stat(filename, &buf);
+ if (ret == -1) return errno;
+ return EOK;
+}
+
+errno_t sss_krb5_cc_verify_ccache(const char *ccname, uid_t uid, gid_t gid,
+ const char *realm, const char *principal)
+{
+ struct sss_krb5_ccache *cc = NULL;
+ TALLOC_CTX *tmp_ctx = NULL;
+ krb5_principal tgt_princ = NULL;
+ krb5_principal princ = NULL;
+ char *tgt_name;
+ krb5_creds mcred = { 0 };
+ krb5_creds cred = { 0 };
+ krb5_error_code kerr;
+ errno_t ret;
+
+ /* first of all verify if the old ccache file/dir exists as we may be
+ * trying to verify if an old ccache exists at all. If no file/dir
+ * exists bail out immediately otherwise a following krb5_cc_resolve()
+ * call may actually create paths and files we do not want to have
+ * around */
+ ret = sss_low_level_path_check(ccname);
+ if (ret) {
+ return ret;
+ }
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new failed.\n");
+ return ENOMEM;
+ }
+
+ ret = sss_open_ccache_as_user(tmp_ctx, ccname, uid, gid, &cc);
+ if (ret) {
+ goto done;
+ }
+
+ tgt_name = talloc_asprintf(tmp_ctx, "krbtgt/%s@%s", realm, realm);
+ if (!tgt_name) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ kerr = krb5_parse_name(cc->context, tgt_name, &tgt_princ);
+ if (kerr) {
+ KRB5_DEBUG(SSSDBG_CRIT_FAILURE, cc->context, kerr);
+ if (kerr == KRB5_PARSE_MALFORMED) ret = EINVAL;
+ else ret = ERR_INTERNAL;
+ goto done;
+ }
+
+ kerr = krb5_parse_name(cc->context, principal, &princ);
+ if (kerr) {
+ KRB5_DEBUG(SSSDBG_CRIT_FAILURE, cc->context, kerr);
+ if (kerr == KRB5_PARSE_MALFORMED) ret = EINVAL;
+ else ret = ERR_INTERNAL;
+ goto done;
+ }
+
+ mcred.client = princ;
+ mcred.server = tgt_princ;
+ /* Type krb5_timestamp is a signed 32-bit integer, so we need to convert the
+ * 64-bit time_t value returned by time(). Just keeping the lower 32 bits
+ * should be enough as Kerberos seems to be planing on making this time
+ * unsigned to avoid the Y2K38 problem.
+ * Please check:
+ * https://web.mit.edu/kerberos/krb5-latest/doc/appdev/y2038.html
+ */
+ mcred.times.endtime = time(NULL) & 0xFFFFFFFF;
+
+ kerr = krb5_cc_retrieve_cred(cc->context, cc->ccache,
+ KRB5_TC_MATCH_TIMES, &mcred, &cred);
+ if (kerr) {
+ if (kerr == KRB5_CC_NOTFOUND || kerr == KRB5_FCC_NOFILE) {
+ DEBUG(SSSDBG_TRACE_INTERNAL, "TGT not found or expired.\n");
+ ret = EINVAL;
+ } else {
+ KRB5_DEBUG(SSSDBG_CRIT_FAILURE, cc->context, kerr);
+ ret = ERR_INTERNAL;
+ }
+ }
+ krb5_free_cred_contents(cc->context, &cred);
+
+done:
+ if (tgt_princ) krb5_free_principal(cc->context, tgt_princ);
+ if (princ) krb5_free_principal(cc->context, princ);
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+errno_t get_ccache_file_data(const char *ccache_file, const char *client_name,
+ struct tgt_times *tgtt)
+{
+ krb5_error_code kerr;
+ krb5_context ctx = NULL;
+ krb5_ccache cc = NULL;
+ krb5_principal client_princ = NULL;
+ krb5_principal server_princ = NULL;
+ char *server_name;
+ krb5_creds mcred;
+ krb5_creds cred;
+ const char *realm_name;
+ int realm_length;
+
+ kerr = sss_krb5_init_context(&ctx);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "sss_krb5_init_context failed.\n");
+ goto done;
+ }
+
+ kerr = krb5_parse_name(ctx, client_name, &client_princ);
+ if (kerr != 0) {
+ KRB5_DEBUG(SSSDBG_OP_FAILURE, ctx, kerr);
+ DEBUG(SSSDBG_CRIT_FAILURE, "krb5_parse_name failed.\n");
+ goto done;
+ }
+
+ sss_krb5_princ_realm(ctx, client_princ, &realm_name, &realm_length);
+ if (realm_length == 0) {
+ kerr = KRB5KRB_ERR_GENERIC;
+ DEBUG(SSSDBG_CRIT_FAILURE, "sss_krb5_princ_realm failed.\n");
+ goto done;
+ }
+
+ server_name = talloc_asprintf(NULL, "krbtgt/%.*s@%.*s",
+ realm_length, realm_name,
+ realm_length, realm_name);
+ if (server_name == NULL) {
+ kerr = KRB5_CC_NOMEM;
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n");
+ goto done;
+ }
+
+ kerr = krb5_parse_name(ctx, server_name, &server_princ);
+ talloc_free(server_name);
+ if (kerr != 0) {
+ KRB5_DEBUG(SSSDBG_OP_FAILURE, ctx, kerr);
+ DEBUG(SSSDBG_CRIT_FAILURE, "krb5_parse_name failed.\n");
+ goto done;
+ }
+
+ kerr = krb5_cc_resolve(ctx, ccache_file, &cc);
+ if (kerr != 0) {
+ KRB5_DEBUG(SSSDBG_OP_FAILURE, ctx, kerr);
+ DEBUG(SSSDBG_CRIT_FAILURE, "krb5_cc_resolve failed.\n");
+ goto done;
+ }
+
+ memset(&mcred, 0, sizeof(mcred));
+ memset(&cred, 0, sizeof(mcred));
+
+ mcred.server = server_princ;
+ mcred.client = client_princ;
+
+ kerr = krb5_cc_retrieve_cred(ctx, cc, 0, &mcred, &cred);
+ if (kerr != 0) {
+ KRB5_DEBUG(SSSDBG_OP_FAILURE, ctx, kerr);
+ DEBUG(SSSDBG_CRIT_FAILURE, "krb5_cc_retrieve_cred failed.\n");
+ goto done;
+ }
+
+ tgtt->authtime = cred.times.authtime;
+ tgtt->starttime = cred.times.starttime;
+ tgtt->endtime = cred.times.endtime;
+ tgtt->renew_till = cred.times.renew_till;
+
+ krb5_free_cred_contents(ctx, &cred);
+
+ kerr = krb5_cc_close(ctx, cc);
+ cc = NULL;
+ if (kerr != 0) {
+ KRB5_DEBUG(SSSDBG_OP_FAILURE, ctx, kerr);
+ DEBUG(SSSDBG_CRIT_FAILURE, "krb5_cc_close failed.\n");
+ goto done;
+ }
+
+ kerr = 0;
+
+done:
+ if (cc != NULL) {
+ krb5_cc_close(ctx, cc);
+ }
+
+ if (client_princ != NULL) {
+ krb5_free_principal(ctx, client_princ);
+ }
+
+ if (server_princ != NULL) {
+ krb5_free_principal(ctx, server_princ);
+ }
+
+ if (ctx != NULL) {
+ krb5_free_context(ctx);
+ }
+
+ if (kerr != 0) {
+ return EIO;
+ }
+
+ return EOK;
+}
+
+errno_t safe_remove_old_ccache_file(const char *old_ccache,
+ const char *new_ccache,
+ uid_t uid, gid_t gid)
+{
+ if ((old_ccache == new_ccache)
+ || (old_ccache && new_ccache
+ && (strcmp(old_ccache, new_ccache) == 0))) {
+ DEBUG(SSSDBG_TRACE_FUNC, "New and old ccache file are the same, "
+ "none will be deleted.\n");
+ return EOK;
+ }
+
+ return sss_krb5_cc_destroy(old_ccache, uid, gid);
+}
+
+krb5_error_code copy_ccache_into_memory(TALLOC_CTX *mem_ctx, krb5_context kctx,
+ const char *ccache_file,
+ char **_mem_name)
+{
+ krb5_error_code kerr;
+ krb5_ccache ccache;
+ krb5_ccache mem_ccache = NULL;
+ char *ccache_name = NULL;
+ krb5_principal princ = NULL;
+ char *mem_name = NULL;
+ char *sep;
+
+ kerr = krb5_cc_resolve(kctx, ccache_file, &ccache);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "error resolving ccache [%s].\n",
+ ccache_file);
+ return kerr;
+ }
+
+ kerr = krb5_cc_get_full_name(kctx, ccache, &ccache_name);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Failed to read name for ccache [%s].\n",
+ ccache_file);
+ goto done;
+ }
+
+ sep = strchr(ccache_name, ':');
+ if (sep == NULL || sep[1] == '\0') {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Ccache name [%s] does not have delimiter[:] .\n", ccache_name);
+ kerr = KRB5KRB_ERR_GENERIC;
+ goto done;
+ }
+
+ if (strncmp(ccache_name, "MEMORY:", sizeof("MEMORY:") -1) == 0) {
+ DEBUG(SSSDBG_TRACE_FUNC, "Ccache [%s] is already memory ccache.\n",
+ ccache_name);
+ *_mem_name = talloc_strdup(mem_ctx, ccache_name);
+ if(*_mem_name == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n");
+ kerr = KRB5KRB_ERR_GENERIC;
+ goto done;
+ }
+ kerr = 0;
+ goto done;
+ }
+ if (strncmp(ccache_name, "FILE:", sizeof("FILE:") -1) == 0) {
+ mem_name = talloc_asprintf(mem_ctx, "MEMORY:%s", sep + 1);
+ if (mem_name == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n");
+ kerr = KRB5KRB_ERR_GENERIC;
+ goto done;
+ }
+ } else {
+ DEBUG(SSSDBG_MINOR_FAILURE, "Unexpected ccache type for ccache [%s], " \
+ "currently only FILE is supported.\n",
+ ccache_name);
+ kerr = KRB5KRB_ERR_GENERIC;
+ goto done;
+ }
+
+ kerr = krb5_cc_resolve(kctx, mem_name, &mem_ccache);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "error resolving ccache [%s].\n", mem_name);
+ goto done;
+ }
+
+ kerr = krb5_cc_get_principal(kctx, ccache, &princ);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "error reading principal from ccache [%s].\n", ccache_name);
+ goto done;
+ }
+
+ kerr = krb5_cc_initialize(kctx, mem_ccache, princ);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Failed to initialize ccache [%s].\n", mem_name);
+ goto done;
+ }
+
+ kerr = krb5_cc_copy_creds(kctx, ccache, mem_ccache);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Failed to copy ccache [%s] to [%s].\n", ccache_name, mem_name);
+ goto done;
+ }
+
+ *_mem_name = mem_name;
+ kerr = 0;
+
+done:
+ if (kerr != 0) {
+ talloc_free(mem_name);
+ }
+
+ krb5_free_string(kctx, ccache_name);
+ krb5_free_principal(kctx, princ);
+
+ if (krb5_cc_close(kctx, ccache) != 0) {
+ DEBUG(SSSDBG_OP_FAILURE, "krb5_cc_close failed.\n");
+ }
+
+ if ((mem_ccache != NULL) && (krb5_cc_close(kctx, mem_ccache) != 0)) {
+ DEBUG(SSSDBG_OP_FAILURE, "krb5_cc_close failed.\n");
+ }
+
+ return kerr;
+}
diff --git a/src/providers/krb5/krb5_ccache.h b/src/providers/krb5/krb5_ccache.h
new file mode 100644
index 0000000..f3928e6
--- /dev/null
+++ b/src/providers/krb5/krb5_ccache.h
@@ -0,0 +1,73 @@
+/*
+ SSSD
+
+ Kerberos 5 Backend Module -- ccache related utilities
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+ Jakub Hrozek <jhrozek@redhat.com>
+
+ Copyright (C) 2014 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 __KRB5_CCACHE_H__
+#define __KRB5_CCACHE_H__
+
+#include "util/util.h"
+
+struct tgt_times {
+ time_t authtime;
+ time_t starttime;
+ time_t endtime;
+ time_t renew_till;
+};
+
+errno_t sss_krb5_precreate_ccache(const char *ccname, uid_t uid, gid_t gid);
+
+errno_t sss_krb5_cc_destroy(const char *ccname, uid_t uid, gid_t gid);
+
+errno_t sss_krb5_check_ccache_princ(krb5_context kctx,
+ const char *ccname,
+ krb5_principal user_princ);
+
+errno_t sss_krb5_cc_verify_ccache(const char *ccname, uid_t uid, gid_t gid,
+ const char *realm, const char *principal);
+
+errno_t get_ccache_file_data(const char *ccache_file, const char *client_name,
+ struct tgt_times *tgtt);
+
+errno_t safe_remove_old_ccache_file(const char *old_ccache,
+ const char *new_ccache,
+ uid_t uid, gid_t gid);
+
+/**
+ * @brief Copy given ccache into a MEMORY ccache
+ *
+ * @param[in] mem_ctx Talloc memory context the new ccache name should be
+ * allocated on
+ * @param[in] kctx Kerberos context
+ * @param[in] ccache_file Name of existing ccache
+ * @param[out] _mem_name Name of the new MEMORY ccache
+ *
+ * In contrast to MEMORY keytabs MEMORY ccaches can and must be removed
+ * explicitly with krb5_cc_destroy() from the memory. Just calling
+ * krb5_cc_close() will keep the MEMORY ccache in memory even if there are no
+ * open handles for the given MEMORY ccache.
+ */
+krb5_error_code copy_ccache_into_memory(TALLOC_CTX *mem_ctx, krb5_context kctx,
+ const char *ccache_file,
+ char **_mem_name);
+#endif /* __KRB5_CCACHE_H__ */
diff --git a/src/providers/krb5/krb5_child.c b/src/providers/krb5/krb5_child.c
new file mode 100644
index 0000000..704f650
--- /dev/null
+++ b/src/providers/krb5/krb5_child.c
@@ -0,0 +1,4247 @@
+/*
+ SSSD
+
+ Kerberos 5 Backend Module -- tgt_req and changepw child
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2009-2010 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 <sys/types.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <popt.h>
+#include <sys/prctl.h>
+
+#include <security/pam_modules.h>
+
+#include "util/util.h"
+#include "util/sss_krb5.h"
+#include "util/user_info_msg.h"
+#include "util/child_common.h"
+#include "util/find_uid.h"
+#include "util/sss_chain_id.h"
+#include "util/sss_ptr_hash.h"
+#include "src/util/util_errors.h"
+#include "providers/backend.h"
+#include "providers/krb5/krb5_auth.h"
+#include "providers/krb5/krb5_utils.h"
+#include "krb5_plugin/idp/idp.h"
+#ifdef BUILD_PASSKEY
+#include "responder/pam/pamsrv_passkey.h"
+#include "krb5_plugin/passkey/passkey.h"
+#endif /* BUILD_PASSKEY */
+#include "sss_cli.h"
+
+#define SSSD_KRB5_CHANGEPW_PRINCIPAL "kadmin/changepw"
+#ifndef BUILD_PASSKEY
+#define SSSD_PASSKEY_QUESTION "passkey"
+#endif /* BUILD_PASSKEY */
+
+typedef krb5_error_code
+(*k5_init_creds_password_fn_t)(krb5_context context, krb5_creds *creds,
+ krb5_principal client, const char *password,
+ krb5_prompter_fct prompter, void *data,
+ krb5_deltat start_time,
+ const char *in_tkt_service,
+ krb5_get_init_creds_opt *k5_gic_options);
+
+enum k5c_fast_opt {
+ K5C_FAST_NEVER,
+ K5C_FAST_TRY,
+ K5C_FAST_DEMAND,
+};
+
+struct cli_opts {
+ char *realm;
+ char *lifetime;
+ char *rtime;
+ char *use_fast_str;
+ char *fast_principal;
+ uint32_t check_pac_flags;
+ bool canonicalize;
+ bool fast_use_anonymous_pkinit;
+};
+
+struct krb5_req {
+ krb5_context ctx;
+ krb5_principal princ;
+ krb5_principal princ_orig;
+ char* name;
+ krb5_creds *creds;
+ bool otp;
+ bool password_prompting;
+ bool pkinit_prompting;
+ char *otp_vendor;
+ char *otp_token_id;
+ char *otp_challenge;
+ krb5_get_init_creds_opt *options;
+ k5_init_creds_password_fn_t krb5_get_init_creds_password;
+
+ struct pam_data *pd;
+
+ char *realm;
+ char *ccname;
+ char *keytab;
+ bool validate;
+ bool posix_domain;
+ bool send_pac;
+ bool use_enterprise_princ;
+ char *fast_ccname;
+
+ const char *upn;
+ uid_t uid;
+ gid_t gid;
+
+ char *old_ccname;
+ bool old_cc_valid;
+ bool old_cc_active;
+ enum k5c_fast_opt fast_val;
+
+ uid_t fast_uid;
+ gid_t fast_gid;
+ struct sss_creds *pcsc_saved_creds;
+
+ struct cli_opts *cli_opts;
+};
+
+static krb5_context krb5_error_ctx;
+#define KRB5_CHILD_DEBUG(level, error) KRB5_DEBUG(level, krb5_error_ctx, error)
+
+static errno_t k5c_attach_otp_info_msg(struct krb5_req *kr);
+static errno_t k5c_attach_oauth2_info_msg(struct krb5_req *kr, struct sss_idp_oauth2 *data);
+#ifdef BUILD_PASSKEY
+static errno_t k5c_attach_passkey_msg(struct krb5_req *kr, struct sss_passkey_challenge *data);
+#endif /* BUILD_PASSKEY */
+static errno_t k5c_attach_keep_alive_msg(struct krb5_req *kr);
+static errno_t k5c_recv_data(struct krb5_req *kr, int fd, uint32_t *offline);
+static errno_t k5c_send_data(struct krb5_req *kr, int fd, errno_t error);
+
+static errno_t k5c_become_user(uid_t uid, gid_t gid, bool is_posix)
+{
+ if (is_posix == false) {
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "Will not drop privileges for a non-POSIX user\n");
+ return EOK;
+ }
+ return become_user(uid, gid);
+}
+
+static krb5_error_code set_lifetime_options(struct cli_opts *cli_opts,
+ krb5_get_init_creds_opt *options)
+{
+ krb5_error_code kerr;
+ krb5_deltat lifetime;
+
+ if (cli_opts->rtime == NULL) {
+ DEBUG(SSSDBG_CONF_SETTINGS,
+ "No specific renewable lifetime requested.\n");
+
+ /* Unset option flag to make sure defaults from krb5.conf are used. */
+ options->flags &= ~(KRB5_GET_INIT_CREDS_OPT_RENEW_LIFE);
+ } else {
+ kerr = krb5_string_to_deltat(cli_opts->rtime, &lifetime);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "krb5_string_to_deltat failed for [%s].\n", cli_opts->rtime);
+ KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
+ return kerr;
+ }
+ DEBUG(SSSDBG_CONF_SETTINGS, "Renewable lifetime is set to [%s]\n",
+ cli_opts->rtime);
+ krb5_get_init_creds_opt_set_renew_life(options, lifetime);
+ }
+
+ if (cli_opts->lifetime == NULL) {
+ DEBUG(SSSDBG_CONF_SETTINGS, "No specific lifetime requested.\n");
+
+ /* Unset option flag to make sure defaults from krb5.conf are used. */
+ options->flags &= ~(KRB5_GET_INIT_CREDS_OPT_TKT_LIFE);
+ } else {
+ kerr = krb5_string_to_deltat(cli_opts->lifetime, &lifetime);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "krb5_string_to_deltat failed for [%s].\n",
+ cli_opts->lifetime);
+ KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
+ return kerr;
+ }
+ DEBUG(SSSDBG_CONF_SETTINGS, "Lifetime is set to [%s]\n",
+ cli_opts->lifetime);
+ krb5_get_init_creds_opt_set_tkt_life(options, lifetime);
+ }
+
+ return 0;
+}
+
+static void set_canonicalize_option(struct cli_opts *cli_opts,
+ krb5_get_init_creds_opt *opts)
+{
+ int canonicalize = 0;
+
+ canonicalize = cli_opts->canonicalize ? 1 : 0;
+ DEBUG(SSSDBG_CONF_SETTINGS, "Canonicalization is set to [%s]\n",
+ cli_opts->canonicalize ? "true" : "false");
+ sss_krb5_get_init_creds_opt_set_canonicalize(opts, canonicalize);
+}
+
+static void set_changepw_options(krb5_get_init_creds_opt *options)
+{
+ sss_krb5_get_init_creds_opt_set_canonicalize(options, 0);
+ krb5_get_init_creds_opt_set_forwardable(options, 0);
+ krb5_get_init_creds_opt_set_proxiable(options, 0);
+ krb5_get_init_creds_opt_set_renew_life(options, 0);
+ krb5_get_init_creds_opt_set_tkt_life(options, 5*60);
+}
+
+static void revert_changepw_options(struct cli_opts *cli_opts,
+ krb5_get_init_creds_opt *options)
+{
+ krb5_error_code kerr;
+
+ set_canonicalize_option(cli_opts, options);
+
+ /* Currently we do not set forwardable and proxiable explicitly, the flags
+ * must be removed so that libkrb5 can take the defaults from krb5.conf */
+ options->flags &= ~(KRB5_GET_INIT_CREDS_OPT_FORWARDABLE);
+ options->flags &= ~(KRB5_GET_INIT_CREDS_OPT_PROXIABLE);
+
+ kerr = set_lifetime_options(cli_opts, options);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_OP_FAILURE, "set_lifetime_options failed.\n");
+ }
+}
+
+
+static errno_t sss_send_pac(krb5_authdata **pac_authdata)
+{
+ struct sss_cli_req_data sss_data;
+ int ret;
+ int errnop;
+
+ sss_data.len = pac_authdata[0]->length;
+ sss_data.data = pac_authdata[0]->contents;
+
+ ret = sss_pac_make_request(SSS_PAC_ADD_PAC_USER, &sss_data,
+ NULL, NULL, &errnop);
+ DEBUG(SSSDBG_TRACE_ALL,
+ "NSS return code [%d], request return code [%d][%s].\n", ret,
+ errnop, sss_strerror(errnop));
+ if (errnop == ERR_CHECK_PAC_FAILED) {
+ return ERR_CHECK_PAC_FAILED;
+ }
+
+ if (ret == NSS_STATUS_UNAVAIL) {
+ DEBUG(SSSDBG_MINOR_FAILURE, "failed to contact PAC responder\n");
+ return EIO;
+ } else if (ret != NSS_STATUS_SUCCESS || errnop != 0) {
+ DEBUG(SSSDBG_OP_FAILURE, "sss_pac_make_request failed [%d][%d].\n",
+ ret, errnop);
+ return EIO;
+ }
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "PAC responder contacted. It might take a bit of time in case the "
+ "cache is not up to date.\n");
+
+ return EOK;
+}
+
+static void sss_krb5_expire_callback_func(krb5_context context, void *data,
+ krb5_timestamp password_expiration,
+ krb5_timestamp account_expiration,
+ krb5_boolean is_last_req)
+{
+ int ret;
+ uint32_t *blob;
+ long exp_time;
+ struct krb5_req *kr = talloc_get_type(data, struct krb5_req);
+
+ if (password_expiration == 0) {
+ return;
+ }
+
+ exp_time = password_expiration - time(NULL);
+ if (exp_time < 0 || exp_time > UINT32_MAX) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Time to expire out of range.\n");
+ return;
+ }
+ DEBUG(SSSDBG_TRACE_INTERNAL, "exp_time: [%ld]\n", exp_time);
+
+ blob = talloc_array(kr->pd, uint32_t, 2);
+ if (blob == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_array failed.\n");
+ return;
+ }
+
+ blob[0] = SSS_PAM_USER_INFO_EXPIRE_WARN;
+ blob[1] = (uint32_t) exp_time;
+
+ ret = pam_add_response(kr->pd, SSS_PAM_USER_INFO, 2 * sizeof(uint32_t),
+ (uint8_t *) blob);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n");
+ }
+
+ return;
+}
+
+#ifdef HAVE_KRB5_GET_INIT_CREDS_OPT_SET_RESPONDER
+/*
+ * TODO: These features generally would requires a significant refactoring
+ * of SSSD and MIT krb5 doesn't support them anyway. They are listed here
+ * simply as a reminder of things that might become future feature potential.
+ *
+ * 1. tokeninfo selection
+ * 2. challenge
+ * 3. discreet token/PIN prompting
+ * 4. interactive OTP format correction
+ * 5. nextOTP
+ *
+ */
+typedef int (*checker)(int c);
+
+static inline checker pick_checker(int format)
+{
+ switch (format) {
+ case KRB5_RESPONDER_OTP_FORMAT_DECIMAL:
+ return isdigit;
+ case KRB5_RESPONDER_OTP_FORMAT_HEXADECIMAL:
+ return isxdigit;
+ case KRB5_RESPONDER_OTP_FORMAT_ALPHANUMERIC:
+ return isalnum;
+ }
+
+ return NULL;
+}
+
+static int token_pin_destructor(char *mem)
+{
+ return sss_erase_talloc_mem_securely(mem);
+}
+
+static krb5_error_code tokeninfo_matches_2fa(TALLOC_CTX *mem_ctx,
+ const krb5_responder_otp_tokeninfo *ti,
+ const char *fa1, size_t fa1_len,
+ const char *fa2, size_t fa2_len,
+ char **out_token, char **out_pin)
+{
+ char *token = NULL, *pin = NULL;
+ checker check = NULL;
+ int i;
+
+ if (ti->flags & KRB5_RESPONDER_OTP_FLAGS_NEXTOTP) {
+ return ENOTSUP;
+ }
+
+ if (ti->challenge != NULL) {
+ return ENOTSUP;
+ }
+
+ /* This is a non-sensical value. */
+ if (ti->length == 0) {
+ return EPROTO;
+ }
+
+ if (ti->flags & KRB5_RESPONDER_OTP_FLAGS_COLLECT_TOKEN) {
+ if (ti->length > 0 && ti->length != fa2_len) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Expected [%d] and given [%zu] token size "
+ "do not match.\n", ti->length, fa2_len);
+ return EMSGSIZE;
+ }
+
+ if (ti->flags & KRB5_RESPONDER_OTP_FLAGS_COLLECT_PIN) {
+ if (ti->flags & KRB5_RESPONDER_OTP_FLAGS_SEPARATE_PIN) {
+
+ pin = talloc_strndup(mem_ctx, fa1, fa1_len);
+ if (pin == NULL) {
+ talloc_free(token);
+ return ENOMEM;
+ }
+ talloc_set_destructor(pin, token_pin_destructor);
+
+ token = talloc_strndup(mem_ctx, fa2, fa2_len);
+ if (token == NULL) {
+ return ENOMEM;
+ }
+ talloc_set_destructor(token, token_pin_destructor);
+
+ check = pick_checker(ti->format);
+ }
+ } else {
+ token = talloc_asprintf(mem_ctx, "%s%s", fa1, fa2);
+ if (token == NULL) {
+ return ENOMEM;
+ }
+ talloc_set_destructor(token, token_pin_destructor);
+
+ check = pick_checker(ti->format);
+ }
+ } else {
+ /* Assuming PIN only required */
+ pin = talloc_strndup(mem_ctx, fa1, fa1_len);
+ if (pin == NULL) {
+ return ENOMEM;
+ }
+ talloc_set_destructor(pin, token_pin_destructor);
+ }
+
+ /* If check is set, we need to verify the contents of the token. */
+ for (i = 0; check != NULL && token[i] != '\0'; i++) {
+ if (!check(token[i])) {
+ talloc_free(token);
+ talloc_free(pin);
+ return EBADMSG;
+ }
+ }
+
+ *out_token = token;
+ *out_pin = pin;
+ return 0;
+}
+
+static krb5_error_code tokeninfo_matches_pwd(TALLOC_CTX *mem_ctx,
+ const krb5_responder_otp_tokeninfo *ti,
+ const char *pwd, size_t len,
+ char **out_token, char **out_pin)
+{
+ char *token = NULL, *pin = NULL;
+ checker check = NULL;
+ int i;
+
+
+ if (ti->flags & KRB5_RESPONDER_OTP_FLAGS_NEXTOTP) {
+ return ENOTSUP;
+ }
+
+ if (ti->challenge != NULL) {
+ return ENOTSUP;
+ }
+
+ /* This is a non-sensical value. */
+ if (ti->length == 0) {
+ return EPROTO;
+ }
+
+ if (ti->flags & KRB5_RESPONDER_OTP_FLAGS_COLLECT_TOKEN) {
+ /* ASSUMPTION: authtok has one of the following formats:
+ * 1. TokenValue
+ * 2. PIN+TokenValue
+ */
+ token = talloc_strndup(mem_ctx, pwd, len);
+ if (token == NULL) {
+ return ENOMEM;
+ }
+ talloc_set_destructor(token, token_pin_destructor);
+
+ if (ti->flags & KRB5_RESPONDER_OTP_FLAGS_COLLECT_PIN) {
+ /* If the server desires a separate PIN, we will split it.
+ * ASSUMPTION: Format of authtok is PIN+TokenValue. */
+ if (ti->flags & KRB5_RESPONDER_OTP_FLAGS_SEPARATE_PIN) {
+ if (ti->length < 1) {
+ talloc_free(token);
+ return ENOTSUP;
+ }
+
+ if (ti->length >= len) {
+ talloc_free(token);
+ return EMSGSIZE;
+ }
+
+ /* Copy the PIN from the front of the value. */
+ pin = talloc_strndup(NULL, pwd, len - ti->length);
+ if (pin == NULL) {
+ talloc_free(token);
+ return ENOMEM;
+ }
+ talloc_set_destructor(pin, token_pin_destructor);
+
+ /* Remove the PIN from the front of the token value. */
+ memmove(token, token + len - ti->length, ti->length + 1);
+
+ check = pick_checker(ti->format);
+ } else {
+ if (ti->length > 0 && ti->length > len) {
+ talloc_free(token);
+ return EMSGSIZE;
+ }
+ }
+ } else {
+ if (ti->length > 0 && ti->length != len) {
+ talloc_free(token);
+ return EMSGSIZE;
+ }
+
+ check = pick_checker(ti->format);
+ }
+ } else {
+ pin = talloc_strndup(mem_ctx, pwd, len);
+ if (pin == NULL) {
+ return ENOMEM;
+ }
+ talloc_set_destructor(pin, token_pin_destructor);
+ }
+
+ /* If check is set, we need to verify the contents of the token. */
+ for (i = 0; check != NULL && token[i] != '\0'; i++) {
+ if (!check(token[i])) {
+ talloc_free(token);
+ talloc_free(pin);
+ return EBADMSG;
+ }
+ }
+
+ *out_token = token;
+ *out_pin = pin;
+ return 0;
+}
+
+static krb5_error_code tokeninfo_matches(TALLOC_CTX *mem_ctx,
+ const krb5_responder_otp_tokeninfo *ti,
+ struct sss_auth_token *auth_tok,
+ char **out_token, char **out_pin)
+{
+ int ret;
+ const char *pwd;
+ size_t len;
+ const char *fa2;
+ size_t fa2_len;
+
+ switch (sss_authtok_get_type(auth_tok)) {
+ case SSS_AUTHTOK_TYPE_PASSWORD:
+ ret = sss_authtok_get_password(auth_tok, &pwd, &len);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "sss_authtok_get_password failed.\n");
+ return ret;
+ }
+
+ return tokeninfo_matches_pwd(mem_ctx, ti, pwd, len, out_token, out_pin);
+ break;
+ case SSS_AUTHTOK_TYPE_2FA_SINGLE:
+ ret = sss_authtok_get_2fa_single(auth_tok, &pwd, &len);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "sss_authtok_get_password failed.\n");
+ return ret;
+ }
+
+ return tokeninfo_matches_pwd(mem_ctx, ti, pwd, len, out_token, out_pin);
+ break;
+ case SSS_AUTHTOK_TYPE_2FA:
+ ret = sss_authtok_get_2fa(auth_tok, &pwd, &len, &fa2, &fa2_len);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "sss_authtok_get_2fa failed.\n");
+ return ret;
+ }
+
+ return tokeninfo_matches_2fa(mem_ctx, ti, pwd, len, fa2, fa2_len,
+ out_token, out_pin);
+ break;
+ default:
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Unsupported authtok type %d\n", sss_authtok_get_type(auth_tok));
+ }
+
+ return EINVAL;
+}
+
+static krb5_error_code answer_otp(krb5_context ctx,
+ struct krb5_req *kr,
+ krb5_responder_context rctx)
+{
+ krb5_responder_otp_challenge *chl;
+ char *token = NULL, *pin = NULL;
+ krb5_error_code ret;
+ size_t i;
+
+ ret = krb5_responder_otp_get_challenge(ctx, rctx, &chl);
+ if (ret != EOK || chl == NULL) {
+ /* Either an error, or nothing to do. */
+ return ret;
+ }
+
+ if (chl->tokeninfo == NULL || chl->tokeninfo[0] == NULL) {
+ /* No tokeninfos? Absurd! */
+ ret = EINVAL;
+ goto done;
+ }
+
+ kr->otp = true;
+
+ if (kr->pd->cmd == SSS_PAM_PREAUTH) {
+ for (i = 0; chl->tokeninfo[i] != NULL; i++) {
+ DEBUG(SSSDBG_TRACE_ALL, "[%zu] Vendor [%s].\n",
+ i, chl->tokeninfo[i]->vendor);
+ DEBUG(SSSDBG_TRACE_ALL, "[%zu] Token-ID [%s].\n",
+ i, chl->tokeninfo[i]->token_id);
+ DEBUG(SSSDBG_TRACE_ALL, "[%zu] Challenge [%s].\n",
+ i, chl->tokeninfo[i]->challenge);
+ DEBUG(SSSDBG_TRACE_ALL, "[%zu] Flags [%d].\n",
+ i, chl->tokeninfo[i]->flags);
+ }
+
+ if (chl->tokeninfo[0]->vendor != NULL) {
+ kr->otp_vendor = talloc_strdup(kr, chl->tokeninfo[0]->vendor);
+ }
+ if (chl->tokeninfo[0]->token_id != NULL) {
+ kr->otp_token_id = talloc_strdup(kr, chl->tokeninfo[0]->token_id);
+ }
+ if (chl->tokeninfo[0]->challenge != NULL) {
+ kr->otp_challenge = talloc_strdup(kr, chl->tokeninfo[0]->challenge);
+ }
+ /* Allocation errors are ignored on purpose */
+
+ DEBUG(SSSDBG_TRACE_INTERNAL, "Exit answer_otp during pre-auth.\n");
+ return EAGAIN;
+ }
+
+ /* Find the first supported tokeninfo which matches our authtoken. */
+ for (i = 0; chl->tokeninfo[i] != NULL; i++) {
+ ret = tokeninfo_matches(kr, chl->tokeninfo[i], kr->pd->authtok,
+ &token, &pin);
+ if (ret == EOK) {
+ break;
+ }
+
+ switch (ret) {
+ case EBADMSG:
+ case EMSGSIZE:
+ case ENOTSUP:
+ case EPROTO:
+ break;
+ default:
+ goto done;
+ }
+ }
+ if (chl->tokeninfo[i] == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "No tokeninfos found which match our credentials.\n");
+ ret = EOK;
+ goto done;
+ }
+
+ if (chl->tokeninfo[i]->flags & KRB5_RESPONDER_OTP_FLAGS_COLLECT_TOKEN) {
+ /* Don't let SSSD cache the OTP authtoken since it is single-use. */
+ ret = pam_add_response(kr->pd, SSS_OTP, 0, NULL);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n");
+ goto done;
+ }
+ }
+
+ /* Respond with the appropriate answer. */
+ ret = krb5_responder_otp_set_answer(ctx, rctx, i, token, pin);
+done:
+ talloc_free(token);
+ talloc_free(pin);
+ krb5_responder_otp_challenge_free(ctx, rctx, chl);
+ return ret;
+}
+
+static bool pkinit_identity_matches(const char *identity,
+ const char *token_name,
+ const char *module_name)
+{
+ TALLOC_CTX *tmp_ctx = NULL;
+ char *str;
+ bool res = false;
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new failed.\n");
+ return false;
+ }
+
+ str = talloc_asprintf(tmp_ctx, "module_name=%s", module_name);
+ if (str == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n");
+ goto done;
+ }
+
+ if (strstr(identity, str) == NULL) {
+ DEBUG(SSSDBG_TRACE_ALL, "Identity [%s] does not contain [%s].\n",
+ identity, str);
+ goto done;
+ }
+ DEBUG(SSSDBG_TRACE_ALL, "Found [%s] in identity [%s].\n", str, identity);
+
+ str = talloc_asprintf(tmp_ctx, "token=%s", token_name);
+ if (str == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n");
+ goto done;
+ }
+
+ if (strstr(identity, str) == NULL) {
+ DEBUG(SSSDBG_TRACE_ALL, "Identity [%s] does not contain [%s].\n",
+ identity, str);
+ goto done;
+ }
+ DEBUG(SSSDBG_TRACE_ALL, "Found [%s] in identity [%s].\n", str, identity);
+
+ res = true;
+
+done:
+ talloc_free(tmp_ctx);
+
+ return res;
+}
+
+static krb5_error_code answer_pkinit(krb5_context ctx,
+ struct krb5_req *kr,
+ krb5_responder_context rctx)
+{
+ krb5_error_code kerr;
+ const char *pin = NULL;
+ const char *token_name = NULL;
+ const char *module_name = NULL;
+ krb5_responder_pkinit_challenge *chl = NULL;
+ size_t c;
+
+ kerr = krb5_responder_pkinit_get_challenge(ctx, rctx, &chl);
+ if (kerr != EOK || chl == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "krb5_responder_pkinit_get_challenge failed.\n");
+ return kerr;
+ }
+ if (chl->identities == NULL || chl->identities[0] == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "No identities for pkinit!\n");
+ kerr = EINVAL;
+ goto done;
+ }
+
+ for (c = 0; chl->identities[c] != NULL; c++) {
+ DEBUG(SSSDBG_TRACE_ALL, "[%zu] Identity [%s] flags [%"PRId32"].\n",
+ c, chl->identities[c]->identity,
+ chl->identities[c]->token_flags);
+ }
+
+ DEBUG(SSSDBG_TRACE_ALL, "Setting pkinit_prompting.\n");
+ kr->pkinit_prompting = true;
+
+ if (kr->pd->cmd == SSS_PAM_AUTHENTICATE
+ && (sss_authtok_get_type(kr->pd->authtok)
+ == SSS_AUTHTOK_TYPE_SC_PIN
+ || sss_authtok_get_type(kr->pd->authtok)
+ == SSS_AUTHTOK_TYPE_SC_KEYPAD)) {
+ kerr = sss_authtok_get_sc(kr->pd->authtok, &pin, NULL,
+ &token_name, NULL,
+ &module_name, NULL,
+ NULL, NULL, NULL, NULL);
+ if (kerr != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "sss_authtok_get_sc failed.\n");
+ goto done;
+ }
+
+ for (c = 0; chl->identities[c] != NULL; c++) {
+ if (chl->identities[c]->identity != NULL
+ && pkinit_identity_matches(chl->identities[c]->identity,
+ token_name, module_name)) {
+ break;
+ }
+ }
+
+ if (chl->identities[c] == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "No matching identity for [%s][%s] found in pkinit challenge.\n",
+ token_name, module_name);
+ kerr = EINVAL;
+ goto done;
+ }
+
+ kerr = krb5_responder_pkinit_set_answer(ctx, rctx,
+ chl->identities[c]->identity,
+ pin);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "krb5_responder_set_answer failed.\n");
+ }
+
+ goto done;
+ }
+
+ kerr = EOK;
+
+done:
+ krb5_responder_pkinit_challenge_free(ctx, rctx, chl);
+
+ return kerr;
+}
+
+static errno_t krb5_req_update(struct krb5_req *dest, struct krb5_req *src)
+{
+ /* Check request validity. This should never happen, but it is better to
+ * be little paranoid. */
+ if (strcmp(dest->ccname, src->ccname) != 0) {
+ return EINVAL;
+ }
+
+ if (strcmp(dest->upn, src->upn) != 0) {
+ return EINVAL;
+ }
+
+ if (dest->uid != src->uid || dest->gid != src->gid) {
+ return EINVAL;
+ }
+
+ /* Update PAM data. */
+ talloc_free(dest->pd);
+ dest->pd = talloc_steal(dest, src->pd);
+
+ return EOK;
+}
+
+static krb5_error_code idp_oauth2_preauth(struct krb5_req *kr,
+ struct sss_idp_oauth2 *oauth2)
+{
+ struct krb5_req *tmpkr = NULL;
+ uint32_t offline;
+ errno_t ret;
+
+ if (oauth2->verification_uri == NULL || oauth2->user_code == NULL) {
+ ret = EINVAL;
+ goto done;
+ }
+
+ /* Challenge was presented. We need to continue the authentication
+ * with this exact child process in order to maintain internal Kerberos
+ * state so we are able to respond to this particular challenge. */
+
+ ret = k5c_attach_oauth2_info_msg(kr, oauth2);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "k5c_attach_oauth2_info_msg failed.\n");
+ return ret;
+ }
+
+ ret = k5c_attach_keep_alive_msg(kr);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "k5c_attach_keep_alive_msg failed.\n");
+ return ret;
+ }
+
+ tmpkr = talloc_zero(NULL, struct krb5_req);
+ if (tmpkr == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ /* Send reply and wait for next step. */
+ ret = k5c_send_data(kr, STDOUT_FILENO, ret);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Failed to send reply\n");
+ }
+
+ ret = k5c_recv_data(tmpkr, STDIN_FILENO, &offline);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ ret = krb5_req_update(kr, tmpkr);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unable to update krb request [%d]: %s\n",
+ ret, sss_strerror(ret));
+ goto done;
+ }
+
+done:
+ talloc_free(tmpkr);
+ return ret;
+}
+
+static krb5_error_code answer_idp_oauth2(krb5_context kctx,
+ struct krb5_req *kr,
+ krb5_responder_context rctx)
+{
+ enum sss_authtok_type type;
+ struct sss_idp_oauth2 *data;
+ const char *challenge;
+ const char *token;
+ size_t token_len;
+ krb5_error_code kerr;
+
+ challenge = krb5_responder_get_challenge(kctx, rctx,
+ SSSD_IDP_OAUTH2_QUESTION);
+ if (challenge == NULL) {
+ return ENOENT;
+ }
+
+ data = sss_idp_oauth2_decode_challenge(challenge);
+ if (data == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "Unable to parse OAuth2 challenge\n");
+ return EINVAL;
+ }
+
+ if (kr->pd->cmd == SSS_PAM_PREAUTH) {
+ kerr = idp_oauth2_preauth(kr, data);
+ if (kerr != EOK) {
+ goto done;
+ }
+ }
+
+ if (kr->pd->cmd != SSS_PAM_AUTHENTICATE) {
+ DEBUG(SSSDBG_OP_FAILURE, "Unexpected command [%d]\n", kr->pd->cmd);
+ kerr = EINVAL;
+ goto done;
+ }
+
+ type = sss_authtok_get_type(kr->pd->authtok);
+ if (type != SSS_AUTHTOK_TYPE_OAUTH2) {
+ DEBUG(SSSDBG_OP_FAILURE, "Unexpected authentication token type [%s]\n",
+ sss_authtok_type_to_str(type));
+ kerr = EINVAL;
+ goto done;
+ }
+
+ kerr = sss_authtok_get_oauth2(kr->pd->authtok, &token, &token_len);
+ if (kerr != EOK) {
+ goto done;
+ }
+
+ if (strlen(data->user_code) != token_len && strcmp(data->user_code, token) != 0) {
+ DEBUG(SSSDBG_OP_FAILURE, "User code do not match!\n");
+ kerr = EINVAL;
+ goto done;
+ }
+
+ /* Don't let SSSD cache the authtoken since it is single-use. */
+ kerr = pam_add_response(kr->pd, SSS_OTP, 0, NULL);
+ if (kerr != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n");
+ goto done;
+ }
+
+ /* The answer is arbitrary but we need to provide some since krb5 lib
+ * expects it. So we choose the pin. */
+ kerr = krb5_responder_set_answer(kctx, rctx, SSSD_IDP_OAUTH2_QUESTION,
+ data->user_code);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_OP_FAILURE, "Unable to set IdP answer [%d]\n", kerr);
+ goto done;
+ }
+
+ kerr = EOK;
+
+done:
+ sss_idp_oauth2_free(data);
+
+ return kerr;
+}
+
+#ifdef BUILD_PASSKEY
+static errno_t k5c_attach_passkey_msg(struct krb5_req *kr,
+ struct sss_passkey_challenge *data)
+{
+ uint8_t *msg;
+ const char *user_verification;
+ int i;
+ size_t msg_len = 0;
+ size_t domain_len = 0;
+ size_t crypto_len = 0;
+ size_t num_creds = 0;
+ size_t cred_len = 0;
+ size_t verification_len = 0;
+ size_t idx = 0;
+ errno_t ret;
+
+ if (data->domain == NULL || data->credential_id_list == NULL
+ || data->cryptographic_challenge == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Empty passkey domain, credential id list, or cryptographic "
+ "challenge\n");
+ return EINVAL;
+ }
+
+ user_verification = data->user_verification == 0 ? "false" : "true";
+ verification_len = strlen(user_verification) + 1;
+ msg_len += verification_len;
+
+ crypto_len = strlen(data->cryptographic_challenge) + 1;
+ msg_len += crypto_len;
+
+ domain_len = strlen(data->domain) + 1;
+ msg_len += domain_len;
+
+ /* credentials list size */
+ msg_len += sizeof(uint32_t);
+
+ for (i = 0; data->credential_id_list[i] != NULL; i++) {
+ msg_len += (strlen(data->credential_id_list[i]) + 1);
+ }
+ num_creds = i;
+
+ msg = talloc_zero_size(kr, msg_len);
+ if (msg == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_size failed.\n");
+ return ENOMEM;
+ }
+
+ /* To avoid sending extraneous data back and forth to pam_sss,
+ * (and reduce boilerplate memcpy code) only the user
+ * verification and cryptographic challenge are retrieved in pam_sss.
+ *
+ * The remaining passkey data (domain, creds list, num_creds)
+ * is sent to the PAM responder and stored in a hash table. The
+ * challenge is used as a unique key of the hash table. The pam_sss
+ * reply includes the challenge which is used to lookup the passkey
+ * data in the PAM responder, ensuring it matches the originating
+ * request */
+ memcpy(msg + idx, user_verification, verification_len);
+ idx += verification_len;
+
+ memcpy(msg + idx, data->cryptographic_challenge, crypto_len);
+ idx += crypto_len;
+
+ memcpy(msg + idx, data->domain, domain_len);
+ idx += domain_len;
+
+ SAFEALIGN_COPY_UINT32(msg + idx, &num_creds, &idx);
+
+ for (i = 0; data->credential_id_list[i] != NULL; i++) {
+ cred_len = strlen(data->credential_id_list[i]) + 1;
+ memcpy(msg + idx, data->credential_id_list[i], cred_len);
+ idx += cred_len;
+ }
+
+ ret = pam_add_response(kr->pd, SSS_PAM_PASSKEY_KRB_INFO, msg_len, msg);
+ talloc_zfree(msg);
+
+ return ret;
+}
+
+static krb5_error_code passkey_preauth(struct krb5_req *kr,
+ struct sss_passkey_challenge *passkey)
+{
+ struct krb5_req *tmpkr = NULL;
+ uint32_t offline;
+ errno_t ret;
+
+ if (passkey->domain == NULL || passkey->credential_id_list == NULL
+ || passkey->cryptographic_challenge == NULL) {
+ ret = EINVAL;
+ goto done;
+ }
+
+ ret = k5c_attach_passkey_msg(kr, passkey);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "k5c_attach_passkey_info_msg failed.\n");
+ return ret;
+ }
+
+ /* Challenge was presented. We need to continue the authentication
+ * with this exact child process in order to maintain internal Kerberos
+ * state so we are able to respond to this particular challenge. */
+ ret = k5c_attach_keep_alive_msg(kr);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "k5c_attach_keep_alive_msg failed.\n");
+ return ret;
+ }
+
+ tmpkr = talloc_zero(NULL, struct krb5_req);
+ if (tmpkr == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ /* Send reply and wait for next step. */
+ ret = k5c_send_data(kr, STDOUT_FILENO, ret);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Failed to send reply\n");
+ }
+
+ ret = k5c_recv_data(tmpkr, STDIN_FILENO, &offline);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ ret = krb5_req_update(kr, tmpkr);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unable to update krb request [%d]: %s\n",
+ ret, sss_strerror(ret));
+ goto done;
+ }
+
+done:
+ talloc_free(tmpkr);
+ return ret;
+}
+#endif /* BUILD_PASSKEY */
+
+static krb5_error_code answer_passkey(krb5_context kctx,
+ struct krb5_req *kr,
+ krb5_responder_context rctx)
+{
+#ifndef BUILD_PASSKEY
+ DEBUG(SSSDBG_TRACE_FUNC, "Passkey auth not possible, SSSD built without passkey support!\n");
+ return EINVAL;
+#else
+ enum sss_authtok_type type;
+ struct sss_passkey_message *msg;
+ struct sss_passkey_message *reply_msg = NULL;
+ const char *challenge;
+ const char *reply;
+ char *reply_str = NULL;
+ enum sss_passkey_phase phase;
+ const char *state;
+ size_t reply_len;
+ krb5_error_code kerr;
+
+ challenge = krb5_responder_get_challenge(kctx, rctx,
+ SSSD_PASSKEY_QUESTION);
+ if (challenge == NULL) {
+ return ENOENT;
+ }
+
+ msg = sss_passkey_message_decode(challenge);
+ if (msg == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "Unable to decode passkey message\n");
+ return EINVAL;
+ }
+
+ if (kr->pd->cmd == SSS_PAM_PREAUTH) {
+ kerr = passkey_preauth(kr, msg->data.challenge);
+ if (kerr != EOK) {
+ goto done;
+ }
+ }
+
+ if (kr->pd->cmd != SSS_PAM_AUTHENTICATE) {
+ DEBUG(SSSDBG_OP_FAILURE, "Unexpected command [%d]\n", kr->pd->cmd);
+ kerr = EINVAL;
+ goto done;
+ }
+
+ type = sss_authtok_get_type(kr->pd->authtok);
+ if (type != SSS_AUTHTOK_TYPE_PASSKEY_REPLY) {
+ DEBUG(SSSDBG_OP_FAILURE, "Unexpected authentication token type [%s]\n",
+ sss_authtok_type_to_str(type));
+ kerr = EINVAL;
+ goto done;
+ }
+
+ kerr = sss_authtok_get_passkey_reply(kr->pd->authtok, &reply, &reply_len);
+ if (kerr != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "Unexpected command [%d]\n", kr->pd->cmd);
+ goto done;
+ }
+
+ phase = SSS_PASSKEY_PHASE_REPLY;
+ state = SSSD_PASSKEY_REPLY_STATE;
+ reply_msg = sss_passkey_message_from_reply_json(phase, state, reply);
+ if (reply_msg == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "Unable to prefix passkey message\n");
+ kerr = EINVAL;
+ goto done;
+ }
+
+ reply_str = sss_passkey_message_encode(reply_msg);
+ if (reply_str == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "Unable to encode passkey message\n");
+ kerr = EINVAL;
+ goto done;
+ }
+
+ /* Don't let SSSD cache the authtoken since it is single-use. */
+ kerr = pam_add_response(kr->pd, SSS_OTP, 0, NULL);
+ if (kerr != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n");
+ goto done;
+ }
+
+ kerr = krb5_responder_set_answer(kctx, rctx, SSSD_PASSKEY_QUESTION,
+ reply_str);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_OP_FAILURE, "Unable to set passkey answer [%d]\n", kerr);
+ goto done;
+ }
+
+ kerr = EOK;
+
+done:
+ if (reply_str != NULL) {
+ free(reply_str);
+ }
+ if (reply_msg != NULL) {
+ sss_passkey_message_free(reply_msg);
+ }
+
+ return kerr;
+#endif /* BUILD_PASSKEY */
+}
+
+static krb5_error_code sss_krb5_responder(krb5_context ctx,
+ void *data,
+ krb5_responder_context rctx)
+{
+ struct krb5_req *kr = talloc_get_type(data, struct krb5_req);
+ const char * const *question_list;
+ size_t c;
+ const char *pwd;
+ int ret;
+ krb5_error_code kerr;
+
+ if (kr == NULL) {
+ return EINVAL;
+ }
+
+ question_list = krb5_responder_list_questions(ctx, rctx);
+
+ if (question_list != NULL) {
+ for (c = 0; question_list[c] != NULL; c++) {
+ DEBUG(SSSDBG_TRACE_ALL, "Got question [%s].\n", question_list[c]);
+
+ if (strcmp(question_list[c],
+ KRB5_RESPONDER_QUESTION_PASSWORD) == 0) {
+ kr->password_prompting = true;
+
+ if ((kr->pd->cmd == SSS_PAM_AUTHENTICATE
+ || kr->pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM
+ || kr->pd->cmd == SSS_PAM_CHAUTHTOK)
+ && sss_authtok_get_type(kr->pd->authtok)
+ == SSS_AUTHTOK_TYPE_PASSWORD) {
+ ret = sss_authtok_get_password(kr->pd->authtok, &pwd, NULL);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "sss_authtok_get_password failed.\n");
+ return ret;
+ }
+
+ kerr = krb5_responder_set_answer(ctx, rctx,
+ KRB5_RESPONDER_QUESTION_PASSWORD,
+ pwd);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "krb5_responder_set_answer failed.\n");
+ }
+
+ return kerr;
+ }
+ } else if (strcmp(question_list[c],
+ KRB5_RESPONDER_QUESTION_PKINIT) == 0
+ && (sss_authtok_get_type(kr->pd->authtok)
+ == SSS_AUTHTOK_TYPE_SC_PIN
+ || sss_authtok_get_type(kr->pd->authtok)
+ == SSS_AUTHTOK_TYPE_SC_KEYPAD)) {
+ return answer_pkinit(ctx, kr, rctx);
+ } else if (strcmp(question_list[c], SSSD_IDP_OAUTH2_QUESTION) == 0) {
+ return answer_idp_oauth2(ctx, kr, rctx);
+ } else if (strcmp(question_list[c], SSSD_PASSKEY_QUESTION) == 0) {
+ return answer_passkey(ctx, kr, rctx);
+ }
+ }
+ }
+
+ return answer_otp(ctx, kr, rctx);
+}
+#endif /* HAVE_KRB5_GET_INIT_CREDS_OPT_SET_RESPONDER */
+
+static char *password_or_responder(const char *password)
+{
+#ifdef HAVE_KRB5_GET_INIT_CREDS_OPT_SET_RESPONDER
+ /* If the new responder interface is available, we will handle even simple
+ * passwords in the responder. */
+ return NULL;
+#else
+ return discard_const(password);
+#endif
+}
+
+static krb5_error_code sss_krb5_prompter(krb5_context context, void *data,
+ const char *name, const char *banner,
+ int num_prompts, krb5_prompt prompts[])
+{
+ int ret;
+ size_t c;
+ struct krb5_req *kr = talloc_get_type(data, struct krb5_req);
+
+ if (kr == NULL) {
+ return EINVAL;
+ }
+
+ DEBUG(SSSDBG_TRACE_ALL,
+ "sss_krb5_prompter name [%s] banner [%s] num_prompts [%d] EINVAL.\n",
+ name, banner, num_prompts);
+
+ if (num_prompts != 0) {
+ for (c = 0; c < num_prompts; c++) {
+ DEBUG(SSSDBG_TRACE_ALL, "Prompt [%zu][%s].\n", c,
+ prompts[c].prompt);
+ }
+
+ DEBUG(SSSDBG_FUNC_DATA, "Prompter interface isn't used for password prompts by SSSD.\n");
+ return KRB5_LIBOS_CANTREADPWD;
+ }
+
+ if (banner == NULL || *banner == '\0') {
+ DEBUG(SSSDBG_FUNC_DATA,
+ "Prompter called with empty banner, nothing to do.\n");
+ return EOK;
+ }
+
+ DEBUG(SSSDBG_FUNC_DATA, "Prompter called with [%s].\n", banner);
+
+ ret = pam_add_response(kr->pd, SSS_PAM_TEXT_MSG, strlen(banner)+1,
+ (const uint8_t *) banner);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n");
+ }
+
+ return EOK;
+}
+
+
+static krb5_error_code create_empty_cred(krb5_context ctx, krb5_principal princ,
+ krb5_creds **_cred)
+{
+ krb5_error_code kerr;
+ krb5_creds *cred = NULL;
+ krb5_data *krb5_realm;
+
+ cred = calloc(sizeof(krb5_creds), 1);
+ if (cred == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "calloc failed.\n");
+ return ENOMEM;
+ }
+
+ kerr = krb5_copy_principal(ctx, princ, &cred->client);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "krb5_copy_principal failed.\n");
+ goto done;
+ }
+
+ krb5_realm = krb5_princ_realm(ctx, princ);
+
+ kerr = krb5_build_principal_ext(ctx, &cred->server,
+ krb5_realm->length, krb5_realm->data,
+ KRB5_TGS_NAME_SIZE, KRB5_TGS_NAME,
+ krb5_realm->length, krb5_realm->data, 0);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "krb5_build_principal_ext failed.\n");
+ goto done;
+ }
+
+ DEBUG(SSSDBG_TRACE_INTERNAL, "Created empty krb5_creds.\n");
+
+done:
+ if (kerr != 0) {
+ krb5_free_cred_contents(ctx, cred);
+ free(cred);
+ } else {
+ *_cred = cred;
+ }
+
+ return kerr;
+}
+
+
+static errno_t handle_randomized(char *in)
+{
+ size_t ccname_len;
+ char *ccname = NULL;
+ int ret;
+
+ /* We only treat the FILE type case in a special way due to the history
+ * of storing FILE type ccache in /tmp and associated security issues */
+ if (in[0] == '/') {
+ ccname = in;
+ } else if (strncmp(in, "FILE:", 5) == 0) {
+ ccname = in + 5;
+ } else {
+ return EOK;
+ }
+
+ ccname_len = strlen(ccname);
+ if (ccname_len >= 6 && strcmp(ccname + (ccname_len - 6), "XXXXXX") == 0) {
+ /* NOTE: this call is only used to create a unique name, as later
+ * krb5_cc_initialize() will unlink and recreate the file.
+ * This is ok because this part of the code is called with
+ * privileges already dropped when handling user ccache, or the ccache
+ * is stored in a private directory. So we do not have huge issues if
+ * something races, we mostly care only about not accidentally use
+ * an existing name and thus failing in the process of saving the
+ * cache. Malicious races can only be avoided by libkrb5 itself. */
+ ret = sss_unique_filename(NULL, ccname);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "mkstemp(\"%s\") failed [%d]: %s!\n",
+ ccname, ret, strerror(ret));
+ return ret;
+ }
+ }
+
+ return EOK;
+}
+
+/* NOTE: callers rely on 'name' being *changed* if it needs to be randomized,
+ * as they will then send the name back to the new name via the return call
+ * k5c_attach_ccname_msg(). Callers will send in a copy of the name if they
+ * do not care for changes. */
+static krb5_error_code create_ccache(char *ccname, krb5_creds *creds)
+{
+ krb5_context kctx = NULL;
+ krb5_ccache kcc = NULL;
+ const char *type;
+ krb5_error_code kerr;
+#ifdef HAVE_KRB5_CC_COLLECTION
+ krb5_ccache cckcc;
+ bool switch_to_cc = false;
+#endif
+
+ /* Set a restrictive umask, just in case we end up creating any file or a
+ * directory. */
+ if (strncmp(ccname, "DIR:", 4) == 0) {
+ umask(SSS_DFL_X_UMASK);
+ } else {
+ umask(SSS_DFL_UMASK);
+ }
+
+ /* we create a new context here as the main process one may have been
+ * opened as root and contain possibly references (even open handles?)
+ * to resources we do not have or do not want to have access to */
+ kerr = krb5_init_context(&kctx);
+ if (kerr) {
+ KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
+ return ERR_INTERNAL;
+ }
+
+ kerr = handle_randomized(ccname);
+ if (kerr) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "handle_randomized failed: %d\n", kerr);
+ goto done;
+ }
+
+ kerr = krb5_cc_resolve(kctx, ccname, &kcc);
+ if (kerr) {
+ KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
+ goto done;
+ }
+
+ type = krb5_cc_get_type(kctx, kcc);
+ DEBUG(SSSDBG_TRACE_ALL, "Initializing ccache of type [%s]\n", type);
+
+#ifdef HAVE_KRB5_CC_COLLECTION
+ if (krb5_cc_support_switch(kctx, type)) {
+ DEBUG(SSSDBG_TRACE_ALL, "CC supports switch\n");
+ kerr = krb5_cc_set_default_name(kctx, ccname);
+ if (kerr) {
+ DEBUG(SSSDBG_TRACE_ALL, "Cannot set default name!\n");
+ KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
+ goto done;
+ }
+
+ kerr = krb5_cc_cache_match(kctx, creds->client, &cckcc);
+ if (kerr == KRB5_CC_NOTFOUND) {
+ DEBUG(SSSDBG_TRACE_ALL, "Match not found\n");
+ kerr = krb5_cc_new_unique(kctx, type, NULL, &cckcc);
+ switch_to_cc = true;
+ }
+ if (kerr) {
+ DEBUG(SSSDBG_TRACE_ALL, "krb5_cc_cache_match failed\n");
+ KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
+ goto done;
+ }
+ krb5_cc_close(kctx, kcc);
+ kcc = cckcc;
+ }
+#endif
+
+ kerr = krb5_cc_initialize(kctx, kcc, creds->client);
+ if (kerr) {
+ DEBUG(SSSDBG_TRACE_ALL, "krb5_cc_initialize failed\n");
+ KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
+ goto done;
+ }
+
+ kerr = krb5_cc_store_cred(kctx, kcc, creds);
+ if (kerr) {
+ DEBUG(SSSDBG_TRACE_ALL, "krb5_cc_store_cred failed\n");
+ KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
+ goto done;
+ }
+
+#ifdef HAVE_KRB5_CC_COLLECTION
+ if (switch_to_cc) {
+ DEBUG(SSSDBG_TRACE_ALL, "switch_to_cc\n");
+ kerr = krb5_cc_switch(kctx, kcc);
+ if (kerr) {
+ DEBUG(SSSDBG_TRACE_ALL, "krb5_cc_switch\n");
+ KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
+ goto done;
+ }
+ }
+#endif
+
+ DEBUG(SSSDBG_TRACE_ALL, "returning: %d\n", kerr);
+done:
+ if (kcc) {
+ /* FIXME: should we krb5_cc_destroy in case of error? */
+ krb5_cc_close(kctx, kcc);
+ }
+
+ krb5_free_context(kctx);
+
+ return kerr;
+}
+
+static errno_t pack_response_packet(TALLOC_CTX *mem_ctx, errno_t error,
+ struct response_data *resp_list,
+ uint8_t **_buf, size_t *_len)
+{
+ uint8_t *buf;
+ size_t size = 0;
+ size_t p = 0;
+ struct response_data *pdr;
+
+ /* A buffer with the following structure must be created:
+ * int32_t status of the request (required)
+ * message (zero or more)
+ *
+ * A message consists of:
+ * int32_t type of the message
+ * int32_t length of the following data
+ * uint8_t[len] data
+ */
+
+ size = sizeof(int32_t);
+
+ for (pdr = resp_list; pdr != NULL; pdr = pdr->next) {
+ size += 2*sizeof(int32_t) + pdr->len;
+ }
+
+ buf = talloc_array(mem_ctx, uint8_t, size);
+ if (!buf) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_array failed\n");
+ return ENOMEM;
+ }
+
+ SAFEALIGN_SET_INT32(&buf[p], error, &p);
+
+ for (pdr = resp_list; pdr != NULL; pdr = pdr->next) {
+ SAFEALIGN_SET_INT32(&buf[p], pdr->type, &p);
+ SAFEALIGN_SET_INT32(&buf[p], pdr->len, &p);
+ safealign_memcpy(&buf[p], pdr->data, pdr->len, &p);
+ }
+
+ DEBUG(SSSDBG_TRACE_INTERNAL, "response packet size: [%zu]\n", p);
+
+ *_buf = buf;
+ *_len = p;
+ return EOK;
+}
+
+static errno_t k5c_attach_otp_info_msg(struct krb5_req *kr)
+{
+ uint8_t *msg = NULL;
+ size_t msg_len;
+ int ret;
+ size_t vendor_len = 0;
+ size_t token_id_len = 0;
+ size_t challenge_len = 0;
+ size_t idx = 0;
+
+ msg_len = 3;
+ if (kr->otp_vendor != NULL) {
+ vendor_len = strlen(kr->otp_vendor);
+ msg_len += vendor_len;
+ }
+
+ if (kr->otp_token_id != NULL) {
+ token_id_len = strlen(kr->otp_token_id);
+ msg_len += token_id_len;
+ }
+
+ if (kr->otp_challenge != NULL) {
+ challenge_len = strlen(kr->otp_challenge);
+ msg_len += challenge_len;
+ }
+
+ msg = talloc_zero_size(kr, msg_len);
+ if (msg == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_size failed.\n");
+ return ENOMEM;
+ }
+
+ if (kr->otp_vendor != NULL) {
+ memcpy(msg, kr->otp_vendor, vendor_len);
+ }
+ idx += vendor_len +1;
+
+ if (kr->otp_token_id != NULL) {
+ memcpy(msg + idx, kr->otp_token_id, token_id_len);
+ }
+ idx += token_id_len +1;
+
+ if (kr->otp_challenge != NULL) {
+ memcpy(msg + idx, kr->otp_challenge, challenge_len);
+ }
+
+ ret = pam_add_response(kr->pd, SSS_PAM_OTP_INFO, msg_len, msg);
+ talloc_zfree(msg);
+
+ return ret;
+}
+
+static errno_t k5c_attach_oauth2_info_msg(struct krb5_req *kr,
+ struct sss_idp_oauth2 *data)
+{
+ uint8_t *msg;
+ const char *curi;
+ size_t msg_len;
+ size_t uri_len = 0;
+ size_t curi_len = 0;
+ size_t user_code_len = 0;
+ size_t idx = 0;
+ errno_t ret;
+
+ if (data->verification_uri == NULL || data->user_code == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Empty oauth2 verification_uri or user_code\n");
+ return EINVAL;
+ }
+
+ msg_len = 0;
+
+ uri_len = strlen(data->verification_uri) + 1;
+ msg_len += uri_len;
+
+ if (data->verification_uri_complete != NULL) {
+ curi = data->verification_uri_complete;
+ curi_len = strlen(curi) + 1;
+ } else {
+ curi = "";
+ curi_len = 1;
+ }
+ msg_len += curi_len;
+
+ user_code_len = strlen(data->user_code) + 1;
+ msg_len += user_code_len;
+
+ msg = talloc_zero_size(NULL, msg_len);
+ if (msg == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_size failed.\n");
+ return ENOMEM;
+ }
+
+ memcpy(msg, data->verification_uri, uri_len);
+ idx += uri_len;
+
+ memcpy(msg + idx, curi, curi_len);
+ idx += curi_len;
+
+ memcpy(msg + idx, data->user_code, user_code_len);
+
+ ret = pam_add_response(kr->pd, SSS_PAM_OAUTH2_INFO, msg_len, msg);
+ talloc_zfree(msg);
+
+ return ret;
+}
+
+
+static errno_t k5c_attach_keep_alive_msg(struct krb5_req *kr)
+{
+ uint8_t *msg;
+ pid_t pid;
+ int ret;
+
+ pid = getpid();
+
+ msg = talloc_memdup(kr, &pid, sizeof(pid_t));
+ if (msg == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_size failed.\n");
+ return ENOMEM;
+ }
+
+ /* Indicate that the krb5 child must be kept alive to continue
+ * authentication with correct internal state of Kerberos API.
+ *
+ * Further communication must be done against the same child process */
+ ret = pam_add_response(kr->pd, SSS_CHILD_KEEP_ALIVE, sizeof(pid_t), msg);
+ talloc_zfree(msg);
+
+ return ret;
+}
+
+static errno_t k5c_attach_ccname_msg(struct krb5_req *kr)
+{
+ char *msg = NULL;
+ int ret;
+
+ if (kr->ccname == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Error obtaining ccname.\n");
+ return ERR_INTERNAL;
+ }
+
+ msg = talloc_asprintf(kr, "%s=%s",CCACHE_ENV_NAME, kr->ccname);
+ if (msg == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n");
+ return ENOMEM;
+ }
+
+ ret = pam_add_response(kr->pd, SSS_PAM_ENV_ITEM,
+ strlen(msg) + 1, (uint8_t *)msg);
+ talloc_zfree(msg);
+
+ return ret;
+}
+
+static errno_t k5c_send_data(struct krb5_req *kr, int fd, errno_t error)
+{
+ ssize_t written;
+ uint8_t *buf;
+ size_t len;
+ int ret;
+
+ DEBUG(SSSDBG_FUNC_DATA, "Received error code %d\n", error);
+
+ ret = pack_response_packet(kr, error, kr->pd->resp_list, &buf, &len);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "pack_response_packet failed.\n");
+ return ret;
+ }
+
+ errno = 0;
+ written = sss_atomic_write_safe_s(fd, buf, len);
+ if (written == -1) {
+ ret = errno;
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "write failed [%d][%s].\n", ret, strerror(ret));
+ return ret;
+ }
+
+ if (written != len) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Write error, wrote [%zu] bytes, expected [%zu]\n",
+ written, len);
+ return EOK;
+ }
+
+ DEBUG(SSSDBG_TRACE_ALL, "Response sent.\n");
+
+ return EOK;
+}
+
+static errno_t get_pkinit_identity(TALLOC_CTX *mem_ctx,
+ struct sss_auth_token *authtok,
+ char **_identity)
+{
+ int ret;
+ char *identity;
+ const char *token_name;
+ const char *module_name;
+ const char *key_id;
+ const char *label;
+
+ ret = sss_authtok_get_sc(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");
+ return ret;
+ }
+
+ DEBUG(SSSDBG_TRACE_ALL, "Got [%s][%s].\n", token_name, module_name);
+
+ if (module_name == NULL || *module_name == '\0') {
+ module_name = "p11-kit-proxy.so";
+ }
+
+ identity = talloc_asprintf(mem_ctx, "PKCS11:module_name=%s", module_name);
+ if (identity == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n");
+ return ENOMEM;
+ }
+
+ if (token_name != NULL && *token_name != '\0') {
+ identity = talloc_asprintf_append(identity, ":token=%s",
+ token_name);
+ if (identity == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "talloc_asprintf_append failed.\n");
+ return ENOMEM;
+ }
+ }
+
+ if (key_id != NULL && *key_id != '\0') {
+ identity = talloc_asprintf_append(identity, ":certid=%s", key_id);
+ if (identity == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "talloc_asprintf_append failed.\n");
+ return ENOMEM;
+ }
+ }
+
+ if (label != NULL && *label != '\0') {
+ identity = talloc_asprintf_append(identity, ":certlabel=%s", label);
+ if (identity == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "talloc_asprintf_append failed.\n");
+ return ENOMEM;
+ }
+ }
+
+ *_identity = identity;
+
+ DEBUG(SSSDBG_TRACE_ALL, "Using pkinit identity [%s].\n", identity);
+
+ return EOK;
+}
+
+static errno_t add_ticket_times_and_upn_to_response(struct krb5_req *kr)
+{
+ int ret;
+ int64_t t[4];
+ krb5_error_code kerr;
+ char *upn = NULL;
+ unsigned int upn_len = 0;
+
+ t[0] = (int64_t) kr->creds->times.authtime;
+ t[1] = (int64_t) kr->creds->times.starttime;
+ t[2] = (int64_t) kr->creds->times.endtime;
+ t[3] = (int64_t) kr->creds->times.renew_till;
+
+ ret = pam_add_response(kr->pd, SSS_KRB5_INFO_TGT_LIFETIME,
+ 4*sizeof(int64_t), (uint8_t *) t);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n");
+ goto done;
+ }
+
+ kerr = krb5_unparse_name_ext(kr->ctx, kr->creds->client, &upn, &upn_len);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_OP_FAILURE, "krb5_unparse_name_ext failed.\n");
+ goto done;
+ }
+
+ ret = pam_add_response(kr->pd, SSS_KRB5_INFO_UPN, upn_len,
+ (uint8_t *) upn);
+ krb5_free_unparsed_name(kr->ctx, upn);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n");
+ goto done;
+ }
+
+done:
+ return ret;
+}
+
+static krb5_error_code validate_tgt(struct krb5_req *kr)
+{
+ krb5_error_code kerr;
+ krb5_error_code kt_err;
+ char *principal = NULL;
+ krb5_keytab keytab;
+ krb5_kt_cursor cursor;
+ krb5_keytab_entry entry;
+ krb5_verify_init_creds_opt opt;
+ krb5_principal validation_princ = NULL;
+ bool realm_entry_found = false;
+ krb5_ccache validation_ccache = NULL;
+ krb5_authdata **pac_authdata = NULL;
+
+ memset(&keytab, 0, sizeof(keytab));
+ kerr = krb5_kt_resolve(kr->ctx, kr->keytab, &keytab);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "error resolving keytab [%s], " \
+ "not verifying TGT.\n", kr->keytab);
+ return kerr;
+ }
+
+ memset(&cursor, 0, sizeof(cursor));
+ kerr = krb5_kt_start_seq_get(kr->ctx, keytab, &cursor);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "error reading keytab [%s], " \
+ "not verifying TGT.\n", kr->keytab);
+ krb5_kt_close(kr->ctx, keytab);
+ return kerr;
+ }
+
+ /* We look for the first entry from our realm or take the last one */
+ memset(&entry, 0, sizeof(entry));
+ while ((kt_err = krb5_kt_next_entry(kr->ctx, keytab, &entry, &cursor)) == 0) {
+ if (validation_princ != NULL) {
+ krb5_free_principal(kr->ctx, validation_princ);
+ validation_princ = NULL;
+ }
+ kerr = krb5_copy_principal(kr->ctx, entry.principal,
+ &validation_princ);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "krb5_copy_principal failed.\n");
+ krb5_kt_end_seq_get(kr->ctx, keytab, &cursor);
+ goto done;
+ }
+
+ kerr = sss_krb5_free_keytab_entry_contents(kr->ctx, &entry);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_MINOR_FAILURE, "Failed to free keytab entry.\n");
+ }
+ memset(&entry, 0, sizeof(entry));
+
+ if (krb5_realm_compare(kr->ctx, validation_princ, kr->creds->client)) {
+ DEBUG(SSSDBG_TRACE_INTERNAL,
+ "Found keytab entry with the realm of the credential.\n");
+ realm_entry_found = true;
+ break;
+ }
+ }
+
+ if (!realm_entry_found) {
+ DEBUG(SSSDBG_TRACE_INTERNAL,
+ "Keytab entry with the realm of the credential not found "
+ "in keytab. Using the last entry.\n");
+ }
+
+ /* Close the keytab here. Even though we're using cursors, the file
+ * handle is stored in the krb5_keytab structure, and it gets
+ * overwritten when the verify_init_creds() call below creates its own
+ * cursor, creating a leak. */
+ kerr = krb5_kt_end_seq_get(kr->ctx, keytab, &cursor);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "krb5_kt_end_seq_get failed, " \
+ "not verifying TGT.\n");
+ goto done;
+ }
+
+ /* check if we got any errors from krb5_kt_next_entry */
+ if (kt_err != 0 && kt_err != KRB5_KT_END) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "error reading keytab [%s], " \
+ "not verifying TGT.\n", kr->keytab);
+ goto done;
+ }
+
+ /* Get the principal to which the key belongs, for logging purposes. */
+ principal = NULL;
+ kerr = krb5_unparse_name(kr->ctx, validation_princ, &principal);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "internal error parsing principal name, "
+ "not verifying TGT.\n");
+ KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
+ goto done;
+ }
+
+
+ krb5_verify_init_creds_opt_init(&opt);
+ krb5_verify_init_creds_opt_set_ap_req_nofail(&opt, TRUE);
+ kerr = krb5_verify_init_creds(kr->ctx, kr->creds, validation_princ, keytab,
+ &validation_ccache, &opt);
+
+ if (kerr == 0) {
+ DEBUG(SSSDBG_TRACE_FUNC, "TGT verified using key for [%s].\n",
+ principal);
+ } else {
+ DEBUG(SSSDBG_CRIT_FAILURE ,"TGT failed verification using key " \
+ "for [%s].\n", principal);
+ goto done;
+ }
+
+ /* Try to find and send the PAC to the PAC responder.
+ * Failures are not critical. */
+ if (kr->send_pac || kr->cli_opts->check_pac_flags != 0) {
+ kerr = sss_extract_pac(kr->ctx, validation_ccache, validation_princ,
+ kr->creds->client, keytab,
+ kr->cli_opts->check_pac_flags, &pac_authdata);
+ if (kerr != 0) {
+ if (kerr == ERR_CHECK_PAC_FAILED) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "PAC check failed for principal [%s].\n", kr->name);
+ goto done;
+ }
+ DEBUG(SSSDBG_OP_FAILURE, "sss_extract_and_send_pac failed, group " \
+ "membership for user with principal [%s] " \
+ "might not be correct.\n", kr->name);
+ kerr = 0;
+ goto done;
+ }
+ }
+
+ if (kr->send_pac) {
+ if(unsetenv("_SSS_LOOPS") != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Failed to unset _SSS_LOOPS, "
+ "sss_pac_make_request will most certainly fail.\n");
+ }
+
+ kerr = sss_send_pac(pac_authdata);
+
+ if(setenv("_SSS_LOOPS", "NO", 0) != 0) {
+ DEBUG(SSSDBG_OP_FAILURE, "Failed to set _SSS_LOOPS.\n");
+ }
+
+ if (kerr != 0) {
+ if (kerr == ERR_CHECK_PAC_FAILED) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "PAC for principal [%s] is not valid.\n", kr->name);
+ goto done;
+ }
+ if (kr->cli_opts->check_pac_flags != 0) {
+ DEBUG(SSSDBG_IMPORTANT_INFO,
+ "pac_check is set but PAC responder is not running, "
+ "failed to properly validate PAC, ignored, "
+ "authentication for [%s] can proceed.\n", kr->name);
+ }
+ DEBUG(SSSDBG_OP_FAILURE, "sss_send_pac failed, group " \
+ "membership for user with principal [%s] " \
+ "might not be correct.\n", kr->name);
+ kerr = 0;
+ }
+ }
+
+done:
+ krb5_free_authdata(kr->ctx, pac_authdata);
+ if (validation_ccache != NULL) {
+ krb5_cc_destroy(kr->ctx, validation_ccache);
+ }
+
+ if (krb5_kt_close(kr->ctx, keytab) != 0) {
+ DEBUG(SSSDBG_MINOR_FAILURE, "krb5_kt_close failed\n");
+ }
+ if (validation_princ != NULL) {
+ krb5_free_principal(kr->ctx, validation_princ);
+ }
+ if (principal != NULL) {
+ sss_krb5_free_unparsed_name(kr->ctx, principal);
+ }
+
+ return kerr;
+
+}
+
+static krb5_error_code get_and_save_tgt_with_keytab(krb5_context ctx,
+ struct cli_opts *cli_opts,
+ krb5_principal princ,
+ krb5_keytab keytab,
+ char *ccname)
+{
+ krb5_error_code kerr = 0;
+ krb5_creds creds;
+ krb5_get_init_creds_opt options;
+
+ memset(&creds, 0, sizeof(creds));
+ memset(&options, 0, sizeof(options));
+
+ krb5_get_init_creds_opt_set_address_list(&options, NULL);
+ krb5_get_init_creds_opt_set_forwardable(&options, 0);
+ krb5_get_init_creds_opt_set_proxiable(&options, 0);
+ set_canonicalize_option(cli_opts, &options);
+
+ kerr = krb5_get_init_creds_keytab(ctx, &creds, princ, keytab, 0, NULL,
+ &options);
+ if (kerr != 0) {
+ KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
+ return kerr;
+ }
+
+ /* Use the updated principal in the creds in case canonicalized */
+ kerr = create_ccache(ccname, &creds);
+ if (kerr != 0) {
+ goto done;
+ }
+ kerr = 0;
+
+done:
+ krb5_free_cred_contents(ctx, &creds);
+
+ return kerr;
+
+}
+
+/* [MS-KILE]: Kerberos Protocol Extensions
+ * https://msdn.microsoft.com/en-us/library/cc233855.aspx
+ * http://download.microsoft.com/download/9/5/E/95EF66AF-9026-4BB0-A41D-A4F81802D92C/%5BMS-KILE%5D.pdf
+ * 2.2.1 KERB-EXT-ERROR
+ */
+bool have_ms_kile_ext_error(unsigned char *data, unsigned int length,
+ uint32_t *_ntstatus)
+{
+ /* [MS-KILE] 2.2.2 KERB-ERROR-DATA
+ * Kerberos V5 messages are defined using Abstract Syntax Notation One
+ * (ASN.1)
+ * KERB-ERROR-DATA ::= SEQUENCE {
+ * data-type [1] INTEGER,
+ * data-value [2] OCTET STRING OPTIONAL
+ * }
+ * We are interested in data-type 3 KERB_ERR_TYPE_EXTENDED
+ */
+ uint8_t kile_asn1_begining[] = {
+ 0x30, 0x15, /* 0x30 is SEQUENCE, 0x15 length */
+ 0xA1, 0x03, /* 0xA1 is 1st element of sequence, 0x03 length */
+ 0x02, 0x01, 0x03, /* 0x02 is INTEGER, 0x01 length, 0x03 value */
+ 0xA2, 0x0E, /* 0xA2 is 2nd element of sequence, 0x0E length */
+ 0x04, 0x0C, /* 0x04 is OCTET STRING, 0x0C length (12 bytes) */
+ };
+ const size_t offset = sizeof(kile_asn1_begining);
+ uint32_t value;
+
+ if (length != 23 || data == NULL) {
+ return false;
+ }
+
+ if (memcmp(data, kile_asn1_begining, offset) != 0) {
+ return false;
+ }
+
+ /* [MS-KILE] 2.2.1 KERB-EXT-ERROR
+ * typedef struct KERB_EXT_ERROR {
+ * unsigned long status;
+ * unsigned long reserved;
+ * unsigned long flags;
+ * } KERB_EXT_ERROR;
+ * Status: An NTSTATUS value. See [MS-ERREF] section 2.3.
+ */
+ value = data[offset + 3] << 24
+ | data[offset + 2] << 16
+ | data[offset + 1] << 8
+ | data[offset + 0];
+
+ *_ntstatus = value;
+ return true;
+}
+
+/* Following NTSTATUS values are from:
+ * [MS-ERREF]: Windows Error Codes -> Section 2.3.1
+ * https://msdn.microsoft.com/en-us/library/cc231196.aspx
+ * http://download.microsoft.com/download/9/5/E/95EF66AF-9026-4BB0-A41D-A4F81802D92C/%5BMS-ERREF%5D.pdf
+ */
+#define NT_STATUS_ACCOUNT_EXPIRED 0xC0000193
+#define NT_STATUS_ACCOUNT_DISABLED 0xC0000072
+
+void check_ms_kile_ext_krb5err(krb5_context context,
+ krb5_init_creds_context init_cred_ctx,
+ krb5_error_code *_kerr)
+{
+ krb5_error_code err;
+ krb5_error *error = NULL;
+ uint32_t ntstatus;
+
+ err = krb5_init_creds_get_error(context, init_cred_ctx, &error);
+ if (err != 0 || error == NULL) {
+ KRB5_CHILD_DEBUG(SSSDBG_TRACE_FUNC, err);
+ return;
+ }
+
+ if (have_ms_kile_ext_error((unsigned char *)error->e_data.data, error->e_data.length,
+ &ntstatus)) {
+ switch (ntstatus) {
+ case NT_STATUS_ACCOUNT_EXPIRED:
+ *_kerr = KRB5KDC_ERR_NAME_EXP;
+ break;
+ case NT_STATUS_ACCOUNT_DISABLED:
+ *_kerr = KRB5KDC_ERR_CLIENT_REVOKED;
+ break;
+ }
+ }
+}
+
+krb5_error_code
+sss_krb5_get_init_creds_password(krb5_context context, krb5_creds *creds,
+ krb5_principal client, const char *password,
+ krb5_prompter_fct prompter, void *data,
+ krb5_deltat start_time,
+ const char *in_tkt_service,
+ krb5_get_init_creds_opt *k5_gic_options)
+{
+ krb5_error_code kerr;
+ krb5_init_creds_context init_cred_ctx = NULL;
+
+ kerr = krb5_init_creds_init(context, client, prompter, data,
+ start_time, k5_gic_options,
+ &init_cred_ctx);
+ if (kerr != 0) {
+ KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
+ goto done;
+ }
+
+ if (password != NULL) {
+ kerr = krb5_init_creds_set_password(context, init_cred_ctx, password);
+ if (kerr != 0) {
+ KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
+ goto done;
+ }
+ }
+
+ if (in_tkt_service != NULL) {
+ kerr = krb5_init_creds_set_service(context, init_cred_ctx,
+ in_tkt_service);
+ if (kerr != 0) {
+ KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
+ goto done;
+ }
+ }
+
+ kerr = krb5_init_creds_get(context, init_cred_ctx);
+ if (kerr == KRB5KDC_ERR_CLIENT_REVOKED) {
+ check_ms_kile_ext_krb5err(context, init_cred_ctx, &kerr);
+ }
+
+ if (kerr != 0) {
+ KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
+ goto done;
+ }
+
+ kerr = krb5_init_creds_get_creds(context, init_cred_ctx, creds);
+
+done:
+ krb5_init_creds_free(context, init_cred_ctx);
+ return kerr;
+}
+
+static krb5_error_code get_and_save_tgt(struct krb5_req *kr,
+ const char *password)
+{
+ const char *realm_name;
+ int realm_length;
+ krb5_error_code kerr;
+ char *cc_name;
+ int ret;
+ char *identity = NULL;
+
+ kerr = sss_krb5_get_init_creds_opt_set_expire_callback(kr->ctx, kr->options,
+ sss_krb5_expire_callback_func,
+ kr);
+ if (kerr != 0) {
+ KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Failed to set expire callback, continue without.\n");
+ }
+
+ sss_krb5_princ_realm(kr->ctx, kr->princ, &realm_name, &realm_length);
+ if (realm_length == 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "sss_krb5_princ_realm failed.\n");
+ return KRB5KRB_ERR_GENERIC;
+ }
+
+ if (sss_authtok_get_type(kr->pd->authtok) == SSS_AUTHTOK_TYPE_SC_PIN
+ || sss_authtok_get_type(kr->pd->authtok)
+ == SSS_AUTHTOK_TYPE_SC_KEYPAD) {
+ DEBUG(SSSDBG_TRACE_ALL,
+ "Found Smartcard credentials, trying pkinit.\n");
+
+ ret = get_pkinit_identity(kr, kr->pd->authtok, &identity);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "get_pkinit_identity failed.\n");
+ return ret;
+ }
+
+ kerr = krb5_get_init_creds_opt_set_pa(kr->ctx, kr->options,
+ "X509_user_identity", identity);
+ talloc_free(identity);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "krb5_get_init_creds_opt_set_pa failed.\n");
+ return kerr;
+ }
+
+ /* TODO: Maybe X509_anchors should be added here as well */
+ }
+
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "Attempting kinit for realm [%s]\n",realm_name);
+ kerr = kr->krb5_get_init_creds_password(kr->ctx, kr->creds, kr->princ,
+ password_or_responder(password),
+ sss_krb5_prompter, kr, 0, NULL,
+ kr->options);
+ if (kr->pd->cmd == SSS_PAM_PREAUTH && kerr != KRB5KDC_ERR_KEY_EXP) {
+ /* Any errors except KRB5KDC_ERR_KEY_EXP are ignored during pre-auth,
+ * only data is collected to be send back to the client.
+ * KRB5KDC_ERR_KEY_EXP must be handled separately to figure out the
+ * possible authentication methods to update the password. */
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "krb5_get_init_creds_password returned [%d] during pre-auth.\n",
+ kerr);
+ return 0;
+ } else {
+ if (kerr != 0) {
+ KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
+
+ /* Special case for IPA password migration */
+ if (kr->pd->cmd == SSS_PAM_AUTHENTICATE
+ && kerr == KRB5_PREAUTH_FAILED
+ && kr->pkinit_prompting == false
+ && kr->password_prompting == false
+ && kr->otp == false
+ && sss_authtok_get_type(kr->pd->authtok)
+ == SSS_AUTHTOK_TYPE_PASSWORD) {
+ return ERR_CREDS_INVALID;
+ }
+
+ /* If during authentication either the MIT Kerberos pkinit
+ * pre-auth module is missing or no Smartcard is inserted and only
+ * pkinit is available KRB5_PREAUTH_FAILED is returned.
+ * ERR_NO_AUTH_METHOD_AVAILABLE is used to indicate to the
+ * frontend that local authentication might be tried.
+ * Same is true if Smartcard credentials are given but only other
+ * authentication methods are available. */
+ if (kr->pd->cmd == SSS_PAM_AUTHENTICATE
+ && kerr == KRB5_PREAUTH_FAILED
+ && kr->pkinit_prompting == false
+ && (( kr->password_prompting == false
+ && kr->otp == false)
+ || ((kr->otp == true
+ || kr->password_prompting == true)
+ && IS_SC_AUTHTOK(kr->pd->authtok))) ) {
+ return ERR_NO_AUTH_METHOD_AVAILABLE;
+ }
+ return kerr;
+ }
+ }
+
+ if (kr->validate) {
+ kerr = validate_tgt(kr);
+ if (kerr != 0) {
+ KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
+ return kerr;
+ }
+
+ } else {
+ DEBUG(SSSDBG_CONF_SETTINGS, "TGT validation is disabled.\n");
+ }
+
+ /* In a non-POSIX environment, we only care about the return code from
+ * krb5_child, so let's not even attempt to create the ccache
+ */
+ if (kr->posix_domain == false) {
+ DEBUG(SSSDBG_TRACE_LIBS,
+ "Finished authentication in a non-POSIX domain\n");
+ goto done;
+ }
+
+ kerr = restore_creds(kr->pcsc_saved_creds);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_OP_FAILURE, "restore_creds failed.\n");
+ }
+ /* Make sure ccache is created and written as the user */
+ if (geteuid() != kr->uid || getegid() != kr->gid) {
+ kerr = k5c_become_user(kr->uid, kr->gid, kr->posix_domain);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "become_user failed.\n");
+ goto done;
+ }
+ }
+
+ DEBUG(SSSDBG_TRACE_INTERNAL,
+ "Running as [%"SPRIuid"][%"SPRIgid"].\n", geteuid(), getegid());
+
+ /* If kr->ccname is cache collection (DIR:/...), we want to work
+ * directly with file ccache (DIR::/...), but cache collection
+ * should be returned back to back end.
+ */
+ cc_name = sss_get_ccache_name_for_principal(kr->pd, kr->ctx,
+ kr->creds->client,
+ kr->ccname);
+ if (cc_name == NULL) {
+ cc_name = kr->ccname;
+ }
+
+ /* Use the updated principal in the creds in case canonicalized */
+ kerr = create_ccache(cc_name, kr->creds);
+ if (kerr != 0) {
+ goto done;
+ }
+
+ /* Successful authentication! Check if ccache contains the
+ * right principal...
+ */
+ kerr = sss_krb5_check_ccache_princ(kr->ctx, kr->ccname, kr->creds->client);
+ if (kerr) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "No ccache for %s in %s?\n", kr->upn, kr->ccname);
+ goto done;
+ }
+
+ kerr = safe_remove_old_ccache_file(kr->old_ccname, kr->ccname,
+ kr->uid, kr->gid);
+ if (kerr != EOK) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "Failed to remove old ccache file [%s], "
+ "please remove it manually.\n", kr->old_ccname);
+ }
+
+ kerr = add_ticket_times_and_upn_to_response(kr);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "add_ticket_times_and_upn_to_response failed.\n");
+ }
+
+ kerr = 0;
+
+done:
+ krb5_free_cred_contents(kr->ctx, kr->creds);
+
+ return kerr;
+
+}
+
+static errno_t map_krb5_error(krb5_error_code kerr)
+{
+ /* just pass SSSD's internal error codes */
+ if (kerr > 0 && IS_SSSD_ERROR(kerr)) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "[%d][%s].\n", kerr, sss_strerror(kerr));
+ return kerr;
+ }
+
+ if (kerr != 0) {
+ KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
+ }
+
+ switch (kerr) {
+ case 0:
+ return ERR_OK;
+
+ case KRB5_LIBOS_CANTREADPWD:
+ return ERR_NO_CREDS;
+
+ case KRB5_KDCREP_SKEW:
+ case KRB5KRB_AP_ERR_SKEW:
+ case KRB5KRB_AP_ERR_TKT_EXPIRED:
+ case KRB5KRB_AP_ERR_TKT_NYV:
+ case KRB5_KDC_UNREACH:
+ case KRB5_REALM_CANT_RESOLVE:
+ case KRB5_REALM_UNKNOWN:
+ return ERR_NETWORK_IO;
+
+ case KRB5KDC_ERR_CLIENT_REVOKED:
+ return ERR_ACCOUNT_LOCKED;
+
+ case KRB5KDC_ERR_NAME_EXP:
+ return ERR_ACCOUNT_EXPIRED;
+
+ case KRB5KDC_ERR_KEY_EXP:
+ return ERR_CREDS_EXPIRED;
+
+ case KRB5KRB_AP_ERR_BAD_INTEGRITY:
+ return ERR_AUTH_FAILED;
+
+ /* ERR_CREDS_INVALID is used to indicate to the IPA provider that trying
+ * password migration would make sense. All Kerberos error codes which can
+ * be seen while migrating LDAP users to IPA should be added here. */
+ case KRB5_PROG_ETYPE_NOSUPP:
+ case KRB5_PREAUTH_FAILED:
+ case KRB5KDC_ERR_PREAUTH_FAILED:
+ return ERR_CREDS_INVALID;
+
+ /* Please do not remove KRB5KRB_ERR_GENERIC here, it is a _generic_ error
+ * code and we cannot make any assumptions about the reason for the error.
+ * As a consequence we cannot return a different error code than a generic
+ * one which unfortunately might result in a unspecific system error
+ * message to the user.
+ *
+ * If there are cases where libkrb5 calls return KRB5KRB_ERR_GENERIC where
+ * SSSD should behave differently this has to be detected by different
+ * means, e.g. by evaluation error messages, and then the error code
+ * should be changed to a more suitable KRB5* error code or immediately to
+ * an SSSD ERR_* error code to avoid the default handling here. */
+ case KRB5KRB_ERR_GENERIC:
+ default:
+ return ERR_INTERNAL;
+ }
+}
+
+static errno_t changepw_child(struct krb5_req *kr, bool prelim)
+{
+ int ret;
+ krb5_error_code kerr = 0;
+ const char *password = NULL;
+ const char *newpassword = NULL;
+ int result_code = -1;
+ krb5_data result_code_string;
+ krb5_data result_string;
+ char *user_error_message = NULL;
+ size_t user_resp_len;
+ uint8_t *user_resp;
+ krb5_prompter_fct prompter = NULL;
+ const char *realm_name;
+ int realm_length;
+ size_t msg_len;
+ uint8_t *msg;
+ uint32_t user_info_type;
+
+ DEBUG(SSSDBG_TRACE_LIBS, "Password change operation\n");
+
+ if (sss_authtok_get_type(kr->pd->authtok) == SSS_AUTHTOK_TYPE_PASSWORD) {
+ ret = sss_authtok_get_password(kr->pd->authtok, &password, NULL);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Failed to fetch current password [%d] %s.\n",
+ ret, strerror(ret));
+ return ERR_NO_CREDS;
+ }
+ }
+
+ if (!prelim) {
+ /* We do not need a password expiration warning here. */
+ prompter = sss_krb5_prompter;
+ }
+
+ set_changepw_options(kr->options);
+ sss_krb5_princ_realm(kr->ctx, kr->princ, &realm_name, &realm_length);
+ if (realm_length == 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "sss_krb5_princ_realm failed.\n");
+ return ERR_INTERNAL;
+ }
+
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "Attempting kinit for realm [%s]\n",realm_name);
+ kerr = kr->krb5_get_init_creds_password(kr->ctx, kr->creds, kr->princ,
+ password_or_responder(password),
+ prompter, kr, 0,
+ SSSD_KRB5_CHANGEPW_PRINCIPAL,
+ kr->options);
+ DEBUG(SSSDBG_TRACE_INTERNAL,
+ "chpass is%s using OTP\n", kr->otp ? "" : " not");
+ if (kerr != 0) {
+ ret = pack_user_info_chpass_error(kr->pd, "Old password not accepted.",
+ &msg_len, &msg);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "pack_user_info_chpass_error failed [%d]\n", ret);
+ } else {
+ ret = pam_add_response(kr->pd, SSS_PAM_USER_INFO, msg_len,
+ msg);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n");
+ }
+ }
+ return kerr;
+ }
+
+ sss_authtok_set_empty(kr->pd->authtok);
+
+ if (prelim) {
+ DEBUG(SSSDBG_TRACE_LIBS,
+ "Initial authentication for change password operation "
+ "successful.\n");
+ krb5_free_cred_contents(kr->ctx, kr->creds);
+ return EOK;
+ }
+
+ ret = sss_authtok_get_password(kr->pd->newauthtok, &newpassword, NULL);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Failed to fetch new password [%d] %s.\n",
+ ret, strerror(ret));
+ return ERR_NO_CREDS;
+ }
+
+ memset(&result_code_string, 0, sizeof(krb5_data));
+ memset(&result_string, 0, sizeof(krb5_data));
+ kerr = krb5_change_password(kr->ctx, kr->creds,
+ discard_const(newpassword), &result_code,
+ &result_code_string, &result_string);
+
+ if (kerr == KRB5_KDC_UNREACH) {
+ return ERR_NETWORK_IO;
+ }
+
+ if (kerr != 0 || result_code != 0) {
+ if (kerr != 0) {
+ KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
+ }
+
+ if (result_code_string.length > 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "krb5_change_password failed [%d][%.*s].\n", result_code,
+ result_code_string.length, result_code_string.data);
+ user_error_message = talloc_strndup(kr->pd, result_code_string.data,
+ result_code_string.length);
+ if (user_error_message == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strndup failed.\n");
+ }
+ }
+
+ if (result_string.length > 0 && result_string.data[0] != '\0') {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "krb5_change_password failed [%d][%.*s].\n", result_code,
+ result_string.length, result_string.data);
+ talloc_free(user_error_message);
+ user_error_message = talloc_strndup(kr->pd, result_string.data,
+ result_string.length);
+ if (user_error_message == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strndup failed.\n");
+ }
+ } else if (result_code == KRB5_KPASSWD_SOFTERROR) {
+ user_error_message = talloc_strdup(kr->pd, "Please make sure the "
+ "password meets the complexity constraints.");
+ if (user_error_message == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strndup failed.\n");
+ }
+ }
+
+ if (user_error_message != NULL) {
+ ret = pack_user_info_chpass_error(kr->pd, user_error_message,
+ &user_resp_len, &user_resp);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "pack_user_info_chpass_error failed [%d]\n", ret);
+ } else {
+ ret = pam_add_response(kr->pd, SSS_PAM_USER_INFO, user_resp_len,
+ user_resp);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n");
+ }
+ }
+ }
+
+ return ERR_CHPASS_FAILED;
+ }
+
+ krb5_free_cred_contents(kr->ctx, kr->creds);
+
+ if (kr->otp == true) {
+ user_info_type = SSS_PAM_USER_INFO_OTP_CHPASS;
+ ret = pam_add_response(kr->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");
+ /* Not fatal */
+ }
+
+ sss_authtok_set_empty(kr->pd->newauthtok);
+ return map_krb5_error(kerr);
+ }
+
+ /* We changed some of the GIC options for the password change, now we have
+ * to change them back to get a fresh TGT. */
+ revert_changepw_options(kr->cli_opts, kr->options);
+
+ ret = sss_authtok_set_password(kr->pd->authtok, newpassword, 0);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set password for fresh TGT.\n");
+ return ret;
+ }
+
+ kerr = get_and_save_tgt(kr, newpassword);
+
+ sss_authtok_set_empty(kr->pd->authtok);
+ sss_authtok_set_empty(kr->pd->newauthtok);
+
+ if (kerr == 0) {
+ kerr = k5c_attach_ccname_msg(kr);
+ }
+ return map_krb5_error(kerr);
+}
+
+static errno_t pam_add_prompting(struct krb5_req *kr)
+{
+ int ret;
+
+ /* add OTP tokeninfo message if available */
+ if (kr->otp) {
+ ret = k5c_attach_otp_info_msg(kr);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "k5c_attach_otp_info_msg failed.\n");
+ return ret;
+ }
+ }
+
+ if (kr->password_prompting) {
+ ret = pam_add_response(kr->pd, SSS_PASSWORD_PROMPTING, 0, NULL);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n");
+ return ret;
+ }
+ }
+
+ if (kr->pkinit_prompting) {
+ ret = pam_add_response(kr->pd, SSS_CERT_AUTH_PROMPTING, 0,
+ NULL);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n");
+ return ret;
+ }
+ }
+
+ return EOK;
+}
+
+static errno_t tgt_req_child(struct krb5_req *kr)
+{
+ const char *password = NULL;
+ krb5_error_code kerr;
+ int ret;
+
+ DEBUG(SSSDBG_TRACE_LIBS, "Attempting to get a TGT\n");
+
+ /* No password is needed for pre-auth or if we have 2FA or SC */
+ if (kr->pd->cmd != SSS_PAM_PREAUTH
+ && sss_authtok_get_type(kr->pd->authtok) != SSS_AUTHTOK_TYPE_2FA
+ && sss_authtok_get_type(kr->pd->authtok) != SSS_AUTHTOK_TYPE_2FA_SINGLE
+ && sss_authtok_get_type(kr->pd->authtok) != SSS_AUTHTOK_TYPE_SC_PIN
+ && sss_authtok_get_type(kr->pd->authtok)
+ != SSS_AUTHTOK_TYPE_SC_KEYPAD) {
+ ret = sss_authtok_get_password(kr->pd->authtok, &password, NULL);
+ switch (ret) {
+ case EOK:
+ break;
+
+ case EACCES:
+ DEBUG(SSSDBG_OP_FAILURE, "Invalid authtok type\n");
+ return ERR_INVALID_CRED_TYPE;
+ break;
+
+ default:
+ DEBUG(SSSDBG_OP_FAILURE, "No credentials available\n");
+ return ERR_NO_CREDS;
+ break;
+ }
+ }
+
+ kerr = get_and_save_tgt(kr, password);
+
+ if (kerr != KRB5KDC_ERR_KEY_EXP) {
+ if (kr->pd->cmd == SSS_PAM_PREAUTH) {
+ ret = pam_add_prompting(kr);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_prompting failed.\n");
+ goto done;
+ }
+ } else {
+ if (kerr == 0) {
+ kerr = k5c_attach_ccname_msg(kr);
+ }
+ }
+ ret = map_krb5_error(kerr);
+ goto done;
+ }
+
+ /* If the password is expired, the KDC will always return
+ KRB5KDC_ERR_KEY_EXP regardless if the supplied password is correct or
+ not. In general the password can still be used to get a changepw ticket.
+ So we validate the password by trying to get a changepw ticket. */
+ DEBUG(SSSDBG_TRACE_LIBS, "Password was expired\n");
+ kerr = sss_krb5_get_init_creds_opt_set_expire_callback(kr->ctx,
+ kr->options,
+ NULL, NULL);
+ if (kerr != 0) {
+ KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Failed to unset expire callback, continue ...\n");
+ }
+
+ set_changepw_options(kr->options);
+ kerr = kr->krb5_get_init_creds_password(kr->ctx, kr->creds, kr->princ_orig,
+ password_or_responder(password),
+ sss_krb5_prompter, kr, 0,
+ SSSD_KRB5_CHANGEPW_PRINCIPAL,
+ kr->options);
+
+ krb5_free_cred_contents(kr->ctx, kr->creds);
+
+ if (kr->pd->cmd == SSS_PAM_PREAUTH) {
+ /* Any errors are ignored during pre-auth, only data is collected to
+ * be send back to the client. Even if the password is expired we
+ * should now know which authentication methods are available to
+ * update the password. */
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "krb5_get_init_creds_password returned [%d] during pre-auth, "
+ "ignored.\n", kerr);
+ ret = pam_add_prompting(kr);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_prompting failed.\n");
+ goto done;
+ }
+ goto done;
+ }
+
+ if (kerr == 0) {
+ ret = ERR_CREDS_EXPIRED;
+
+ /* If the password is expired, we can safely remove the ccache from the
+ * cache and disk if it is not actively used anymore. This will allow
+ * to create a new random ccache if sshd with privilege separation is
+ * used. */
+ if (kr->old_cc_active == false && kr->old_ccname) {
+ ret = safe_remove_old_ccache_file(kr->old_ccname, NULL,
+ kr->uid, kr->gid);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Failed to remove old ccache file [%s], "
+ "please remove it manually.\n", kr->old_ccname);
+ }
+ ret = ERR_CREDS_EXPIRED_CCACHE;
+ }
+ } else {
+ ret = map_krb5_error(kerr);
+ }
+
+done:
+ sss_authtok_set_empty(kr->pd->authtok);
+ return ret;
+}
+
+static errno_t kuserok_child(struct krb5_req *kr)
+{
+ krb5_boolean access_allowed;
+ krb5_error_code kerr;
+
+ DEBUG(SSSDBG_TRACE_LIBS, "Verifying if principal can log in as user\n");
+
+ /* krb5_kuserok tries to verify that kr->pd->user is a locally known
+ * account, so we have to unset _SSS_LOOPS to make getpwnam() work. */
+ if (unsetenv("_SSS_LOOPS") != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Failed to unset _SSS_LOOPS, "
+ "krb5_kuserok will most certainly fail.\n");
+ }
+
+ kerr = krb5_set_default_realm(kr->ctx, kr->realm);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "krb5_set_default_realm failed, "
+ "krb5_kuserok may fail.\n");
+ }
+
+ access_allowed = krb5_kuserok(kr->ctx, kr->princ, kr->pd->user);
+ DEBUG(SSSDBG_TRACE_LIBS,
+ "Access was %s\n", access_allowed ? "allowed" : "denied");
+
+ if (access_allowed) {
+ return EOK;
+ }
+
+ return ERR_AUTH_DENIED;
+}
+
+static errno_t renew_tgt_child(struct krb5_req *kr)
+{
+ const char *ccname;
+ krb5_ccache ccache = NULL;
+ krb5_error_code kerr;
+ int ret;
+
+ DEBUG(SSSDBG_TRACE_LIBS, "Renewing a ticket\n");
+
+ ret = sss_authtok_get_ccfile(kr->pd->authtok, &ccname, NULL);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Unsupported authtok type for TGT renewal [%d].\n",
+ sss_authtok_get_type(kr->pd->authtok));
+ return ERR_INVALID_CRED_TYPE;
+ }
+
+ kerr = krb5_cc_resolve(kr->ctx, ccname, &ccache);
+ if (kerr != 0) {
+ KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
+ goto done;
+ }
+
+ kerr = krb5_get_renewed_creds(kr->ctx, kr->creds, kr->princ, ccache, NULL);
+ if (kerr != 0) {
+ goto done;
+ }
+
+ if (kr->validate) {
+ kerr = validate_tgt(kr);
+ if (kerr != 0) {
+ KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
+ goto done;
+ }
+
+ } else {
+ DEBUG(SSSDBG_CONF_SETTINGS, "TGT validation is disabled.\n");
+ }
+
+ kerr = krb5_cc_initialize(kr->ctx, ccache, kr->princ);
+ if (kerr != 0) {
+ KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
+ goto done;
+ }
+
+ kerr = krb5_cc_store_cred(kr->ctx, ccache, kr->creds);
+ if (kerr != 0) {
+ KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
+ goto done;
+ }
+
+ kerr = add_ticket_times_and_upn_to_response(kr);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "add_ticket_times_and_upn_to_response failed.\n");
+ }
+
+ kerr = k5c_attach_ccname_msg(kr);
+
+done:
+ krb5_free_cred_contents(kr->ctx, kr->creds);
+
+ if (ccache != NULL) {
+ krb5_cc_close(kr->ctx, ccache);
+ }
+
+ if (kerr == KRB5KRB_AP_ERR_TKT_EXPIRED) {
+ DEBUG(SSSDBG_TRACE_LIBS,
+ "Attempted to renew an expired TGT, changing the error code "
+ "to expired creds internally\n");
+ /* map_krb5_error() won't touch the SSSD-internal code */
+ kerr = ERR_CREDS_EXPIRED;
+ }
+
+ return map_krb5_error(kerr);
+}
+
+static errno_t create_empty_ccache(struct krb5_req *kr)
+{
+ krb5_creds *creds = NULL;
+ krb5_error_code kerr;
+
+ if (kr->old_cc_valid == false) {
+ DEBUG(SSSDBG_TRACE_LIBS, "Creating empty ccache\n");
+ kerr = create_empty_cred(kr->ctx, kr->princ, &creds);
+ if (kerr == 0) {
+ kerr = create_ccache(kr->ccname, creds);
+ }
+ } else {
+ DEBUG(SSSDBG_TRACE_LIBS, "Existing ccache still valid, reusing\n");
+ kerr = 0;
+ }
+
+ if (kerr == 0) {
+ kerr = k5c_attach_ccname_msg(kr);
+ }
+
+ krb5_free_creds(kr->ctx, creds);
+
+ return map_krb5_error(kerr);
+}
+
+static errno_t unpack_authtok(struct sss_auth_token *tok,
+ uint8_t *buf, size_t size, size_t *p)
+{
+ uint32_t auth_token_type;
+ uint32_t auth_token_length;
+ errno_t ret = EOK;
+
+ SAFEALIGN_COPY_UINT32_CHECK(&auth_token_type, buf + *p, size, p);
+ SAFEALIGN_COPY_UINT32_CHECK(&auth_token_length, buf + *p, size, p);
+ if (auth_token_length > (size - *p)) {
+ return EINVAL;
+ }
+ 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, (char *)(buf + *p), 0);
+ break;
+ case SSS_AUTHTOK_TYPE_CCFILE:
+ ret = sss_authtok_set_ccfile(tok, (char *)(buf + *p), 0);
+ break;
+ case SSS_AUTHTOK_TYPE_2FA_SINGLE:
+ ret = sss_authtok_set_2fa_single(tok, (char *)(buf + *p), 0);
+ break;
+ case SSS_AUTHTOK_TYPE_2FA:
+ 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, (buf + *p),
+ auth_token_length);
+ break;
+ default:
+ return EINVAL;
+ }
+
+ if (ret == EOK) {
+ *p += auth_token_length;
+ }
+ return ret;
+}
+
+static const char *krb5_child_command_to_str(int cmd)
+{
+ switch (cmd) {
+ case SSS_PAM_AUTHENTICATE:
+ return "auth";
+ case SSS_PAM_CHAUTHTOK:
+ return "password change";
+ case SSS_PAM_CHAUTHTOK_PRELIM:
+ return "password change checks";
+ case SSS_PAM_ACCT_MGMT:
+ return "account management";
+ case SSS_CMD_RENEW:
+ return "ticket renewal";
+ case SSS_PAM_PREAUTH:
+ return "pre-auth";
+ }
+
+ DEBUG(SSSDBG_MINOR_FAILURE, "Unexpected command %d\n", cmd);
+ return "-unexpected-";
+}
+
+static errno_t unpack_buffer(uint8_t *buf, size_t size,
+ struct krb5_req *kr, uint32_t *offline)
+{
+ size_t p = 0;
+ uint32_t len;
+ uint32_t validate;
+ uint32_t posix_domain;
+ uint32_t send_pac;
+ uint32_t use_enterprise_princ;
+ struct pam_data *pd;
+ errno_t ret;
+
+ DEBUG(SSSDBG_TRACE_LIBS, "total buffer size: [%zu]\n", size);
+
+ if (!offline || !kr) return EINVAL;
+
+ pd = create_pam_data(kr);
+ if (pd == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "create_pam_data failed.\n");
+ return ENOMEM;
+ }
+ kr->pd = pd;
+
+ SAFEALIGN_COPY_UINT32_CHECK(&pd->cmd, buf + p, size, &p);
+ SAFEALIGN_COPY_UINT32_CHECK(&kr->uid, buf + p, size, &p);
+ SAFEALIGN_COPY_UINT32_CHECK(&kr->gid, buf + p, size, &p);
+ SAFEALIGN_COPY_UINT32_CHECK(&validate, buf + p, size, &p);
+ kr->validate = (validate == 0) ? false : true;
+ SAFEALIGN_COPY_UINT32_CHECK(&posix_domain, buf + p, size, &p);
+ kr->posix_domain = (posix_domain == 0) ? false : true;
+ SAFEALIGN_COPY_UINT32_CHECK(offline, buf + p, size, &p);
+ SAFEALIGN_COPY_UINT32_CHECK(&send_pac, buf + p, size, &p);
+ kr->send_pac = (send_pac == 0) ? false : true;
+ SAFEALIGN_COPY_UINT32_CHECK(&use_enterprise_princ, buf + p, size, &p);
+ kr->use_enterprise_princ = (use_enterprise_princ == 0) ? false : true;
+ SAFEALIGN_COPY_UINT32_CHECK(&len, buf + p, size, &p);
+ if (len > size - p) return EINVAL;
+ kr->upn = talloc_strndup(kr, (char *)(buf + p), len);
+ if (kr->upn == NULL) return ENOMEM;
+ p += len;
+
+ DEBUG(SSSDBG_CONF_SETTINGS,
+ "cmd [%d (%s)] uid [%llu] gid [%llu] validate [%s] "
+ "enterprise principal [%s] offline [%s] UPN [%s]\n",
+ pd->cmd, krb5_child_command_to_str(pd->cmd),
+ (unsigned long long) kr->uid, (unsigned long long) kr->gid,
+ kr->validate ? "true" : "false",
+ kr->use_enterprise_princ ? "true" : "false",
+ *offline ? "true" : "false", kr->upn ? kr->upn : "none");
+
+ if (pd->cmd == SSS_PAM_AUTHENTICATE ||
+ pd->cmd == SSS_PAM_PREAUTH ||
+ pd->cmd == SSS_CMD_RENEW ||
+ pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM || pd->cmd == SSS_PAM_CHAUTHTOK) {
+ SAFEALIGN_COPY_UINT32_CHECK(&len, buf + p, size, &p);
+ if (len > size - p) return EINVAL;
+ kr->ccname = talloc_strndup(kr, (char *)(buf + p), len);
+ if (kr->ccname == NULL) return ENOMEM;
+ p += len;
+
+ SAFEALIGN_COPY_UINT32_CHECK(&len, buf + p, size, &p);
+ if (len > size - p) return EINVAL;
+
+ if (len > 0) {
+ kr->old_ccname = talloc_strndup(kr, (char *)(buf + p), len);
+ if (kr->old_ccname == NULL) return ENOMEM;
+ p += len;
+ } else {
+ DEBUG(SSSDBG_TRACE_INTERNAL, "No old ccache\n");
+ }
+
+ SAFEALIGN_COPY_UINT32_CHECK(&len, buf + p, size, &p);
+ if (len > size - p) return EINVAL;
+
+ if (len > 0) {
+ kr->keytab = talloc_strndup(kr, (char *)(buf + p), len);
+ p += len;
+ } else {
+ kr->keytab = NULL;
+ }
+
+ ret = unpack_authtok(pd->authtok, buf, size, &p);
+ if (ret) {
+ return ret;
+ }
+
+ DEBUG(SSSDBG_CONF_SETTINGS,
+ "ccname: [%s] old_ccname: [%s] keytab: [%s]\n",
+ kr->ccname,
+ kr->old_ccname ? kr->old_ccname : "not set",
+ kr->keytab ? kr->keytab : "not set");
+ } else {
+ kr->ccname = NULL;
+ kr->old_ccname = NULL;
+ kr->keytab = NULL;
+ sss_authtok_set_empty(pd->authtok);
+ }
+
+ if (pd->cmd == SSS_PAM_CHAUTHTOK) {
+ ret = unpack_authtok(pd->newauthtok, buf, size, &p);
+ if (ret) {
+ return ret;
+ }
+ } else {
+ sss_authtok_set_empty(pd->newauthtok);
+ }
+
+ if (pd->cmd == SSS_PAM_ACCT_MGMT) {
+ SAFEALIGN_COPY_UINT32_CHECK(&len, buf + p, size, &p);
+ if (len > size - p) return EINVAL;
+ pd->user = talloc_strndup(pd, (char *)(buf + p), len);
+ if (pd->user == NULL) return ENOMEM;
+ p += len;
+ DEBUG(SSSDBG_CONF_SETTINGS, "user: [%s]\n", pd->user);
+ } else {
+ pd->user = NULL;
+ }
+
+ return EOK;
+}
+
+static int krb5_cleanup(struct krb5_req *kr)
+{
+ if (kr == NULL) return EOK;
+
+ if (kr->options != NULL) {
+ sss_krb5_get_init_creds_opt_free(kr->ctx, kr->options);
+ }
+
+ if (kr->creds != NULL) {
+ krb5_free_cred_contents(kr->ctx, kr->creds);
+ krb5_free_creds(kr->ctx, kr->creds);
+ }
+ if (kr->name != NULL)
+ sss_krb5_free_unparsed_name(kr->ctx, kr->name);
+ if (kr->princ != NULL)
+ krb5_free_principal(kr->ctx, kr->princ);
+ if (kr->princ_orig != NULL)
+ krb5_free_principal(kr->ctx, kr->princ_orig);
+ if (kr->ctx != NULL)
+ krb5_free_context(kr->ctx);
+
+ memset(kr, 0, sizeof(struct krb5_req));
+
+ return EOK;
+}
+
+static krb5_error_code get_tgt_times(krb5_context ctx, const char *ccname,
+ krb5_principal server_principal,
+ krb5_principal client_principal,
+ sss_krb5_ticket_times *tgtt)
+{
+ krb5_error_code krberr;
+ krb5_ccache ccache = NULL;
+ krb5_creds mcred;
+ krb5_creds cred;
+
+ krberr = krb5_cc_resolve(ctx, ccname, &ccache);
+ if (krberr != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "krb5_cc_resolve failed.\n");
+ KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, krberr);
+ goto done;
+ }
+
+ memset(&mcred, 0, sizeof(mcred));
+ memset(&cred, 0, sizeof(mcred));
+
+ mcred.server = server_principal;
+ mcred.client = client_principal;
+
+ krberr = krb5_cc_retrieve_cred(ctx, ccache, 0, &mcred, &cred);
+ if (krberr == KRB5_FCC_NOFILE) {
+ DEBUG(SSSDBG_TRACE_LIBS, "FAST ccache must be recreated\n");
+ } else if (krberr != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "krb5_cc_retrieve_cred failed\n");
+ KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, krberr);
+ krberr = 0;
+ goto done;
+ }
+
+ tgtt->authtime = cred.times.authtime;
+ tgtt->starttime = cred.times.starttime;
+ tgtt->endtime = cred.times.endtime;
+ tgtt->renew_till = cred.times.renew_till;
+
+ krb5_free_cred_contents(ctx, &cred);
+
+ krberr = 0;
+
+done:
+ if (ccache != NULL) {
+ krb5_cc_close(ctx, ccache);
+ }
+
+ return krberr;
+}
+
+static krb5_error_code get_fast_ccache_with_anonymous_pkinit(krb5_context ctx,
+ uid_t fast_uid,
+ gid_t fast_gid,
+ bool posix_domain,
+ struct cli_opts *cli_opts,
+ krb5_keytab keytab,
+ krb5_principal client_princ,
+ char *ccname,
+ const char *realm)
+{
+ krb5_error_code kerr;
+ krb5_get_init_creds_opt *options;
+ struct sss_creds *saved_creds = NULL;
+ krb5_preauthtype pkinit = KRB5_PADATA_PK_AS_REQ;
+ krb5_creds creds = { 0 };
+
+ kerr = sss_krb5_get_init_creds_opt_alloc(ctx, &options);
+ if (kerr != 0) {
+ KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
+ return kerr;
+ }
+
+ krb5_get_init_creds_opt_set_tkt_life(options, 10 * 60);
+ krb5_get_init_creds_opt_set_renew_life(options, 0);
+ krb5_get_init_creds_opt_set_forwardable(options, 0);
+ krb5_get_init_creds_opt_set_proxiable(options, 0);
+ krb5_get_init_creds_opt_set_canonicalize(options, 1);
+ krb5_get_init_creds_opt_set_preauth_list(options, &pkinit, 1);
+
+ kerr = krb5_build_principal(ctx, &creds.server, strlen(realm), realm,
+ KRB5_TGS_NAME, realm, NULL);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_OP_FAILURE, "Failed to create principal.\n");
+ goto done;
+ }
+
+ creds.client = client_princ;
+
+ kerr = krb5_get_init_creds_password(ctx, &creds, client_princ, NULL,
+ sss_krb5_prompter, NULL, 0, NULL,
+ options);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Failed to get FAST credential with anonymous PKINIT.\n");
+ goto done;
+ }
+
+ kerr = switch_creds(NULL, fast_uid, fast_gid, 0, NULL, &saved_creds);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Failed to switch credentials to store FAST ccache with "
+ "expected permissions.\n");
+ goto done;
+ }
+
+ kerr = create_ccache(ccname, &creds);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_OP_FAILURE, "Failed to store FAST ccache.\n");
+ goto done;
+ }
+
+ kerr = restore_creds(saved_creds);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Failed to restore credentials, krb5_child might run with wrong "
+ "permissions, aborting.\n");
+ goto done;
+ }
+
+done:
+ sss_krb5_get_init_creds_opt_free(ctx, options);
+ talloc_free(saved_creds);
+
+ return kerr;
+}
+
+static krb5_error_code get_fast_ccache_with_keytab(krb5_context ctx,
+ uid_t fast_uid,
+ gid_t fast_gid,
+ bool posix_domain,
+ struct cli_opts *cli_opts,
+ krb5_keytab keytab,
+ krb5_principal client_princ,
+ char *ccname)
+{
+ krb5_error_code kerr;
+ pid_t fchild_pid;
+ int status;
+
+ fchild_pid = fork();
+ switch (fchild_pid) {
+ case -1:
+ DEBUG(SSSDBG_CRIT_FAILURE, "fork failed\n");
+ return EIO;
+ case 0:
+ /* Child */
+ debug_prg_name = talloc_asprintf(NULL, "krb5_child[%d]", getpid());
+ if (debug_prg_name == NULL) {
+ debug_prg_name = "krb5_child";
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n");
+ /* Try to carry on */
+ }
+
+ kerr = k5c_become_user(fast_uid, fast_gid, posix_domain);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "become_user failed: %d\n", kerr);
+ exit(1);
+ }
+ DEBUG(SSSDBG_TRACE_INTERNAL,
+ "Running as [%"SPRIuid"][%"SPRIgid"].\n", geteuid(), getegid());
+
+ kerr = get_and_save_tgt_with_keytab(ctx, cli_opts, client_princ,
+ keytab, ccname);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "get_and_save_tgt_with_keytab failed: %d\n", kerr);
+ exit(2);
+ }
+ exit(0);
+ default:
+ /* Parent */
+ do {
+ errno = 0;
+ kerr = waitpid(fchild_pid, &status, 0);
+ } while (kerr == -1 && errno == EINTR);
+
+ if (kerr > 0) {
+ if (WIFEXITED(status)) {
+ kerr = WEXITSTATUS(status);
+ /* Don't blindly fail if the child fails, but check
+ * the ccache again */
+ if (kerr != 0) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "Creating FAST ccache failed, krb5_child will "
+ "likely fail!\n");
+ }
+ } else {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "krb5_child subprocess %d terminated unexpectedly\n",
+ fchild_pid);
+ }
+ } else {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Failed to wait for child %d\n", fchild_pid);
+ /* Let the code re-check the TGT times and fail if we
+ * can't find the updated principal */
+ }
+ }
+
+ return 0;
+}
+
+static krb5_error_code check_fast_ccache(TALLOC_CTX *mem_ctx,
+ krb5_context ctx,
+ uid_t fast_uid,
+ gid_t fast_gid,
+ bool posix_domain,
+ struct cli_opts *cli_opts,
+ const char *primary,
+ const char *realm,
+ const char *keytab_name,
+ char **fast_ccname)
+{
+ TALLOC_CTX *tmp_ctx = NULL;
+ krb5_error_code kerr;
+ char *ccname;
+ char *server_name;
+ sss_krb5_ticket_times tgtt;
+ krb5_keytab keytab = NULL;
+ krb5_principal client_princ = NULL;
+ krb5_principal server_princ = NULL;
+ krb5_principal client_search_princ = NULL;
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new failed.\n");
+ return ENOMEM;
+ }
+
+ ccname = talloc_asprintf(tmp_ctx, "FILE:%s/fast_ccache_%s", DB_PATH, realm);
+ if (ccname == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n");
+ kerr = ENOMEM;
+ goto done;
+ }
+
+ if (cli_opts->fast_use_anonymous_pkinit) {
+ kerr = krb5_build_principal(ctx, &client_princ, strlen(realm), realm,
+ KRB5_WELLKNOWN_NAMESTR,
+ KRB5_ANONYMOUS_PRINCSTR, NULL);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "Failed to create anonymous PKINIT principal.\n");
+ goto done;
+ }
+
+ /* Anonymous pkinit is using the canonical principal
+ * WELLKNOWN/ANONYMOUS@WELLKNOWN:ANONYMOUS so we need an additional
+ * client_search_princ to find it in the ccache to determine the
+ * lifetime. */
+ kerr = krb5_build_principal(ctx, &client_search_princ,
+ strlen(KRB5_ANONYMOUS_REALMSTR),
+ KRB5_ANONYMOUS_REALMSTR,
+ KRB5_WELLKNOWN_NAMESTR,
+ KRB5_ANONYMOUS_PRINCSTR, NULL);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "Failed to create anonymous PKINIT principal.\n");
+ goto done;
+ }
+ } else {
+ if (keytab_name != NULL) {
+ kerr = krb5_kt_resolve(ctx, keytab_name, &keytab);
+ } else {
+ kerr = krb5_kt_default(ctx, &keytab);
+ }
+ if (kerr) {
+ const char *__err_msg = sss_krb5_get_error_message(ctx, kerr);
+ DEBUG(SSSDBG_FATAL_FAILURE,
+ "Failed to read keytab file [%s]: %s\n",
+ sss_printable_keytab_name(ctx, keytab_name),
+ __err_msg);
+ sss_krb5_free_error_message(ctx, __err_msg);
+ goto done;
+ }
+
+ kerr = find_principal_in_keytab(ctx, keytab, primary, realm, &client_princ);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "find_principal_in_keytab failed for principal %s@%s.\n",
+ primary, realm);
+ goto done;
+ }
+ }
+
+ server_name = talloc_asprintf(tmp_ctx, "krbtgt/%s@%s", realm, realm);
+ if (server_name == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n");
+ kerr = ENOMEM;
+ goto done;
+ }
+
+ kerr = krb5_parse_name(ctx, server_name, &server_princ);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "krb5_parse_name failed.\n");
+ goto done;
+ }
+
+ memset(&tgtt, 0, sizeof(tgtt));
+ kerr = get_tgt_times(ctx, ccname, server_princ,
+ client_search_princ != NULL ? client_search_princ
+ : client_princ,
+ &tgtt);
+ if (kerr == 0) {
+ if (tgtt.endtime > time(NULL)) {
+ DEBUG(SSSDBG_FUNC_DATA, "FAST TGT is still valid.\n");
+ goto done;
+ }
+ }
+
+ /* Need to recreate the FAST ccache */
+ if (cli_opts->fast_use_anonymous_pkinit) {
+ kerr = get_fast_ccache_with_anonymous_pkinit(ctx, fast_uid, fast_gid,
+ posix_domain, cli_opts,
+ keytab, client_princ,
+ ccname, realm);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_MINOR_FAILURE, "Creating FAST ccache with anonymous "
+ "PKINIT failed, krb5_child will "
+ "likely fail!\n");
+ }
+ } else {
+ kerr = get_fast_ccache_with_keytab(ctx, fast_uid, fast_gid, posix_domain,
+ cli_opts, keytab, client_princ, ccname);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_MINOR_FAILURE, "Creating FAST ccache with keytab failed, "
+ "krb5_child will likely fail!\n");
+ }
+ }
+
+ /* Check the ccache times again. Should be updated ... */
+ memset(&tgtt, 0, sizeof(tgtt));
+ kerr = get_tgt_times(ctx, ccname, server_princ,
+ client_search_princ != NULL ? client_search_princ
+ : client_princ,
+ &tgtt);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_OP_FAILURE, "get_tgt_times() failed\n");
+ goto done;
+ }
+
+ if (tgtt.endtime < time(NULL)) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "FAST TGT was renewed but is already expired, please check that "
+ "time is synchronized with server.\n");
+ kerr = ERR_CREDS_EXPIRED;
+ goto done;
+ }
+ DEBUG(SSSDBG_FUNC_DATA, "FAST TGT was successfully recreated!\n");
+
+done:
+ if (client_princ != NULL) {
+ krb5_free_principal(ctx, client_princ);
+ }
+ if (client_search_princ != NULL) {
+ krb5_free_principal(ctx, client_search_princ);
+ }
+ if (server_princ != NULL) {
+ krb5_free_principal(ctx, server_princ);
+ }
+
+ if (kerr == 0) {
+ *fast_ccname = talloc_steal(mem_ctx, ccname);
+ }
+ talloc_free(tmp_ctx);
+
+ if (keytab != NULL) {
+ krb5_kt_close(ctx, keytab);
+ }
+
+ return kerr;
+}
+
+static errno_t k5c_recv_data(struct krb5_req *kr, int fd, uint32_t *offline)
+{
+ uint8_t buf[IN_BUF_SIZE];
+ ssize_t len;
+ errno_t ret;
+
+ errno = 0;
+ len = sss_atomic_read_safe_s(fd, buf, IN_BUF_SIZE, NULL);
+ if (len == -1) {
+ ret = errno;
+ ret = (ret == 0) ? EINVAL: ret;
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "read failed [%d][%s].\n", ret, strerror(ret));
+ return ret;
+ }
+
+ ret = unpack_buffer(buf, len, kr, offline);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "unpack_buffer failed.\n");
+ }
+
+ return ret;
+}
+
+static int k5c_setup_fast(struct krb5_req *kr, bool demand)
+{
+ krb5_principal fast_princ_struct;
+ krb5_data *realm_data;
+ char *fast_principal_realm;
+ char *fast_principal;
+ krb5_error_code kerr;
+ char *tmp_str = NULL;
+ char *new_ccname;
+
+ if (kr->cli_opts->fast_principal) {
+ DEBUG(SSSDBG_CONF_SETTINGS, "Fast principal is set to [%s]\n",
+ kr->cli_opts->fast_principal);
+ kerr = krb5_parse_name(kr->ctx, kr->cli_opts->fast_principal,
+ &fast_princ_struct);
+ if (kerr) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "krb5_parse_name failed.\n");
+ return kerr;
+ }
+ kerr = sss_krb5_unparse_name_flags(kr->ctx, fast_princ_struct,
+ KRB5_PRINCIPAL_UNPARSE_NO_REALM,
+ &tmp_str);
+ if (kerr) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "sss_krb5_unparse_name_flags failed.\n");
+ return kerr;
+ }
+ fast_principal = talloc_strdup(kr, tmp_str);
+ if (!fast_principal) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup failed.\n");
+ return KRB5KRB_ERR_GENERIC;
+ }
+ free(tmp_str);
+ realm_data = krb5_princ_realm(kr->ctx, fast_princ_struct);
+ fast_principal_realm = talloc_asprintf(kr, "%.*s", realm_data->length,
+ realm_data->data);
+ if (!fast_principal_realm) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n");
+ return ENOMEM;
+ }
+ } else {
+ fast_principal_realm = kr->realm;
+ fast_principal = NULL;
+ }
+
+ kerr = check_fast_ccache(kr, kr->ctx, kr->fast_uid, kr->fast_gid,
+ kr->posix_domain, kr->cli_opts,
+ fast_principal, fast_principal_realm,
+ kr->keytab, &kr->fast_ccname);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "check_fast_ccache failed.\n");
+ KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
+ return kerr;
+ }
+
+ kerr = copy_ccache_into_memory(kr, kr->ctx, kr->fast_ccname, &new_ccname);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "copy_ccache_into_memory failed.\n");
+ return kerr;
+ }
+
+ talloc_free(kr->fast_ccname);
+ kr->fast_ccname = new_ccname;
+
+ kerr = sss_krb5_get_init_creds_opt_set_fast_ccache_name(kr->ctx,
+ kr->options,
+ kr->fast_ccname);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "sss_krb5_get_init_creds_opt_set_fast_ccache_name "
+ "failed.\n");
+ KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
+ return kerr;
+ }
+
+ if (demand) {
+ kerr = sss_krb5_get_init_creds_opt_set_fast_flags(kr->ctx,
+ kr->options,
+ SSS_KRB5_FAST_REQUIRED);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "sss_krb5_get_init_creds_opt_set_fast_flags "
+ "failed.\n");
+ KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
+ return kerr;
+ }
+ }
+
+ return EOK;
+}
+
+static errno_t check_use_fast(const char *use_fast_str,
+ enum k5c_fast_opt *_fast_val)
+{
+ enum k5c_fast_opt fast_val;
+
+ if (use_fast_str == NULL || strcasecmp(use_fast_str, "never") == 0) {
+ DEBUG(SSSDBG_CONF_SETTINGS, "Not using FAST.\n");
+ fast_val = K5C_FAST_NEVER;
+ } else if (strcasecmp(use_fast_str, "try") == 0) {
+ fast_val = K5C_FAST_TRY;
+ } else if (strcasecmp(use_fast_str, "demand") == 0) {
+ fast_val = K5C_FAST_DEMAND;
+ } else {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Unsupported value [%s] for krb5_use_fast.\n",
+ use_fast_str);
+ return EINVAL;
+ }
+
+ *_fast_val = fast_val;
+ return EOK;
+}
+
+static errno_t old_ccache_valid(struct krb5_req *kr, bool *_valid)
+{
+ errno_t ret;
+ bool valid;
+
+ valid = false;
+
+ ret = sss_krb5_cc_verify_ccache(kr->old_ccname,
+ kr->uid, kr->gid,
+ kr->realm, kr->upn);
+ switch (ret) {
+ case ERR_NOT_FOUND:
+ case ENOENT:
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "Saved ccache %s doesn't exist, ignoring\n", kr->old_ccname);
+ break;
+ case EINVAL:
+ /* cache found but no TGT or expired */
+ case EOK:
+ valid = true;
+ break;
+ default:
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Cannot check if saved ccache %s is valid\n",
+ kr->old_ccname);
+ return ret;
+ }
+
+ *_valid = valid;
+ return EOK;
+}
+
+static int k5c_check_old_ccache(struct krb5_req *kr)
+{
+ errno_t ret;
+
+ if (kr->old_ccname) {
+ ret = old_ccache_valid(kr, &kr->old_cc_valid);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "old_ccache_valid failed.\n");
+ return ret;
+ }
+
+ ret = check_if_uid_is_active(kr->uid, &kr->old_cc_active);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "check_if_uid_is_active failed.\n");
+ return ret;
+ }
+
+ DEBUG(SSSDBG_TRACE_ALL,
+ "Ccache_file is [%s] and is %s active and TGT is %s valid.\n",
+ kr->old_ccname ? kr->old_ccname : "not set",
+ kr->old_cc_active ? "" : "not",
+ kr->old_cc_valid ? "" : "not");
+ }
+
+ return EOK;
+}
+
+static int k5c_precreate_ccache(struct krb5_req *kr, uint32_t offline)
+{
+ errno_t ret;
+
+ /* The ccache file should be (re)created if one of the following conditions
+ * is true:
+ * - it doesn't exist (kr->old_ccname == NULL)
+ * - the backend is online and the current ccache file is not used, i.e
+ * the related user is currently not logged in and it is not a renewal
+ * request
+ * (offline && !kr->old_cc_active && kr->pd->cmd != SSS_CMD_RENEW)
+ * - the backend is offline and the current cache file not used and
+ * it does not contain a valid TGT
+ * (offline && !kr->old_cc_active && !kr->valid_tgt)
+ */
+ if (kr->old_ccname == NULL ||
+ (offline && !kr->old_cc_active && !kr->old_cc_valid) ||
+ (!offline && !kr->old_cc_active && kr->pd->cmd != SSS_CMD_RENEW)) {
+ DEBUG(SSSDBG_TRACE_ALL, "Recreating ccache\n");
+
+ ret = sss_krb5_precreate_ccache(kr->ccname, kr->uid, kr->gid);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "ccache creation failed.\n");
+ return ret;
+ }
+ } else {
+ /* We can reuse the old ccache */
+ kr->ccname = kr->old_ccname;
+ }
+
+ return EOK;
+}
+
+static int k5c_ccache_setup(struct krb5_req *kr, uint32_t offline)
+{
+ errno_t ret;
+
+ if (kr->pd->cmd == SSS_PAM_ACCT_MGMT) {
+ return EOK;
+ }
+
+ ret = k5c_check_old_ccache(kr);
+ if (ret != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Cannot check old ccache [%s]: [%d][%s]. " \
+ "Assuming old cache is invalid " \
+ "and not used.\n",
+ kr->old_ccname, ret, sss_strerror(ret));
+ }
+
+ /* Pre-creating the ccache must be done as root, otherwise we can't mkdir
+ * some of the DIR: cache components. One example is /run/user/$UID because
+ * logind doesn't create the directory until the session phase, whereas
+ * we need the directory during the auth phase already
+ */
+ ret = k5c_precreate_ccache(kr, offline);
+ if (ret != 0) {
+ DEBUG(SSSDBG_OP_FAILURE, "Cannot precreate ccache\n");
+ return ret;
+ }
+
+ return EOK;
+}
+
+static int k5c_setup(struct krb5_req *kr, uint32_t offline)
+{
+ krb5_error_code kerr;
+ int parse_flags;
+
+ /* Set the global error context */
+ krb5_error_ctx = kr->ctx;
+
+ if (debug_level & SSSDBG_TRACE_ALL) {
+ kerr = sss_child_set_krb5_tracing(kr->ctx);
+ if (kerr != 0) {
+ KRB5_CHILD_DEBUG(SSSDBG_MINOR_FAILURE, kerr);
+ return EIO;
+ }
+ }
+
+ /* Enterprise principals require that a default realm is available. To
+ * make SSSD more robust in the case that the default realm option is
+ * missing in krb5.conf or to allow SSSD to work with multiple unconnected
+ * realms (e.g. AD domains without trust between them) the default realm
+ * will be set explicitly. */
+ if (kr->use_enterprise_princ) {
+ kerr = krb5_set_default_realm(kr->ctx, kr->realm);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "krb5_set_default_realm failed.\n");
+ }
+ }
+
+ parse_flags = kr->use_enterprise_princ ? KRB5_PRINCIPAL_PARSE_ENTERPRISE : 0;
+ kerr = sss_krb5_parse_name_flags(kr->ctx, kr->upn, parse_flags, &kr->princ);
+ if (kerr != 0) {
+ KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
+ return kerr;
+ }
+
+ kerr = krb5_parse_name(kr->ctx, kr->upn, &kr->princ_orig);
+ if (kerr != 0) {
+ KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
+ return kerr;
+ }
+
+ kerr = krb5_unparse_name(kr->ctx, kr->princ, &kr->name);
+ if (kerr != 0) {
+ KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
+ return kerr;
+ }
+
+ kr->creds = calloc(1, sizeof(krb5_creds));
+ if (kr->creds == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "calloc failed.\n");
+ return ENOMEM;
+ }
+
+#ifdef HAVE_KRB5_GET_INIT_CREDS_OPT_SET_RESPONDER
+ kerr = krb5_get_init_creds_opt_set_responder(kr->ctx, kr->options,
+ sss_krb5_responder, kr);
+ if (kerr != 0) {
+ KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
+ return kerr;
+ }
+#endif
+
+#ifdef HAVE_KRB5_GET_INIT_CREDS_OPT_SET_CHANGE_PASSWORD_PROMPT
+ /* A prompter is used to catch messages about when a password will
+ * expire. The library shall not use the prompter to ask for a new password
+ * but shall return KRB5KDC_ERR_KEY_EXP. */
+ krb5_get_init_creds_opt_set_change_password_prompt(kr->options, 0);
+#endif
+
+ kerr = set_lifetime_options(kr->cli_opts, kr->options);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_OP_FAILURE, "set_lifetime_options failed.\n");
+ return kerr;
+ }
+
+ if (!offline) {
+ set_canonicalize_option(kr->cli_opts, kr->options);
+ }
+
+/* TODO: set options, e.g.
+ * krb5_get_init_creds_opt_set_forwardable
+ * krb5_get_init_creds_opt_set_proxiable
+ * krb5_get_init_creds_opt_set_etype_list
+ * krb5_get_init_creds_opt_set_address_list
+ * krb5_get_init_creds_opt_set_preauth_list
+ * krb5_get_init_creds_opt_set_salt
+ * krb5_get_init_creds_opt_set_change_password_prompt
+ * krb5_get_init_creds_opt_set_pa
+ */
+
+ return kerr;
+}
+
+static krb5_error_code check_keytab_name(struct krb5_req *kr)
+{
+ krb5_error_code kerr;
+ char krb5_conf_keytab[MAX_KEYTAB_NAME_LEN];
+ char *path_start = NULL;
+
+ if (kr->keytab == NULL && (
+ kr->pd->cmd == SSS_PAM_AUTHENTICATE ||
+ kr->pd->cmd == SSS_PAM_PREAUTH ||
+ kr->pd->cmd == SSS_CMD_RENEW ||
+ kr->pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM ||
+ kr->pd->cmd == SSS_PAM_CHAUTHTOK)) {
+
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "Missing krb5_keytab option for domain, looking for default one\n");
+
+ kerr = krb5_kt_default_name(kr->ctx, krb5_conf_keytab, sizeof(krb5_conf_keytab));
+ if (kerr != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Unable to get default keytab location from krb.conf\n");
+ return kerr;
+ }
+
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "krb5_kt_default_name() returned: %s\n",
+ krb5_conf_keytab);
+
+ /* krb5_kt_default_name() can return file path with "FILE:" prefix,
+ it need to be removed */
+ if (0 == strncmp(krb5_conf_keytab, "FILE:", strlen("FILE:"))) {
+ path_start = krb5_conf_keytab + strlen("FILE:");
+ } else {
+ path_start = krb5_conf_keytab;
+ }
+
+ kr->keytab = talloc_strndup(kr->pd, path_start, strlen(path_start));
+
+ DEBUG(SSSDBG_TRACE_FUNC, "krb5_child will default to: %s\n", path_start);
+ }
+
+ return 0;
+}
+
+static krb5_error_code privileged_krb5_setup(struct krb5_req *kr,
+ uint32_t offline)
+{
+ krb5_error_code kerr;
+ int ret;
+ char *mem_keytab;
+
+ kr->realm = kr->cli_opts->realm;
+ if (kr->realm == NULL) {
+ DEBUG(SSSDBG_MINOR_FAILURE, "Realm not available.\n");
+ }
+
+ kerr = krb5_init_context(&kr->ctx);
+ if (kerr != 0) {
+ KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
+ return kerr;
+ }
+
+ kerr = check_keytab_name(kr);
+ if (kerr != 0) {
+ KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
+ return kerr;
+ }
+
+ kerr = sss_krb5_get_init_creds_opt_alloc(kr->ctx, &kr->options);
+ if (kerr != 0) {
+ KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
+ return kerr;
+ }
+
+ ret = check_use_fast(kr->cli_opts->use_fast_str, &kr->fast_val);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "check_use_fast failed.\n");
+ return ret;
+ }
+
+ /* For ccache types FILE: and DIR: we might need to create some directory
+ * components as root. Cache files are not needed during preauth. */
+ if (kr->pd->cmd != SSS_PAM_PREAUTH) {
+ ret = k5c_ccache_setup(kr, offline);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "k5c_ccache_setup failed.\n");
+ return ret;
+ }
+ }
+
+ if (!(offline ||
+ (kr->fast_val == K5C_FAST_NEVER && kr->validate == false))) {
+ /* A Keytab is not used if fast with anonymous pkinit is used (and validate is false)*/
+ if (!(kr->cli_opts->fast_use_anonymous_pkinit == true && kr->validate == false)) {
+ kerr = copy_keytab_into_memory(kr, kr->ctx, kr->keytab, &mem_keytab,
+ NULL);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_OP_FAILURE, "copy_keytab_into_memory failed.\n");
+ return kerr;
+ }
+
+ talloc_free(kr->keytab);
+ kr->keytab = mem_keytab;
+ }
+
+ if (kr->fast_val != K5C_FAST_NEVER) {
+ kerr = k5c_setup_fast(kr, kr->fast_val == K5C_FAST_DEMAND);
+ if (kerr != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "Cannot set up FAST\n");
+ return kerr;
+ }
+ }
+ }
+
+ if (kr->send_pac) {
+ ret = sss_pac_check_and_open();
+ if (ret != EOK) {
+ DEBUG(SSSDBG_MINOR_FAILURE, "Cannot open the PAC responder socket\n");
+ /* Not fatal */
+ }
+ }
+
+ return 0;
+}
+
+static void try_open_krb5_conf(void)
+{
+ int fd;
+ int ret;
+
+ fd = open("/etc/krb5.conf", O_RDONLY);
+ if (fd != -1) {
+ close(fd);
+ } else {
+ ret = errno;
+ if (ret == EACCES || ret == EPERM) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "User with uid:%"SPRIuid" gid:%"SPRIgid" cannot read "
+ "/etc/krb5.conf. It might cause problems\n",
+ geteuid(), getegid());
+ } else {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "Cannot open /etc/krb5.conf [%d]: %s\n",
+ ret, strerror(ret));
+ }
+ }
+}
+
+int main(int argc, const char *argv[])
+{
+ struct krb5_req *kr = NULL;
+ uint32_t offline;
+ int opt;
+ poptContext pc;
+ int dumpable = 1;
+ int debug_fd = -1;
+ const char *opt_logger = NULL;
+ errno_t ret;
+ krb5_error_code kerr;
+ uid_t fast_uid = 0;
+ gid_t fast_gid = 0;
+ long chain_id = 0;
+ struct cli_opts cli_opts = { 0 };
+ int sss_creds_password = 0;
+ long dummy_long = 0;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ SSSD_DEBUG_OPTS
+ {"dumpable", 0, POPT_ARG_INT, &dumpable, 0,
+ _("Allow core dumps"), NULL },
+ {"debug-fd", 0, POPT_ARG_INT, &debug_fd, 0,
+ _("An open file descriptor for the debug logs"), NULL},
+ SSSD_LOGGER_OPTS
+ {CHILD_OPT_FAST_CCACHE_UID, 0, POPT_ARG_INT, &fast_uid, 0,
+ _("The user to create FAST ccache as"), NULL},
+ {CHILD_OPT_FAST_CCACHE_GID, 0, POPT_ARG_INT, &fast_gid, 0,
+ _("The group to create FAST ccache as"), NULL},
+ {CHILD_OPT_FAST_USE_ANONYMOUS_PKINIT, 0, POPT_ARG_NONE, NULL, 'A',
+ _("Use anonymous PKINIT to request FAST armor ticket"), NULL},
+ {CHILD_OPT_REALM, 0, POPT_ARG_STRING, &cli_opts.realm, 0,
+ _("Kerberos realm to use"), NULL},
+ {CHILD_OPT_LIFETIME, 0, POPT_ARG_STRING, &cli_opts.lifetime, 0,
+ _("Requested lifetime of the ticket"), NULL},
+ {CHILD_OPT_RENEWABLE_LIFETIME, 0, POPT_ARG_STRING, &cli_opts.rtime, 0,
+ _("Requested renewable lifetime of the ticket"), NULL},
+ {CHILD_OPT_USE_FAST, 0, POPT_ARG_STRING, &cli_opts.use_fast_str, 0,
+ _("FAST options ('never', 'try', 'demand')"), NULL},
+ {CHILD_OPT_FAST_PRINCIPAL, 0, POPT_ARG_STRING,
+ &cli_opts.fast_principal, 0,
+ _("Specifies the server principal to use for FAST"), NULL},
+ {CHILD_OPT_CANONICALIZE, 0, POPT_ARG_NONE, NULL, 'C',
+ _("Requests canonicalization of the principal name"), NULL},
+ {CHILD_OPT_SSS_CREDS_PASSWORD, 0, POPT_ARG_NONE, &sss_creds_password,
+ 0, _("Use custom version of krb5_get_init_creds_password"), NULL},
+ {CHILD_OPT_CHAIN_ID, 0, POPT_ARG_LONG, &chain_id,
+ 0, _("Tevent chain ID used for logging purposes"), NULL},
+ {CHILD_OPT_CHECK_PAC, 0, POPT_ARG_LONG, &dummy_long, 0,
+ _("Check PAC flags"), NULL},
+ POPT_TABLEEND
+ };
+
+ /* Set debug level to invalid value so we can decide if -d 0 was used. */
+ debug_level = SSSDBG_INVALID;
+
+ cli_opts.canonicalize = false;
+ cli_opts.fast_use_anonymous_pkinit = false;
+
+ pc = poptGetContext(argv[0], argc, argv, long_options, 0);
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ switch(opt) {
+ case 'A':
+ cli_opts.fast_use_anonymous_pkinit = true;
+ break;
+ case 'C':
+ cli_opts.canonicalize = true;
+ break;
+ default:
+ fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0), poptStrerror(opt));
+ poptPrintUsage(pc, stderr, 0);
+ _exit(-1);
+ }
+ }
+
+ cli_opts.check_pac_flags = 0;
+ if (dummy_long >= 0 && dummy_long <= UINT32_MAX) {
+ cli_opts.check_pac_flags = (uint32_t) dummy_long;
+ } else {
+ fprintf(stderr, "\nInvalid value [%ld] of check-pac option\n\n",
+ dummy_long);
+ poptPrintUsage(pc, stderr, 0);
+ _exit(-1);
+ }
+
+ poptFreeContext(pc);
+
+ prctl(PR_SET_DUMPABLE, (dumpable == 0) ? 0 : 1);
+
+ debug_prg_name = talloc_asprintf(NULL, "krb5_child[%d]", getpid());
+ if (!debug_prg_name) {
+ debug_prg_name = "krb5_child";
+ ERROR("talloc_asprintf failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ if (debug_fd != -1) {
+ opt_logger = sss_logger_str[FILES_LOGGER];
+ ret = set_debug_file_from_fd(debug_fd);
+ if (ret != EOK) {
+ opt_logger = sss_logger_str[STDERR_LOGGER];
+ ERROR("set_debug_file_from_fd failed.\n");
+ }
+ }
+
+ sss_chain_id_set_format(DEBUG_CHAIN_ID_FMT_RID);
+ sss_chain_id_set((uint64_t)chain_id);
+
+ DEBUG_INIT(debug_level, opt_logger);
+
+ DEBUG(SSSDBG_TRACE_FUNC, "krb5_child started.\n");
+
+ kr = talloc_zero(NULL, struct krb5_req);
+ if (kr == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+ talloc_steal(kr, debug_prg_name);
+
+ kr->fast_uid = fast_uid;
+ kr->fast_gid = fast_gid;
+ kr->cli_opts = &cli_opts;
+ if (sss_creds_password != 0) {
+ kr->krb5_get_init_creds_password = sss_krb5_get_init_creds_password;
+ } else {
+ kr->krb5_get_init_creds_password = krb5_get_init_creds_password;
+ }
+
+ ret = k5c_recv_data(kr, STDIN_FILENO, &offline);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ if (cli_opts.check_pac_flags != 0 && !kr->validate) {
+ DEBUG(SSSDBG_IMPORTANT_INFO,
+ "PAC check is requested but krb5_validate is set to false. "
+ "PAC checks will be skipped.\n");
+ }
+
+ kerr = privileged_krb5_setup(kr, offline);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "privileged_krb5_setup failed.\n");
+ ret = EFAULT;
+ goto done;
+ }
+
+ /* For PKINIT we might need access to the pcscd socket which by default
+ * is only allowed for authenticated users. Since PKINIT is part of
+ * the authentication and the user is not authenticated yet, we have
+ * to use different privileges and can only drop it only after the TGT is
+ * received. The fast_uid and fast_gid are the IDs the backend is running
+ * with. This can be either root or the 'sssd' user. Root is allowed by
+ * default and the 'sssd' user is allowed with the help of the
+ * sssd-pcsc.rules policy-kit rule. So those IDs are a suitable choice. We
+ * can only call switch_creds() because after the TGT is returned we have
+ * to switch to the IDs of the user to store the TGT.
+ * If we are offline we have to switch to the user's credentials directly
+ * to make sure the empty ccache is created with the expected
+ * ownership. */
+ if (IS_SC_AUTHTOK(kr->pd->authtok) && !offline) {
+ kerr = switch_creds(kr, kr->fast_uid, kr->fast_gid, 0, NULL,
+ &kr->pcsc_saved_creds);
+ } else {
+ kerr = k5c_become_user(kr->uid, kr->gid, kr->posix_domain);
+ }
+ if (kerr != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "become_user failed.\n");
+ ret = EFAULT;
+ goto done;
+ }
+
+ DEBUG(SSSDBG_TRACE_INTERNAL,
+ "Running as [%"SPRIuid"][%"SPRIgid"].\n", geteuid(), getegid());
+
+ try_open_krb5_conf();
+
+ ret = k5c_setup(kr, offline);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "k5c_setup failed.\n");
+ goto done;
+ }
+
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "Will perform %s\n", krb5_child_command_to_str(kr->pd->cmd));
+ switch(kr->pd->cmd) {
+ case SSS_PAM_AUTHENTICATE:
+ /* If we are offline, we need to create an empty ccache file */
+ if (offline) {
+ DEBUG(SSSDBG_TRACE_FUNC, "Will perform offline auth\n");
+ ret = create_empty_ccache(kr);
+ } else {
+ DEBUG(SSSDBG_TRACE_FUNC, "Will perform online auth\n");
+ ret = tgt_req_child(kr);
+ }
+ break;
+ case SSS_PAM_CHAUTHTOK:
+ ret = changepw_child(kr, false);
+ break;
+ case SSS_PAM_CHAUTHTOK_PRELIM:
+ ret = changepw_child(kr, true);
+ break;
+ case SSS_PAM_ACCT_MGMT:
+ ret = kuserok_child(kr);
+ break;
+ case SSS_CMD_RENEW:
+ if (offline) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Cannot renew TGT while offline\n");
+ ret = KRB5_KDC_UNREACH;
+ goto done;
+ }
+ ret = renew_tgt_child(kr);
+ break;
+ case SSS_PAM_PREAUTH:
+ ret = tgt_req_child(kr);
+ break;
+ default:
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "PAM command [%d] not supported.\n", kr->pd->cmd);
+ ret = EINVAL;
+ goto done;
+ }
+
+ ret = k5c_send_data(kr, STDOUT_FILENO, ret);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Failed to send reply\n");
+ }
+
+done:
+ if (ret == EOK) {
+ DEBUG(SSSDBG_TRACE_FUNC, "krb5_child completed successfully\n");
+ ret = 0;
+ } else {
+ DEBUG(SSSDBG_CRIT_FAILURE, "krb5_child failed!\n");
+ ret = -1;
+ }
+ krb5_cleanup(kr);
+ talloc_free(kr);
+ exit(ret);
+}
diff --git a/src/providers/krb5/krb5_child_handler.c b/src/providers/krb5/krb5_child_handler.c
new file mode 100644
index 0000000..54088e4
--- /dev/null
+++ b/src/providers/krb5/krb5_child_handler.c
@@ -0,0 +1,1022 @@
+/*
+ SSSD
+
+ Kerberos 5 Backend Module - Manage krb5_child
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2010 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 <signal.h>
+
+#include "util/util.h"
+#include "util/child_common.h"
+#include "util/sss_chain_id.h"
+#include "providers/krb5/krb5_common.h"
+#include "providers/krb5/krb5_auth.h"
+#include "src/providers/krb5/krb5_utils.h"
+#include "util/sss_ptr_hash.h"
+
+#ifndef KRB5_CHILD_DIR
+#ifndef SSSD_LIBEXEC_PATH
+#error "SSSD_LIBEXEC_PATH not defined"
+#endif /* SSSD_LIBEXEC_PATH */
+
+#define KRB5_CHILD_DIR SSSD_LIBEXEC_PATH
+#endif /* KRB5_CHILD_DIR */
+
+#define KRB5_CHILD KRB5_CHILD_DIR"/krb5_child"
+
+#define TIME_T_MAX LONG_MAX
+#define int64_to_time_t(val) ((time_t)((val) < TIME_T_MAX ? val : TIME_T_MAX))
+
+struct handle_child_state {
+ struct tevent_context *ev;
+ struct krb5child_req *kr;
+ uint8_t *buf;
+ ssize_t len;
+
+ struct tevent_timer *timeout_handler;
+ pid_t child_pid;
+
+ struct child_io_fds *io;
+};
+
+static errno_t pack_authtok(struct io_buffer *buf, size_t *rp,
+ struct sss_auth_token *tok)
+{
+ uint32_t auth_token_type;
+ uint32_t auth_token_length = 0;
+ const char *data;
+ size_t len;
+ errno_t ret = EOK;
+
+ auth_token_type = sss_authtok_get_type(tok);
+
+ switch (auth_token_type) {
+ case SSS_AUTHTOK_TYPE_EMPTY:
+ auth_token_length = 0;
+ data = "";
+ break;
+ case SSS_AUTHTOK_TYPE_PASSWORD:
+ ret = sss_authtok_get_password(tok, &data, &len);
+ auth_token_length = len + 1;
+ break;
+ case SSS_AUTHTOK_TYPE_CCFILE:
+ ret = sss_authtok_get_ccfile(tok, &data, &len);
+ auth_token_length = len + 1;
+ break;
+ case SSS_AUTHTOK_TYPE_2FA_SINGLE:
+ ret = sss_authtok_get_2fa_single(tok, &data, &len);
+ auth_token_length = len + 1;
+ break;
+ case SSS_AUTHTOK_TYPE_2FA:
+ 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:
+ data = (char *) sss_authtok_get_data(tok);
+ auth_token_length = sss_authtok_get_size(tok);
+ break;
+ default:
+ ret = EINVAL;
+ }
+
+ if (ret == EOK) {
+ SAFEALIGN_COPY_UINT32(&buf->data[*rp], &auth_token_type, rp);
+ SAFEALIGN_COPY_UINT32(&buf->data[*rp], &auth_token_length, rp);
+ if (data != NULL) {
+ safealign_memcpy(&buf->data[*rp], data, auth_token_length, rp);
+ }
+ }
+
+ return ret;
+}
+
+static errno_t create_send_buffer(struct krb5child_req *kr,
+ struct io_buffer **io_buf)
+{
+ struct io_buffer *buf;
+ size_t rp;
+ const char *keytab;
+ uint32_t validate;
+ uint32_t send_pac;
+ uint32_t use_enterprise_principal;
+ uint32_t posix_domain = 0;
+ size_t username_len = 0;
+ errno_t ret;
+
+ keytab = dp_opt_get_cstring(kr->krb5_ctx->opts, KRB5_KEYTAB);
+ if (keytab == NULL) {
+ DEBUG(SSSDBG_TRACE_FUNC, "krb5_keytab not set for domain in sssd.conf\n");
+ keytab = "";
+ }
+
+ validate = dp_opt_get_bool(kr->krb5_ctx->opts, KRB5_VALIDATE) ? 1 : 0;
+
+ /* Always send PAC except for local IPA users and IPA server mode */
+ switch (kr->krb5_ctx->config_type) {
+ case K5C_IPA_CLIENT:
+ send_pac = kr->upn_from_different_realm ? 1 : 0;
+ break;
+ case K5C_IPA_SERVER:
+ send_pac = 0;
+ break;
+ default:
+ send_pac = 1;
+ break;
+ }
+
+ /* Renewals from KCM do not initialize kr->dom */
+ if (kr->pd->cmd == SSS_CMD_RENEW || kr->dom->type == DOM_TYPE_POSIX) {
+ posix_domain = 1;
+ } else if (kr->dom->type != DOM_TYPE_APPLICATION) {
+ return EINVAL;
+ }
+
+ if (kr->pd->cmd == SSS_CMD_RENEW || kr->pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM
+ || kr->pd->cmd == SSS_PAM_CHAUTHTOK || kr->is_offline) {
+ use_enterprise_principal = false;
+ } else {
+ use_enterprise_principal = dp_opt_get_bool(kr->krb5_ctx->opts,
+ KRB5_USE_ENTERPRISE_PRINCIPAL) ? 1 : 0;
+ }
+
+ buf = talloc(kr, struct io_buffer);
+ if (buf == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc failed.\n");
+ return ENOMEM;
+ }
+
+ buf->size = 9*sizeof(uint32_t) + strlen(kr->upn);
+
+ if (kr->pd->cmd == SSS_PAM_AUTHENTICATE ||
+ kr->pd->cmd == SSS_PAM_PREAUTH ||
+ kr->pd->cmd == SSS_CMD_RENEW ||
+ kr->pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM ||
+ kr->pd->cmd == SSS_PAM_CHAUTHTOK) {
+ buf->size += 4*sizeof(uint32_t) + strlen(kr->ccname) + strlen(keytab) +
+ sss_authtok_get_size(kr->pd->authtok);
+
+ buf->size += sizeof(uint32_t);
+ if (kr->old_ccname) {
+ buf->size += strlen(kr->old_ccname);
+ }
+ }
+
+ if (kr->pd->cmd == SSS_PAM_CHAUTHTOK) {
+ buf->size += 2*sizeof(uint32_t) +
+ sss_authtok_get_size(kr->pd->newauthtok);
+ }
+
+ if (kr->pd->cmd == SSS_PAM_ACCT_MGMT) {
+ username_len = strlen(kr->kuserok_user);
+ buf->size += sizeof(uint32_t) + username_len;
+ }
+
+ buf->data = talloc_size(kr, buf->size);
+ if (buf->data == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_size failed.\n");
+ talloc_free(buf);
+ return ENOMEM;
+ }
+
+ rp = 0;
+ SAFEALIGN_COPY_UINT32(&buf->data[rp], &kr->pd->cmd, &rp);
+ SAFEALIGN_COPY_UINT32(&buf->data[rp], &kr->uid, &rp);
+ SAFEALIGN_COPY_UINT32(&buf->data[rp], &kr->gid, &rp);
+ SAFEALIGN_COPY_UINT32(&buf->data[rp], &validate, &rp);
+ SAFEALIGN_COPY_UINT32(&buf->data[rp], &posix_domain, &rp);
+ SAFEALIGN_COPY_UINT32(&buf->data[rp], &kr->is_offline, &rp);
+ SAFEALIGN_COPY_UINT32(&buf->data[rp], &send_pac, &rp);
+ SAFEALIGN_COPY_UINT32(&buf->data[rp], &use_enterprise_principal, &rp);
+
+ SAFEALIGN_SET_UINT32(&buf->data[rp], strlen(kr->upn), &rp);
+ safealign_memcpy(&buf->data[rp], kr->upn, strlen(kr->upn), &rp);
+
+ if (kr->pd->cmd == SSS_PAM_AUTHENTICATE ||
+ kr->pd->cmd == SSS_PAM_PREAUTH ||
+ kr->pd->cmd == SSS_CMD_RENEW ||
+ kr->pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM ||
+ kr->pd->cmd == SSS_PAM_CHAUTHTOK) {
+ SAFEALIGN_SET_UINT32(&buf->data[rp], strlen(kr->ccname), &rp);
+ safealign_memcpy(&buf->data[rp], kr->ccname, strlen(kr->ccname), &rp);
+
+ if (kr->old_ccname) {
+ SAFEALIGN_SET_UINT32(&buf->data[rp], strlen(kr->old_ccname), &rp);
+ safealign_memcpy(&buf->data[rp], kr->old_ccname,
+ strlen(kr->old_ccname), &rp);
+ } else {
+ SAFEALIGN_SET_UINT32(&buf->data[rp], 0, &rp);
+ }
+
+ SAFEALIGN_SET_UINT32(&buf->data[rp], strlen(keytab), &rp);
+ safealign_memcpy(&buf->data[rp], keytab, strlen(keytab), &rp);
+
+ ret = pack_authtok(buf, &rp, kr->pd->authtok);
+ if (ret) {
+ return ret;
+ }
+ }
+
+ if (kr->pd->cmd == SSS_PAM_CHAUTHTOK) {
+ ret = pack_authtok(buf, &rp, kr->pd->newauthtok);
+ if (ret) {
+ return ret;
+ }
+ }
+
+ if (kr->pd->cmd == SSS_PAM_ACCT_MGMT) {
+ SAFEALIGN_SET_UINT32(&buf->data[rp], username_len, &rp);
+ safealign_memcpy(&buf->data[rp], kr->kuserok_user, username_len, &rp);
+ }
+
+ *io_buf = buf;
+
+ return EOK;
+}
+
+static void krb5_child_terminate(pid_t pid)
+{
+ int ret;
+
+ if (pid == 0) {
+ return;
+ }
+
+ ret = kill(pid, SIGKILL);
+ if (ret == -1) {
+ ret = errno;
+ DEBUG(SSSDBG_CRIT_FAILURE, "kill failed [%d]: %s\n",
+ ret, sss_strerror(ret));
+ }
+}
+
+static void krb5_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 handle_child_state *state = tevent_req_data(req,
+ struct handle_child_state);
+
+ if (state->timeout_handler == NULL) {
+ return;
+ }
+
+ /* No I/O expected anymore, make sure sockets are closed properly */
+ state->io->in_use = false;
+
+ DEBUG(SSSDBG_IMPORTANT_INFO,
+ "Timeout for child [%d] reached. In case KDC is distant or network "
+ "is slow you may consider increasing value of krb5_auth_timeout.\n",
+ state->child_pid);
+
+ krb5_child_terminate(state->child_pid);
+
+ tevent_req_error(req, ETIMEDOUT);
+}
+
+static errno_t activate_child_timeout_handler(struct tevent_req *req,
+ struct tevent_context *ev,
+ const uint32_t timeout_seconds)
+{
+ struct timeval tv;
+ struct handle_child_state *state = tevent_req_data(req,
+ struct handle_child_state);
+
+ tv = tevent_timeval_current();
+ tv = tevent_timeval_add(&tv, timeout_seconds, 0);
+ state->timeout_handler = tevent_add_timer(ev, state, tv,
+ krb5_child_timeout, req);
+ if (state->timeout_handler == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "tevent_add_timer failed.\n");
+ return ENOMEM;
+ }
+
+ return EOK;
+}
+
+errno_t set_extra_args(TALLOC_CTX *mem_ctx, struct krb5_ctx *krb5_ctx,
+ struct sss_domain_info *domain,
+ const char ***krb5_child_extra_args)
+{
+ const char **extra_args;
+ const char *krb5_realm;
+ uint64_t chain_id;
+ size_t c = 0;
+ int ret;
+
+ if (krb5_ctx == NULL || krb5_child_extra_args == NULL) {
+ return EINVAL;
+ }
+
+ extra_args = talloc_zero_array(mem_ctx, const char *, 12);
+ if (extra_args == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n");
+ return ENOMEM;
+ }
+
+ extra_args[c] = talloc_asprintf(extra_args,
+ "--"CHILD_OPT_FAST_CCACHE_UID"=%"SPRIuid,
+ getuid());
+ if (extra_args[c] == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+ c++;
+
+ extra_args[c] = talloc_asprintf(extra_args,
+ "--"CHILD_OPT_FAST_CCACHE_GID"=%"SPRIgid,
+ getgid());
+ if (extra_args[c] == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+ c++;
+
+ krb5_realm = krb5_ctx->realm;
+ if (domain != NULL && IS_SUBDOMAIN(domain) && dp_opt_get_bool(krb5_ctx->opts, KRB5_USE_SUBDOMAIN_REALM)) {
+ DEBUG(SSSDBG_CONF_SETTINGS, "Use subdomain realm %s.\n", domain->realm);
+ krb5_realm = domain->realm;
+ }
+
+ if (krb5_ctx->realm != NULL) {
+ extra_args[c] = talloc_asprintf(extra_args, "--"CHILD_OPT_REALM"=%s",
+ krb5_realm);
+ if (extra_args[c] == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+ c++;
+ }
+
+ if (krb5_ctx->lifetime_str != NULL) {
+ extra_args[c] = talloc_asprintf(extra_args, "--"CHILD_OPT_LIFETIME"=%s",
+ krb5_ctx->lifetime_str);
+ if (extra_args[c] == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+ c++;
+ }
+
+ if (krb5_ctx->rlife_str != NULL) {
+ extra_args[c] = talloc_asprintf(extra_args,
+ "--"CHILD_OPT_RENEWABLE_LIFETIME"=%s",
+ krb5_ctx->rlife_str);
+ if (extra_args[c] == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+ c++;
+ }
+
+ if (krb5_ctx->use_fast_str != NULL) {
+ extra_args[c] = talloc_asprintf(extra_args, "--"CHILD_OPT_USE_FAST"=%s",
+ krb5_ctx->use_fast_str);
+ if (extra_args[c] == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+ c++;
+
+ if (krb5_ctx->fast_principal != NULL) {
+ extra_args[c] = talloc_asprintf(extra_args,
+ "--"CHILD_OPT_FAST_PRINCIPAL"=%s",
+ krb5_ctx->fast_principal);
+ if (extra_args[c] == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+ c++;
+ }
+
+ if (krb5_ctx->fast_use_anonymous_pkinit) {
+ extra_args[c] = talloc_strdup(extra_args,
+ "--" CHILD_OPT_FAST_USE_ANONYMOUS_PKINIT);
+ if (extra_args[c] == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+ c++;
+ }
+ }
+
+ if (krb5_ctx->check_pac_flags != 0) {
+ extra_args[c] = talloc_asprintf(extra_args,
+ "--"CHILD_OPT_CHECK_PAC"=%"PRIu32,
+ krb5_ctx->check_pac_flags);
+ if (extra_args[c] == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+ c++;
+ }
+
+ if (krb5_ctx->canonicalize) {
+ extra_args[c] = talloc_strdup(extra_args,
+ "--" CHILD_OPT_CANONICALIZE);
+ if (extra_args[c] == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+ c++;
+ }
+
+ if (krb5_ctx->sss_creds_password) {
+ extra_args[c] = talloc_strdup(extra_args,
+ "--" CHILD_OPT_SSS_CREDS_PASSWORD);
+ if (extra_args[c] == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ c++;
+ }
+
+ chain_id = sss_chain_id_get();
+ extra_args[c] = talloc_asprintf(extra_args,
+ "--"CHILD_OPT_CHAIN_ID"=%lu",
+ chain_id);
+ if (extra_args[c] == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+ c++;
+
+ extra_args[c] = NULL;
+
+ *krb5_child_extra_args = extra_args;
+
+ ret = EOK;
+
+done:
+
+ if (ret != EOK) {
+ talloc_free(extra_args);
+ }
+
+ return ret;
+}
+
+static void child_exited(int child_status,
+ struct tevent_signal *sige,
+ void *pvt)
+{
+ struct child_io_fds *io = talloc_get_type(pvt, struct child_io_fds);
+
+ /* Do not free it if we still need to read some data. Just mark that the
+ * child has exited so we know we need to free it later. */
+ if (io->in_use) {
+ io->child_exited = true;
+ return;
+ }
+
+ /* The child has finished and we don't need to use the file descriptors
+ * any more. This will close them and remove them from io hash table. */
+ talloc_free(io);
+}
+
+static void child_keep_alive_timeout(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval tv,
+ void *pvt)
+{
+ struct child_io_fds *io = talloc_get_type(pvt, struct child_io_fds);
+
+ DEBUG(SSSDBG_IMPORTANT_INFO, "Keep alive timeout for child [%d] reached.\n",
+ io->pid);
+
+ /* No I/O expected anymore, make sure sockets are closed properly */
+ io->in_use = false;
+
+ krb5_child_terminate(io->pid);
+}
+
+static errno_t fork_child(struct tevent_context *ev,
+ struct krb5child_req *kr,
+ pid_t *_child_pid,
+ struct child_io_fds **_io)
+{
+ TALLOC_CTX *tmp_ctx;
+ int pipefd_to_child[2] = PIPE_INIT;
+ int pipefd_from_child[2] = PIPE_INIT;
+ const char **krb5_child_extra_args;
+ struct child_io_fds *io;
+ struct tevent_timer *te;
+ struct timeval tv;
+ char *io_key;
+ pid_t pid = 0;
+ errno_t ret;
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ return ENOMEM;
+ }
+
+ ret = set_extra_args(tmp_ctx, kr->krb5_ctx, kr->dom, &krb5_child_extra_args);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "set_extra_args failed.\n");
+ goto done;
+ }
+
+ ret = pipe(pipefd_from_child);
+ if (ret == -1) {
+ ret = errno;
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "pipe (from) failed [%d][%s].\n", errno, strerror(errno));
+ goto done;
+ }
+
+ ret = pipe(pipefd_to_child);
+ if (ret == -1) {
+ ret = errno;
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "pipe (to) failed [%d][%s].\n", errno, strerror(errno));
+ goto done;
+ }
+
+ pid = fork();
+
+ if (pid == 0) { /* child */
+ exec_child_ex(tmp_ctx,
+ pipefd_to_child, pipefd_from_child,
+ KRB5_CHILD, KRB5_CHILD_LOG_FILE,
+ krb5_child_extra_args, false,
+ STDIN_FILENO, STDOUT_FILENO);
+
+ /* We should never get here */
+ DEBUG(SSSDBG_CRIT_FAILURE, "BUG: Could not exec KRB5 child\n");
+ ret = ERR_INTERNAL;
+ goto done;
+ } else if (pid < 0) { /* error */
+ ret = errno;
+ DEBUG(SSSDBG_CRIT_FAILURE, "fork failed [%d]: %s\n", ret, strerror(ret));
+ goto done;
+ }
+
+ /* parent */
+
+ io = talloc_zero(tmp_ctx, struct child_io_fds);
+ if (io == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+ talloc_set_destructor((void*)io, child_io_destructor);
+
+ io->pid = pid;
+
+ /* Set file descriptors. */
+ io->read_from_child_fd = pipefd_from_child[0];
+ io->write_to_child_fd = pipefd_to_child[1];
+ PIPE_FD_CLOSE(pipefd_from_child[1]);
+ PIPE_FD_CLOSE(pipefd_to_child[0]);
+ sss_fd_nonblocking(io->read_from_child_fd);
+ sss_fd_nonblocking(io->write_to_child_fd);
+
+ /* Add io to pid:io hash table. */
+ io_key = talloc_asprintf(tmp_ctx, "%d", pid);
+ if (io_key == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = sss_ptr_hash_add(kr->krb5_ctx->io_table, io_key, io,
+ struct child_io_fds);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "Unable to add child io to hash table "
+ "[%d]: %s\n", ret, sss_strerror(ret));
+ goto done;
+ }
+
+ /* Setup child's keep alive timeout for open file descriptors. This timeout
+ * is quite big to allow additional user interactions when the child is kept
+ * alive for further communication. */
+ tv = tevent_timeval_current_ofs(300, 0);
+ te = tevent_add_timer(ev, io, tv, child_keep_alive_timeout, io);
+ if (te == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unable to setup child timeout\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ /* Setup the child handler. It will free io and remove it from the hash
+ * table when it exits. */
+ ret = child_handler_setup(ev, pid, child_exited, io, NULL);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Could not set up child signal handler "
+ "[%d]: %s\n", ret, sss_strerror(ret));
+ goto done;
+ }
+
+ /* Steal the io pair so it can outlive this request if needed. */
+ talloc_steal(kr->krb5_ctx->io_table, io);
+
+ *_child_pid = pid;
+ *_io = io;
+
+ ret = EOK;
+
+done:
+ if (ret != EOK) {
+ PIPE_CLOSE(pipefd_from_child);
+ PIPE_CLOSE(pipefd_to_child);
+ krb5_child_terminate(pid);
+ }
+
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+static void handle_child_step(struct tevent_req *subreq);
+static void handle_child_done(struct tevent_req *subreq);
+
+struct tevent_req *handle_child_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct krb5child_req *kr)
+{
+ struct tevent_req *req, *subreq;
+ struct handle_child_state *state;
+ char *io_key;
+ int ret;
+ struct io_buffer *buf = NULL;
+
+ req = tevent_req_create(mem_ctx, &state, struct handle_child_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (kr->krb5_ctx->io_table == NULL) {
+ /* Create IO/pipe table if it does not exist. */
+ kr->krb5_ctx->io_table = sss_ptr_hash_create(kr->krb5_ctx, NULL, NULL);
+ if (kr->krb5_ctx->io_table == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ }
+
+ state->ev = ev;
+ state->kr = kr;
+ state->buf = NULL;
+ state->len = 0;
+ state->child_pid = -1;
+ state->timeout_handler = NULL;
+
+ ret = create_send_buffer(kr, &buf);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "create_send_buffer failed.\n");
+ goto fail;
+ }
+
+ if (kr->pd->child_pid == 0) {
+ /* Create new child. */
+ ret = fork_child(ev, kr, &state->child_pid, &state->io);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "fork_child failed.\n");
+ goto fail;
+ }
+
+ /* Setup timeout. If failed, terminate the child process. */
+ ret = activate_child_timeout_handler(req, ev,
+ dp_opt_get_int(kr->krb5_ctx->opts, KRB5_AUTH_TIMEOUT));
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unable to setup child timeout "
+ "[%d]: %s\n", ret, sss_strerror(ret));
+ krb5_child_terminate(state->child_pid);
+ goto fail;
+ }
+ } else {
+ /* Continue talking to an existing child. */
+ io_key = talloc_asprintf(state, "%d", kr->pd->child_pid);
+ if (io_key == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ state->io = sss_ptr_hash_lookup(kr->krb5_ctx->io_table, io_key,
+ struct child_io_fds);
+ if (state->io == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "Unable to locate pipe for child pid=%s\n",
+ io_key);
+ ret = ENOENT;
+ goto fail;
+ }
+ }
+
+ state->io->in_use = true;
+ subreq = write_pipe_safe_send(state, ev, buf->data, buf->size,
+ state->io->write_to_child_fd);
+ if (!subreq) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, handle_child_step, req);
+
+ return req;
+
+fail:
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void handle_child_step(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct handle_child_state *state = tevent_req_data(req,
+ struct handle_child_state);
+ int ret;
+
+ ret = write_pipe_safe_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ subreq = read_pipe_safe_send(state, state->ev,
+ state->io->read_from_child_fd);
+ if (!subreq) {
+ ret = ENOMEM;
+ goto done;
+ }
+ tevent_req_set_callback(subreq, handle_child_done, req);
+
+done:
+ if (ret != EOK) {
+ state->io->in_use = false;
+ if (state->io->child_exited) {
+ talloc_free(state->io);
+ }
+
+ tevent_req_error(req, ret);
+ }
+}
+
+static void handle_child_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct handle_child_state *state = tevent_req_data(req,
+ struct handle_child_state);
+ int ret;
+
+ talloc_zfree(state->timeout_handler);
+
+ ret = read_pipe_safe_recv(subreq, state, &state->buf, &state->len);
+ state->io->in_use = false;
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ goto done;
+ }
+
+done:
+ state->io->in_use = false;
+ if (state->io->child_exited) {
+ talloc_free(state->io);
+ }
+
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+int handle_child_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ uint8_t **buf, ssize_t *len)
+{
+ struct handle_child_state *state = tevent_req_data(req,
+ struct handle_child_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ *buf = talloc_move(mem_ctx, &state->buf);
+ *len = state->len;
+
+ return EOK;
+}
+
+static const char *krb5_child_response_type_to_str(int32_t type)
+{
+ switch (type) {
+ case SSS_PAM_ENV_ITEM:
+ return "Env variable to be set with pam_putenv(3)";
+ case SSS_PAM_USER_INFO:
+ return "Message to be displayed to the user";
+ case SSS_OTP:
+ return "Authtok was a OTP";
+ case SSS_PAM_TEXT_MSG:
+ return "Plain text message to be displayed to the user";
+ case SSS_PAM_OTP_INFO:
+ return "OTP info";
+ case SSS_PASSWORD_PROMPTING:
+ return "Password prompting is possible";
+ case SSS_CERT_AUTH_PROMPTING:
+ return "Certificate based authentication is available";
+ case SSS_KRB5_INFO_TGT_LIFETIME:
+ return "TGT lifetime info";
+ case SSS_KRB5_INFO_UPN:
+ return "UPN info";
+ case SSS_CHILD_KEEP_ALIVE:
+ return "Keep alive";
+ case SSS_PAM_OAUTH2_INFO:
+ return "OAuth2 info";
+ case SSS_PAM_PASSKEY_INFO:
+ return "Passkey info";
+ case SSS_PAM_PASSKEY_KRB_INFO:
+ return "Passkey kerberos info";
+ }
+
+ DEBUG(SSSDBG_MINOR_FAILURE, "Unexpected response type %d\n", type);
+ return "-unexpected-";
+}
+
+errno_t
+parse_krb5_child_response(TALLOC_CTX *mem_ctx, uint8_t *buf, ssize_t len,
+ struct pam_data *pd, int pwd_exp_warning,
+ struct krb5_child_response **_res)
+{
+ ssize_t pref_len;
+ size_t p;
+ errno_t ret;
+ bool skip;
+ char *ccname = NULL;
+ size_t ccname_len = 0;
+ int32_t msg_status;
+ int32_t msg_type;
+ int32_t msg_len;
+ int64_t time_data;
+ struct tgt_times tgtt;
+ uint32_t expiration;
+ uint32_t msg_subtype;
+ struct krb5_child_response *res;
+ const char *upn = NULL;
+ size_t upn_len = 0;
+ bool otp = false;
+
+ if ((size_t) len < sizeof(int32_t)) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "message too short.\n");
+ return EINVAL;
+ }
+
+ memset(&tgtt, 0, sizeof(struct tgt_times));
+
+ if (pwd_exp_warning < 0) {
+ pwd_exp_warning = KERBEROS_PWEXPIRE_WARNING_TIME;
+ }
+
+ /* A buffer with the following structure is expected.
+ * int32_t status of the request (required)
+ * message (zero or more)
+ *
+ * A message consists of:
+ * int32_t type of the message
+ * int32_t length of the following data
+ * uint8_t[len] data
+ */
+
+ p=0;
+ SAFEALIGN_COPY_INT32(&msg_status, buf+p, &p);
+
+ while (p < len) {
+ skip = false;
+ SAFEALIGN_COPY_INT32(&msg_type, buf+p, &p);
+ SAFEALIGN_COPY_INT32(&msg_len, buf+p, &p);
+
+ DEBUG(SSSDBG_TRACE_LIBS, "child response: "
+ "status code: %d (%s), msg type: %d (%s), len: %d\n",
+ msg_status, sss_strerror(msg_status),
+ msg_type, krb5_child_response_type_to_str(msg_type),
+ msg_len);
+
+ if (msg_len > len - p) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "message format error [%d] > [%zu].\n",
+ msg_len, len - p);
+ return EINVAL;
+ }
+
+ /* We need to save the name of the credential cache file. To find it
+ * we check if the data part of a message starts with
+ * CCACHE_ENV_NAME"=". pref_len also counts the trailing '=' because
+ * sizeof() counts the trailing '\0' of a string. */
+ pref_len = sizeof(CCACHE_ENV_NAME);
+ if ((msg_type == SSS_PAM_ENV_ITEM) &&
+ (msg_len > pref_len) &&
+ (strncmp((const char *) &buf[p], CCACHE_ENV_NAME"=", pref_len) == 0)) {
+ ccname = (char *) &buf[p+pref_len];
+ ccname_len = msg_len-pref_len;
+ }
+
+ if (msg_type == SSS_KRB5_INFO_TGT_LIFETIME &&
+ msg_len == 4*sizeof(int64_t)) {
+ SAFEALIGN_COPY_INT64(&time_data, buf+p, NULL);
+ tgtt.authtime = int64_to_time_t(time_data);
+ SAFEALIGN_COPY_INT64(&time_data, buf+p+sizeof(int64_t), NULL);
+ tgtt.starttime = int64_to_time_t(time_data);
+ SAFEALIGN_COPY_INT64(&time_data, buf+p+2*sizeof(int64_t), NULL);
+ tgtt.endtime = int64_to_time_t(time_data);
+ SAFEALIGN_COPY_INT64(&time_data, buf+p+3*sizeof(int64_t), NULL);
+ tgtt.renew_till = int64_to_time_t(time_data);
+ DEBUG(SSSDBG_TRACE_LIBS,
+ "TGT times are [%"SPRItime"][%"SPRItime"][%"SPRItime"][%"SPRItime"].\n",
+ tgtt.authtime, tgtt.starttime, tgtt.endtime, tgtt.renew_till);
+ }
+
+ if (msg_type == SSS_KRB5_INFO_UPN) {
+ upn = (char *) buf + p;
+ upn_len = msg_len;
+ }
+
+ if (msg_type == SSS_PAM_USER_INFO) {
+ SAFEALIGN_COPY_UINT32(&msg_subtype, buf + p, NULL);
+ if (msg_subtype == SSS_PAM_USER_INFO_EXPIRE_WARN) {
+ SAFEALIGN_COPY_UINT32(&expiration,
+ buf + p + sizeof(uint32_t), NULL);
+ if (pwd_exp_warning > 0 &&
+ difftime(pwd_exp_warning, expiration) < 0.0) {
+ skip = true;
+ }
+ }
+ }
+
+ if (msg_type == SSS_OTP) {
+ otp = true;
+ skip = true;
+ }
+
+ if (!skip) {
+ ret = pam_add_response(pd, msg_type, msg_len, &buf[p]);
+ if (ret != EOK) {
+ /* This is not a fatal error */
+ DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n");
+ }
+ }
+
+ p += msg_len;
+
+ if ((p < len) && (p + 2*sizeof(int32_t) > len)) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "The remainder of the message is too short.\n");
+ return EINVAL;
+ }
+ }
+
+ res = talloc_zero(mem_ctx, struct krb5_child_response);
+ if (!res) return ENOMEM;
+
+ res->otp = otp;
+ res->msg_status = msg_status;
+ memcpy(&res->tgtt, &tgtt, sizeof(tgtt));
+
+ if (ccname) {
+ res->ccname = talloc_strndup(res, ccname, ccname_len);
+ if (res->ccname == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strndup failed.\n");
+ talloc_free(res);
+ return ENOMEM;
+ }
+ }
+
+ if (upn != NULL) {
+ res->correct_upn = talloc_strndup(res, upn, upn_len);
+ if (res->correct_upn == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strndup failed.\n");
+ talloc_free(res);
+ return ENOMEM;
+ }
+ }
+
+ *_res = res;
+ return EOK;
+}
diff --git a/src/providers/krb5/krb5_common.c b/src/providers/krb5/krb5_common.c
new file mode 100644
index 0000000..6498258
--- /dev/null
+++ b/src/providers/krb5/krb5_common.c
@@ -0,0 +1,1261 @@
+/*
+ SSSD
+
+ Kerberos Provider Common Functions
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2008-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/>.
+*/
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <arpa/inet.h>
+#include <ctype.h>
+
+#include "providers/backend.h"
+#include "providers/krb5/krb5_common.h"
+#include "providers/krb5/krb5_opts.h"
+#include "providers/krb5/krb5_utils.h"
+#include "providers/fail_over.h"
+
+#ifdef HAVE_KRB5_CC_COLLECTION
+/* krb5 profile functions */
+#include <profile.h>
+#endif
+
+static errno_t check_lifetime(TALLOC_CTX *mem_ctx, struct dp_option *opts,
+ const int opt_id, char **lifetime_str)
+{
+ int ret;
+ char *str = NULL;
+ krb5_deltat lifetime;
+
+ str = dp_opt_get_string(opts, opt_id);
+ if (str == NULL || *str == '\0') {
+ DEBUG(SSSDBG_FUNC_DATA, "No lifetime configured.\n");
+ *lifetime_str = NULL;
+ return EOK;
+ }
+
+ if (isdigit(str[strlen(str)-1])) {
+ str = talloc_asprintf(mem_ctx, "%ss", str);
+ if (str == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = dp_opt_set_string(opts, opt_id, str);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "dp_opt_set_string failed.\n");
+ goto done;
+ }
+ } else {
+ str = talloc_strdup(mem_ctx, str);
+ if (str == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+ }
+
+ ret = krb5_string_to_deltat(str, &lifetime);
+ if (ret != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Invalid value [%s] for a lifetime.\n", str);
+ ret = EINVAL;
+ goto done;
+ }
+
+ *lifetime_str = str;
+
+ ret = EOK;
+
+done:
+ if (ret != EOK) {
+ talloc_free(str);
+ }
+
+ return ret;
+}
+
+#ifdef HAVE_KRB5_CC_COLLECTION
+/* source default_ccache_name from krb5.conf */
+static errno_t sss_get_system_ccname_template(TALLOC_CTX *mem_ctx,
+ char **ccname)
+{
+ krb5_context ctx;
+ profile_t p;
+ char *value = NULL;
+ long ret;
+
+ *ccname = NULL;
+
+ ret = sss_krb5_init_context(&ctx);
+ if (ret) return ret;
+
+ ret = krb5_get_profile(ctx, &p);
+ if (ret) goto done;
+
+ ret = profile_get_string(p, "libdefaults", "default_ccache_name",
+ NULL, NULL, &value);
+ profile_release(p);
+ if (ret) goto done;
+
+ if (!value) {
+ ret = ERR_NOT_FOUND;
+ goto done;
+ }
+
+ *ccname = talloc_strdup(mem_ctx, value);
+ if (*ccname == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = EOK;
+
+done:
+ krb5_free_context(ctx);
+ free(value);
+ return ret;
+}
+#else
+static errno_t sss_get_system_ccname_template(TALLOC_CTX *mem_ctx,
+ char **ccname)
+{
+ DEBUG(SSSDBG_CONF_SETTINGS,
+ "Your kerberos library does not support the default_ccache_name "
+ "option or the profile library. Please use krb5_ccname_template "
+ "in sssd.conf if you want to change the default\n");
+ *ccname = NULL;
+ return ERR_NOT_FOUND;
+}
+#endif
+
+static void sss_check_cc_template(const char *cc_template)
+{
+ size_t template_len;
+
+ template_len = strlen(cc_template);
+ if (template_len >= 6 &&
+ strcmp(cc_template + (template_len - 6), "XXXXXX") != 0) {
+ DEBUG(SSSDBG_CONF_SETTINGS, "ccache file name template [%s] doesn't "
+ "contain randomizing characters (XXXXXX), file might not "
+ "be rewritable\n", cc_template);
+ }
+}
+
+errno_t sss_krb5_check_options(struct dp_option *opts,
+ struct sss_domain_info *dom,
+ struct krb5_ctx *krb5_ctx)
+{
+ TALLOC_CTX *tmp_ctx = NULL;
+ int ret;
+ const char *realm;
+ const char *dummy;
+ char *ccname;
+
+ if (opts == NULL || dom == NULL || krb5_ctx == NULL) {
+ return EINVAL;
+ }
+
+ tmp_ctx = talloc_new(NULL);
+ if (!tmp_ctx) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ realm = dp_opt_get_cstring(opts, KRB5_REALM);
+ if (realm == NULL) {
+ ret = dp_opt_set_string(opts, KRB5_REALM, dom->name);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "dp_opt_set_string failed.\n");
+ goto done;
+ }
+ realm = dom->name;
+ }
+
+ krb5_ctx->realm = talloc_strdup(krb5_ctx, realm);
+ if (krb5_ctx->realm == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Failed to set realm, krb5_child might not work as expected.\n");
+ }
+
+ ret = check_lifetime(krb5_ctx, opts, KRB5_RENEWABLE_LIFETIME,
+ &krb5_ctx->rlife_str);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Failed to check value of krb5_renewable_lifetime. [%d][%s]\n",
+ ret, strerror(ret));
+ goto done;
+ }
+
+ ret = check_lifetime(krb5_ctx, opts, KRB5_LIFETIME,
+ &krb5_ctx->lifetime_str);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Failed to check value of krb5_lifetime. [%d][%s]\n",
+ ret, strerror(ret));
+ goto done;
+ }
+
+ krb5_ctx->use_fast_str = dp_opt_get_cstring(opts, KRB5_USE_FAST);
+ if (krb5_ctx->use_fast_str != NULL) {
+ ret = check_fast(krb5_ctx->use_fast_str, &krb5_ctx->use_fast);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "check_fast failed.\n");
+ goto done;
+ }
+
+ if (krb5_ctx->use_fast) {
+ krb5_ctx->fast_principal = dp_opt_get_cstring(opts,
+ KRB5_FAST_PRINCIPAL);
+ krb5_ctx->fast_use_anonymous_pkinit = dp_opt_get_bool(opts,
+ KRB5_FAST_USE_ANONYMOUS_PKINIT);
+ }
+ }
+
+ /* In contrast to MIT KDCs AD does not automatically canonicalize the
+ * enterprise principal in an AS request but requires the canonicalize
+ * flags to be set. To be on the safe side we always enable
+ * canonicalization if enterprise principals are used. */
+ krb5_ctx->canonicalize = false;
+ if (dp_opt_get_bool(opts, KRB5_CANONICALIZE)
+ || dp_opt_get_bool(opts, KRB5_USE_ENTERPRISE_PRINCIPAL)) {
+ krb5_ctx->canonicalize = true;
+ }
+
+ dummy = dp_opt_get_cstring(opts, KRB5_KDC);
+ if (dummy == NULL) {
+ DEBUG(SSSDBG_CONF_SETTINGS, "No KDC explicitly configured, using defaults.\n");
+ }
+
+ dummy = dp_opt_get_cstring(opts, KRB5_KPASSWD);
+ if (dummy == NULL) {
+ DEBUG(SSSDBG_CONF_SETTINGS, "No kpasswd server explicitly configured, "
+ "using the KDC or defaults.\n");
+ }
+
+ ccname = dp_opt_get_string(opts, KRB5_CCNAME_TMPL);
+ if (ccname != NULL) {
+ DEBUG(SSSDBG_CONF_SETTINGS,
+ "The credential ccache name template has been explicitly set "
+ "in sssd.conf, it is recommended to set default_ccache_name "
+ "in krb5.conf instead so that a system default is used\n");
+ ccname = talloc_strdup(tmp_ctx, ccname);
+ if (!ccname) {
+ ret = ENOMEM;
+ goto done;
+ }
+ } else {
+ ret = sss_get_system_ccname_template(tmp_ctx, &ccname);
+ if (ret && ret != ERR_NOT_FOUND) {
+ goto done;
+ }
+ if (ret == ERR_NOT_FOUND) {
+ /* Use fallback default */
+ ccname = talloc_strdup(tmp_ctx, DEFAULT_CCNAME_TEMPLATE);
+ if (!ccname) {
+ ret = ENOMEM;
+ goto done;
+ }
+ }
+
+ /* set back in opts */
+ ret = dp_opt_set_string(opts, KRB5_CCNAME_TMPL, ccname);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "dp_opt_set_string failed.\n");
+ goto done;
+ }
+ }
+
+ if ((ccname[0] == '/') || (strncmp(ccname, "FILE:", 5) == 0)) {
+ DEBUG(SSSDBG_CONF_SETTINGS, "ccache is of type FILE\n");
+ /* warn if the file type (which is usually created in a sticky bit
+ * laden directory) does not have randomizing characters */
+ sss_check_cc_template(ccname);
+
+ if (ccname[0] == '/') {
+ /* /path/to/cc prepend FILE: */
+ DEBUG(SSSDBG_CONF_SETTINGS, "The ccname template was "
+ "missing an explicit type, but is an absolute "
+ "path specifier. Assuming FILE:\n");
+
+ ccname = talloc_asprintf(tmp_ctx, "FILE:%s", ccname);
+ if (!ccname) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = dp_opt_set_string(opts, KRB5_CCNAME_TMPL, ccname);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "dp_opt_set_string failed.\n");
+ goto done;
+ }
+ }
+ }
+
+ ret = EOK;
+
+done:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+errno_t krb5_try_kdcip(struct confdb_ctx *cdb, const char *conf_path,
+ struct dp_option *opts, int opt_id)
+{
+ char *krb5_servers = NULL;
+ errno_t ret;
+
+ krb5_servers = dp_opt_get_string(opts, opt_id);
+ if (krb5_servers == NULL) {
+ DEBUG(SSSDBG_CONF_SETTINGS,
+ "No KDC found in configuration, trying legacy option\n");
+ ret = confdb_get_string(cdb, NULL, conf_path,
+ "krb5_kdcip", NULL, &krb5_servers);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "confdb_get_string failed.\n");
+ return ret;
+ }
+
+ if (krb5_servers != NULL)
+ {
+ ret = dp_opt_set_string(opts, opt_id, krb5_servers);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "dp_opt_set_string failed.\n");
+ talloc_free(krb5_servers);
+ return ret;
+ }
+
+ DEBUG(SSSDBG_CONF_SETTINGS,
+ "Set krb5 server [%s] based on legacy krb5_kdcip option\n",
+ krb5_servers);
+ DEBUG(SSSDBG_FATAL_FAILURE,
+ "Your configuration uses the deprecated option "
+ "'krb5_kdcip' to specify the KDC. Please change the "
+ "configuration to use the 'krb5_server' option "
+ "instead.\n");
+ talloc_free(krb5_servers);
+ }
+ }
+
+ return EOK;
+}
+
+errno_t sss_krb5_get_options(TALLOC_CTX *memctx, struct confdb_ctx *cdb,
+ const char *conf_path, struct dp_option **_opts)
+{
+ int ret;
+ struct dp_option *opts;
+
+ ret = dp_get_options(memctx, cdb, conf_path, default_krb5_opts,
+ KRB5_OPTS, &opts);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "dp_get_options failed.\n");
+ goto done;
+ }
+
+ /* If there is no KDC, try the deprecated krb5_kdcip option, too */
+ /* FIXME - this can be removed in a future version */
+ ret = krb5_try_kdcip(cdb, conf_path, opts, KRB5_KDC);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "sss_krb5_try_kdcip failed.\n");
+ goto done;
+ }
+
+ *_opts = opts;
+ ret = EOK;
+
+done:
+ if (ret != EOK) {
+ talloc_zfree(opts);
+ }
+
+ return ret;
+}
+
+void sss_krb5_parse_lookahead(const char *param, size_t *primary, size_t *backup)
+{
+ int ret;
+
+ if (primary == NULL || backup == NULL) {
+ return;
+ }
+
+ *primary = SSS_KRB5_LOOKAHEAD_PRIMARY_DEFAULT;
+ *backup = SSS_KRB5_LOOKAHEAD_BACKUP_DEFAULT;
+
+ if (param == NULL) {
+ return;
+ }
+
+ if (strchr(param, ':')) {
+ ret = sscanf(param, "%zu:%zu", primary, backup);
+ if (ret != 2) {
+ DEBUG(SSSDBG_MINOR_FAILURE, "Could not parse krb5_kdcinfo_lookahead!\n");
+ }
+ } else {
+ ret = sscanf(param, "%zu", primary);
+ if (ret != 1) {
+ DEBUG(SSSDBG_MINOR_FAILURE, "Could not parse krb5_kdcinfo_lookahead!\n");
+ }
+ }
+
+ DEBUG(SSSDBG_CONF_SETTINGS,
+ "Option krb5_kdcinfo_lookahead set to %zu:%zu",
+ *primary, *backup);
+}
+
+
+static int remove_info_files_destructor(void *p)
+{
+ int ret;
+ struct remove_info_files_ctx *ctx = talloc_get_type(p,
+ struct remove_info_files_ctx);
+
+ ret = remove_krb5_info_files(ctx, ctx->realm);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "remove_krb5_info_files failed.\n");
+ }
+ ctx->krb5_service->removal_callback_available = false;
+
+ return 0;
+}
+
+static errno_t
+krb5_add_krb5info_offline_callback(struct krb5_service *krb5_service)
+{
+ int ret;
+ struct remove_info_files_ctx *ctx = NULL;
+
+ if (krb5_service == NULL || krb5_service->name == NULL
+ || krb5_service->realm == NULL
+ || krb5_service->be_ctx == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Missing KDC service name or realm!\n");
+ return EINVAL;
+ }
+
+ if (krb5_service->removal_callback_available) {
+ DEBUG(SSSDBG_TRACE_ALL,
+ "Removal callback already available for service [%s].\n",
+ krb5_service->name);
+ return EOK;
+ }
+
+ ctx = talloc_zero(krb5_service->be_ctx, struct remove_info_files_ctx);
+ if (ctx == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zfree failed.\n");
+ return ENOMEM;
+ }
+
+ ctx->realm = talloc_strdup(ctx, krb5_service->realm);
+ if (ctx->realm == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup failed!\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ctx->be_ctx = krb5_service->be_ctx;
+ ctx->krb5_service = krb5_service;
+ ctx->kdc_service_name = talloc_strdup(ctx, krb5_service->name);
+ if (ctx->kdc_service_name == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup failed!\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = be_add_offline_cb(ctx, krb5_service->be_ctx,
+ remove_krb5_info_files_callback, ctx, NULL);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "be_add_offline_cb failed.\n");
+ goto done;
+ }
+
+ talloc_set_destructor((TALLOC_CTX *) ctx, remove_info_files_destructor);
+ krb5_service->removal_callback_available = true;
+
+ ret = EOK;
+
+done:
+ if (ret != EOK) {
+ talloc_zfree(ctx);
+ }
+
+ return ret;
+}
+
+static errno_t write_krb5info_file_contents(struct krb5_service *krb5_service,
+ const char *contents,
+ const char *service)
+{
+ int ret;
+ int fd = -1;
+ char *tmp_name = NULL;
+ char *krb5info_name = NULL;
+ TALLOC_CTX *tmp_ctx = NULL;
+ const char *name_tmpl = NULL;
+ size_t server_len;
+ ssize_t written;
+
+ if (krb5_service == NULL || krb5_service->realm == NULL
+ || *krb5_service->realm == '\0'
+ || contents == NULL || *contents == '\0'
+ || service == NULL || *service == '\0') {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Missing or empty realm, server or service.\n");
+ return EINVAL;
+ }
+
+ if (sss_krb5_realm_has_proxy(krb5_service->realm)) {
+ DEBUG(SSSDBG_CONF_SETTINGS,
+ "KDC Proxy available for realm [%s], no kdcinfo file created.\n",
+ krb5_service->realm);
+ return EOK;
+ }
+
+ if (strcmp(service, SSS_KRB5KDC_FO_SRV) == 0) {
+ name_tmpl = KDCINFO_TMPL;
+ } else if (strcmp(service, SSS_KRB5KPASSWD_FO_SRV) == 0) {
+ name_tmpl = KPASSWDINFO_TMPL;
+ } else {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported service [%s].\n", service);
+ return EINVAL;
+ }
+
+ server_len = strlen(contents);
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new failed.\n");
+ return ENOMEM;
+ }
+
+ tmp_name = talloc_asprintf(tmp_ctx, PUBCONF_PATH"/.krb5info_dummy_XXXXXX");
+ if (tmp_name == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ krb5info_name = talloc_asprintf(tmp_ctx, name_tmpl, krb5_service->realm);
+ if (krb5info_name == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ fd = sss_unique_file(tmp_ctx, tmp_name, &ret);
+ if (fd == -1) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "sss_unique_file failed [%d][%s].\n", ret, strerror(ret));
+ goto done;
+ }
+
+ errno = 0;
+ written = sss_atomic_write_s(fd, discard_const(contents), server_len);
+ if (written == -1) {
+ ret = errno;
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "write failed [%d][%s].\n", ret, strerror(ret));
+ goto done;
+ }
+
+ if (written != server_len) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Write error, wrote [%zd] bytes, expected [%zu]\n",
+ written, server_len);
+ ret = EIO;
+ goto done;
+ }
+
+ ret = fchmod(fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
+ if (ret == -1) {
+ ret = errno;
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "fchmod failed [%d][%s].\n", ret, strerror(ret));
+ goto done;
+ }
+
+ ret = close(fd);
+ fd = -1;
+ if (ret == -1) {
+ ret = errno;
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "close failed [%d][%s].\n", ret, strerror(ret));
+ goto done;
+ }
+
+ ret = rename(tmp_name, krb5info_name);
+ if (ret == -1) {
+ ret = errno;
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "rename failed [%d][%s].\n", ret, strerror(ret));
+ goto done;
+ }
+
+ ret = krb5_add_krb5info_offline_callback(krb5_service);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "Failed to add offline callback, krb5info "
+ "file might not be removed properly.\n");
+ }
+
+ ret = EOK;
+done:
+ if (fd != -1) {
+ close(fd);
+ }
+
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+errno_t write_krb5info_file(struct krb5_service *krb5_service,
+ const char **server_list,
+ const char *service)
+{
+ int i;
+ errno_t ret;
+ TALLOC_CTX *tmp_ctx = NULL;
+ char *contents = NULL;
+
+ if (krb5_service == NULL || server_list == NULL || service == NULL) {
+ return EINVAL;
+ }
+
+ if (server_list[0] == NULL) {
+ return EOK;
+ }
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ return ENOMEM;
+ }
+
+ contents = talloc_strdup(tmp_ctx, "");
+ if (contents == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ i = 0;
+ do {
+ contents = talloc_asprintf_append(contents, "%s\n", server_list[i]);
+ if (contents == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ i++;
+ } while (server_list[i] != NULL);
+
+ ret = write_krb5info_file_contents(krb5_service, contents, service);
+done:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+static const char* fo_server_address_or_name(TALLOC_CTX *tmp_ctx, struct fo_server *server)
+{
+ struct resolv_hostent *srvaddr;
+ char *address;
+
+ if (!server) return NULL;
+
+ srvaddr = fo_get_server_hostent(server);
+ if (srvaddr) {
+ address = resolv_get_string_address(tmp_ctx, srvaddr);
+ if (address) {
+ return sss_escape_ip_address(tmp_ctx,
+ srvaddr->family,
+ address);
+ }
+ }
+
+ address = discard_const(fo_get_server_name(server));
+ if (address != NULL && fo_get_use_search_list(server) == false) {
+ if (address[strlen(address)-1] != '.') {
+ address = talloc_asprintf(tmp_ctx, "%s.", address);
+ if (address == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "talloc_asprintf failed.\n");
+ }
+ }
+ }
+ return address;
+}
+
+errno_t write_krb5info_file_from_fo_server(struct krb5_service *krb5_service,
+ struct fo_server *server,
+ bool force_default_port,
+ const char *service,
+ bool (*filter)(struct fo_server *))
+{
+ TALLOC_CTX *tmp_ctx = NULL;
+ const char **server_list;
+ size_t server_idx;
+ struct fo_server *item;
+ int primary;
+ int port;
+ const char *address;
+ errno_t ret;
+ size_t n_lookahead_primary;
+ size_t n_lookahead_backup;
+
+ if (krb5_service == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "The krb5_service must not be NULL!\n");
+ return EINVAL;
+ }
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new failed\n");
+ return ENOMEM;
+ }
+
+ n_lookahead_primary = krb5_service->lookahead_primary;
+ n_lookahead_backup = krb5_service->lookahead_backup;
+
+ server_idx = 0;
+ server_list = talloc_zero_array(tmp_ctx,
+ const char *,
+ fo_server_count(server) + 1);
+ if (server_list == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero_array failed\n");
+ talloc_free(tmp_ctx);
+ return ENOMEM;
+ }
+
+ if (filter == NULL || filter(server) == false) {
+ address = fo_server_address_or_name(tmp_ctx, server);
+ if (address) {
+ if (!force_default_port) {
+ port = fo_get_server_port(server);
+ if (port != 0) {
+ address = talloc_asprintf(tmp_ctx, "%s:%d", address, port);
+ if (address == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n");
+ talloc_free(tmp_ctx);
+ return ENOMEM;
+ }
+ }
+ }
+
+ server_list[server_idx++] = address;
+ if (fo_is_server_primary(server)) {
+ if (n_lookahead_primary > 0) {
+ n_lookahead_primary--;
+ }
+ } else {
+ if (n_lookahead_backup > 0) {
+ n_lookahead_backup--;
+ }
+ }
+ } else {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Server without name and address found in list.\n");
+ }
+ }
+
+ for (primary = 1; primary >= 0; --primary) {
+ for (item = fo_server_next(server) ? fo_server_next(server) : fo_server_first(server);
+ item != server;
+ item = fo_server_next(item) ? fo_server_next(item) : fo_server_first(item)) {
+
+ if (primary && n_lookahead_primary == 0) break;
+ if (!primary && n_lookahead_backup == 0) break;
+ if (primary && !fo_is_server_primary(item)) continue;
+ if (!primary && fo_is_server_primary(item)) continue;
+ if (filter != NULL && filter(item)) continue;
+
+ address = fo_server_address_or_name(tmp_ctx, item);
+ if (address == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Server without name and address found in list.\n");
+ continue;
+ }
+
+ if (!force_default_port) {
+ port = fo_get_server_port(item);
+ if (port != 0) {
+ address = talloc_asprintf(tmp_ctx, "%s:%d", address, port);
+ if (address == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n");
+ talloc_free(tmp_ctx);
+ return ENOMEM;
+ }
+ }
+ }
+
+ server_list[server_idx++] = address;
+ if (primary) {
+ n_lookahead_primary--;
+ } else {
+ n_lookahead_backup--;
+ }
+ }
+ }
+ if (server_list[0] == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "There is no server that can be written into kdc info file.\n");
+ ret = EINVAL;
+ } else {
+ ret = write_krb5info_file(krb5_service,
+ server_list,
+ service);
+ }
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+
+static void krb5_resolve_callback(void *private_data, struct fo_server *server)
+{
+ struct krb5_service *krb5_service;
+ int ret;
+
+ krb5_service = talloc_get_type(private_data, struct krb5_service);
+ if (!krb5_service) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Bad private_data\n");
+ return;
+ }
+
+ if (krb5_service->write_kdcinfo) {
+ ret = write_krb5info_file_from_fo_server(krb5_service,
+ server,
+ false,
+ krb5_service->name,
+ NULL);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "write to %s/kdcinfo.%s failed, authentication might fail.\n",
+ PUBCONF_PATH, krb5_service->realm);
+ }
+ }
+}
+
+static errno_t _krb5_servers_init(struct be_ctx *ctx,
+ struct krb5_service *service,
+ const char *service_name,
+ const char *servers,
+ bool primary)
+{
+ TALLOC_CTX *tmp_ctx;
+ char **list = NULL;
+ errno_t ret = 0;
+ int i;
+ char *port_str;
+ long port;
+ char *server_spec;
+ char *endptr;
+ struct servent *servent;
+
+ tmp_ctx = talloc_new(NULL);
+ if (!tmp_ctx) {
+ return ENOMEM;
+ }
+
+ ret = split_on_separator(tmp_ctx, servers, ',', true, true, &list, NULL);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Failed to parse server list!\n");
+ goto done;
+ }
+
+ for (i = 0; list[i]; i++) {
+ talloc_steal(service, list[i]);
+ server_spec = talloc_strdup(service, list[i]);
+ if (!server_spec) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ if (be_fo_is_srv_identifier(server_spec)) {
+ if (!primary) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "Failed to add server [%s] to failover service: "
+ "SRV resolution only allowed for primary servers!\n",
+ list[i]);
+ continue;
+ }
+
+ ret = be_fo_add_srv_server(ctx, service_name, service_name, NULL,
+ BE_FO_PROTO_UDP, true, NULL);
+ if (ret) {
+ DEBUG(SSSDBG_FATAL_FAILURE, "Failed to add server\n");
+ goto done;
+ }
+
+ DEBUG(SSSDBG_TRACE_FUNC, "Added service lookup\n");
+ continue;
+ }
+
+ /* Do not try to get port number if last character is ']' */
+ if (server_spec[strlen(server_spec) - 1] != ']') {
+ port_str = strrchr(server_spec, ':');
+ } else {
+ port_str = NULL;
+ }
+
+ if (port_str == NULL) {
+ port = 0;
+ } else {
+ *port_str = '\0';
+ ++port_str;
+ if (isdigit(*port_str)) {
+ errno = 0;
+ port = strtol(port_str, &endptr, 10);
+ if (errno != 0) {
+ ret = errno;
+ DEBUG(SSSDBG_CRIT_FAILURE, "strtol failed on [%s]: [%d][%s].\n", port_str,
+ ret, strerror(ret));
+ goto done;
+ }
+ if (*endptr != '\0') {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Found additional characters [%s] in port number "
+ "[%s].\n", endptr, port_str);
+ ret = EINVAL;
+ goto done;
+ }
+
+ if (port < 1 || port > 65535) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Illegal port number [%ld].\n", port);
+ ret = EINVAL;
+ goto done;
+ }
+ } else if (isalpha(*port_str)) {
+ servent = getservbyname(port_str, NULL);
+ if (servent == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "getservbyname cannot find service [%s].\n",
+ port_str);
+ ret = EINVAL;
+ goto done;
+ }
+
+ port = servent->s_port;
+ } else {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported port specifier in [%s].\n", list[i]);
+ ret = EINVAL;
+ goto done;
+ }
+ }
+
+ /* It could be ipv6 address in square brackets. Remove
+ * the brackets if needed. */
+ ret = remove_ipv6_brackets(server_spec);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ ret = be_fo_add_server(ctx, service_name, server_spec, (int) port,
+ list[i], primary);
+ if (ret && ret != EEXIST) {
+ DEBUG(SSSDBG_FATAL_FAILURE, "Failed to add server\n");
+ goto done;
+ }
+
+ DEBUG(SSSDBG_TRACE_FUNC, "Added Server %s\n", list[i]);
+ }
+
+done:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+static inline errno_t
+krb5_primary_servers_init(struct be_ctx *ctx, struct krb5_service *service,
+ const char *service_name, const char *servers)
+{
+ return _krb5_servers_init(ctx, service, service_name, servers, true);
+}
+
+static inline errno_t
+krb5_backup_servers_init(struct be_ctx *ctx, struct krb5_service *service,
+ const char *service_name, const char *servers)
+{
+ return _krb5_servers_init(ctx, service, service_name, servers, false);
+}
+
+static int krb5_user_data_cmp(void *ud1, void *ud2)
+{
+ return strcasecmp((char*) ud1, (char*) ud2);
+}
+
+struct krb5_service *krb5_service_new(TALLOC_CTX *mem_ctx,
+ struct be_ctx *be_ctx,
+ const char *service_name,
+ const char *realm,
+ bool use_kdcinfo,
+ size_t n_lookahead_primary,
+ size_t n_lookahead_backup)
+{
+ struct krb5_service *service;
+
+ service = talloc_zero(mem_ctx, struct krb5_service);
+ if (service == NULL) {
+ return NULL;
+ }
+
+ service->name = talloc_strdup(service, service_name);
+ if (service->name == NULL) {
+ talloc_free(service);
+ return NULL;
+ }
+
+ service->realm = talloc_strdup(service, realm);
+ if (service->realm == NULL) {
+ talloc_free(service);
+ return NULL;
+ }
+
+ DEBUG(SSSDBG_CONF_SETTINGS,
+ "write_kdcinfo for realm %s set to %s\n",
+ realm,
+ use_kdcinfo ? "true" : "false");
+ service->write_kdcinfo = use_kdcinfo;
+ service->lookahead_primary = n_lookahead_primary;
+ service->lookahead_backup = n_lookahead_backup;
+
+ service->be_ctx = be_ctx;
+ return service;
+}
+
+int krb5_service_init(TALLOC_CTX *memctx, struct be_ctx *ctx,
+ const char *service_name,
+ const char *primary_servers,
+ const char *backup_servers,
+ const char *realm,
+ bool use_kdcinfo,
+ size_t n_lookahead_primary,
+ size_t n_lookahead_backup,
+ struct krb5_service **_service)
+{
+ TALLOC_CTX *tmp_ctx;
+ struct krb5_service *service;
+ int ret;
+
+ tmp_ctx = talloc_new(memctx);
+ if (!tmp_ctx) {
+ return ENOMEM;
+ }
+
+ service = krb5_service_new(tmp_ctx, ctx, service_name, realm, use_kdcinfo,
+ n_lookahead_primary, n_lookahead_backup);
+ if (!service) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = be_fo_add_service(ctx, service_name, krb5_user_data_cmp);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Failed to create failover service!\n");
+ goto done;
+ }
+
+ if (!primary_servers) {
+ DEBUG(SSSDBG_CONF_SETTINGS,
+ "No primary servers defined, using service discovery\n");
+ primary_servers = BE_SRV_IDENTIFIER;
+ }
+
+ ret = krb5_primary_servers_init(ctx, service, service_name, primary_servers);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ if (backup_servers) {
+ ret = krb5_backup_servers_init(ctx, service, service_name,
+ backup_servers);
+ if (ret != EOK) {
+ goto done;
+ }
+ }
+
+ ret = be_fo_service_add_callback(memctx, ctx, service_name,
+ krb5_resolve_callback, service);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Failed to add failover callback!\n");
+ goto done;
+ }
+
+ ret = EOK;
+
+done:
+ if (ret == EOK) {
+ *_service = talloc_steal(memctx, service);
+ }
+ talloc_zfree(tmp_ctx);
+ return ret;
+}
+
+
+errno_t remove_krb5_info_files(TALLOC_CTX *mem_ctx, const char *realm)
+{
+ int ret;
+ errno_t err;
+ char *file;
+
+ file = talloc_asprintf(mem_ctx, KDCINFO_TMPL, realm);
+ if(file == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n");
+ return ENOMEM;
+ }
+
+ errno = 0;
+ ret = unlink(file);
+ if (ret == -1) {
+ err = errno;
+ DEBUG(SSSDBG_FUNC_DATA, "Could not remove [%s], [%d][%s]\n", file,
+ err, strerror(err));
+ }
+
+ file = talloc_asprintf(mem_ctx, KPASSWDINFO_TMPL, realm);
+ if(file == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n");
+ return ENOMEM;
+ }
+
+ errno = 0;
+ ret = unlink(file);
+ if (ret == -1) {
+ err = errno;
+ DEBUG(SSSDBG_FUNC_DATA, "Could not remove [%s], [%d][%s]\n", file,
+ err, strerror(err));
+ }
+
+ return EOK;
+}
+
+void remove_krb5_info_files_callback(void *pvt)
+{
+ int ret;
+ struct remove_info_files_ctx *ctx = talloc_get_type(pvt,
+ struct remove_info_files_ctx);
+
+ ret = be_fo_run_callbacks_at_next_request(ctx->be_ctx,
+ ctx->kdc_service_name);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "be_fo_run_callbacks_at_next_request(kdc_service_name) failed, "
+ "krb5 info files will not be removed, because "
+ "it is unclear if they will be recreated properly.\n");
+ return;
+ }
+ if (ctx->kpasswd_service_name != NULL) {
+ ret = be_fo_run_callbacks_at_next_request(ctx->be_ctx,
+ ctx->kpasswd_service_name);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "be_fo_run_callbacks_at_next_request(kpasswd_service_name) failed, "
+ "krb5 info files will not be removed, because "
+ "it is unclear if they will be recreated properly.\n");
+ return;
+ }
+ }
+
+ /* Freeing the remove_info_files_ctx will remove the related krb5info
+ * file. Additionally the callback from the list of callbacks is removed,
+ * it will be added again when a new krb5info file is created. */
+ talloc_free(ctx);
+}
+
+errno_t krb5_get_simple_upn(TALLOC_CTX *mem_ctx, struct krb5_ctx *krb5_ctx,
+ struct sss_domain_info *dom, const char *username,
+ const char *user_dom, char **_upn)
+{
+ const char *realm = NULL;
+ char *uc_dom = NULL;
+ char *upn;
+ char *name;
+ TALLOC_CTX *tmp_ctx = NULL;
+ errno_t ret;
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new failed.\n");
+ return ENOMEM;
+ }
+
+ if (user_dom != NULL && dom->name != NULL &&
+ strcasecmp(dom->name, user_dom) != 0) {
+ uc_dom = get_uppercase_realm(tmp_ctx, user_dom);
+ if (uc_dom == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "get_uppercase_realm failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+ } else {
+ realm = dp_opt_get_cstring(krb5_ctx->opts, KRB5_REALM);
+ if (realm == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "Missing Kerberos realm.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+ }
+
+ /* The internal username is qualified, but we are only interested in
+ * the name part
+ */
+ ret = sss_parse_internal_fqname(tmp_ctx, username, &name, NULL);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Could not parse [%s] into name and "
+ "domain components, login might fail\n", username);
+ upn = talloc_strdup(tmp_ctx, username);
+ } else {
+ /* NOTE: this is a hack, works only in some environments */
+ upn = talloc_asprintf(tmp_ctx, "%s@%s",
+ name, realm != NULL ? realm : uc_dom);
+ }
+
+ if (upn == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ DEBUG(SSSDBG_TRACE_ALL, "Using simple UPN [%s].\n", upn);
+ *_upn = talloc_steal(mem_ctx, upn);
+ ret = EOK;
+done:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+errno_t compare_principal_realm(const char *upn, const char *realm,
+ bool *different_realm)
+{
+ char *at_sign;
+
+ if (upn == NULL || realm == NULL || different_realm == NULL ||
+ *upn == '\0' || *realm == '\0') {
+ return EINVAL;
+ }
+
+ at_sign = strchr(upn, '@');
+
+ if (at_sign == NULL) {
+ return EINVAL;
+ }
+
+ if (strcmp(realm, at_sign + 1) == 0) {
+ *different_realm = false;
+ } else {
+ *different_realm = true;
+ }
+
+ return EOK;
+}
diff --git a/src/providers/krb5/krb5_common.h b/src/providers/krb5/krb5_common.h
new file mode 100644
index 0000000..80552ab
--- /dev/null
+++ b/src/providers/krb5/krb5_common.h
@@ -0,0 +1,249 @@
+/*
+ SSSD
+
+ Kerberos Backend, common header file
+
+ Authors:
+ 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 __KRB5_COMMON_H__
+#define __KRB5_COMMON_H__
+
+#include "config.h"
+#include <stdbool.h>
+
+#include "providers/backend.h"
+#include "util/util.h"
+#include "util/sss_krb5.h"
+
+#define KDCINFO_TMPL PUBCONF_PATH"/kdcinfo.%s"
+#define KPASSWDINFO_TMPL PUBCONF_PATH"/kpasswdinfo.%s"
+
+#define SSS_KRB5KDC_FO_SRV "KERBEROS"
+#define SSS_KRB5KPASSWD_FO_SRV "KPASSWD"
+#define SSS_KRB5_LOOKAHEAD_PRIMARY_DEFAULT 3
+#define SSS_KRB5_LOOKAHEAD_BACKUP_DEFAULT 1
+
+enum krb5_opts {
+ KRB5_KDC = 0,
+ KRB5_BACKUP_KDC,
+ KRB5_REALM,
+ KRB5_CCACHEDIR,
+ KRB5_CCNAME_TMPL,
+ KRB5_AUTH_TIMEOUT,
+ KRB5_KEYTAB,
+ KRB5_VALIDATE,
+ KRB5_KPASSWD,
+ KRB5_BACKUP_KPASSWD,
+ KRB5_STORE_PASSWORD_IF_OFFLINE,
+ KRB5_RENEWABLE_LIFETIME,
+ KRB5_LIFETIME,
+ KRB5_RENEW_INTERVAL,
+ KRB5_USE_FAST,
+ KRB5_FAST_PRINCIPAL,
+ KRB5_FAST_USE_ANONYMOUS_PKINIT,
+ KRB5_CANONICALIZE,
+ KRB5_USE_ENTERPRISE_PRINCIPAL,
+ KRB5_USE_KDCINFO,
+ KRB5_KDCINFO_LOOKAHEAD,
+ KRB5_MAP_USER,
+ KRB5_USE_SUBDOMAIN_REALM,
+
+ KRB5_OPTS
+};
+
+typedef enum { INIT_PW, INIT_KT, RENEW, VALIDATE } action_type;
+
+struct krb5_service {
+ struct be_ctx *be_ctx;
+ char *name;
+ char *realm;
+ bool write_kdcinfo;
+ size_t lookahead_primary;
+ size_t lookahead_backup;
+ bool removal_callback_available;
+};
+
+struct fo_service;
+struct deferred_auth_ctx;
+struct renew_tgt_ctx;
+
+enum krb5_config_type {
+ K5C_GENERIC,
+ K5C_IPA_CLIENT,
+ K5C_IPA_SERVER
+};
+
+struct map_id_name_to_krb_primary {
+ const char *id_name;
+ const char* krb_primary;
+};
+
+struct krb5_ctx {
+ /* opts taken from kinit */
+ /* in seconds */
+ krb5_deltat starttime;
+ krb5_deltat lifetime;
+ char *lifetime_str;
+ krb5_deltat rlife;
+ char *rlife_str;
+
+ int forwardable;
+ int proxiable;
+ int addresses;
+
+ int not_forwardable;
+ int not_proxiable;
+ int no_addresses;
+
+ int verbose;
+
+ char* principal_name;
+ char* service_name;
+ char* keytab_name;
+ char* k5_cache_name;
+ char* k4_cache_name;
+
+ action_type action;
+
+ struct dp_option *opts;
+ struct krb5_service *service;
+ struct krb5_service *kpasswd_service;
+
+ sss_regexp_t *illegal_path_re;
+
+ struct deferred_auth_ctx *deferred_auth_ctx;
+ struct renew_tgt_ctx *renew_tgt_ctx;
+ struct kcm_renew_tgt_ctx *kcm_renew_tgt_ctx;
+ bool use_fast;
+ bool sss_creds_password;
+
+ hash_table_t *wait_queue_hash;
+ hash_table_t *io_table;
+
+ enum krb5_config_type config_type;
+
+ struct map_id_name_to_krb_primary *name_to_primary;
+
+ char *realm;
+
+ const char *use_fast_str;
+ const char *fast_principal;
+ bool fast_use_anonymous_pkinit;
+ uint32_t check_pac_flags;
+
+ bool canonicalize;
+};
+
+struct remove_info_files_ctx {
+ char *realm;
+ struct be_ctx *be_ctx;
+ const char *kdc_service_name;
+ const char *kpasswd_service_name;
+ struct krb5_service *krb5_service;
+};
+
+errno_t sss_krb5_check_options(struct dp_option *opts,
+ struct sss_domain_info *dom,
+ struct krb5_ctx *krb5_ctx);
+
+errno_t krb5_try_kdcip(struct confdb_ctx *cdb, const char *conf_path,
+ struct dp_option *opts, int opt_id);
+
+errno_t sss_krb5_get_options(TALLOC_CTX *memctx, struct confdb_ctx *cdb,
+ const char *conf_path, struct dp_option **_opts);
+
+void sss_krb5_parse_lookahead(const char *param, size_t *primary, size_t *backup);
+
+errno_t write_krb5info_file(struct krb5_service *krb5_service,
+ const char **server_list,
+ const char *service);
+
+errno_t write_krb5info_file_from_fo_server(struct krb5_service *krb5_service,
+ struct fo_server *server,
+ bool force_default_port,
+ const char *service,
+ bool (*filter)(struct fo_server *));
+
+struct krb5_service *krb5_service_new(TALLOC_CTX *mem_ctx,
+ struct be_ctx *be_ctx,
+ const char *service_name,
+ const char *realm,
+ bool use_kdcinfo,
+ size_t n_lookahead_primary,
+ size_t n_lookahead_backup);
+
+int krb5_service_init(TALLOC_CTX *memctx, struct be_ctx *ctx,
+ const char *service_name,
+ const char *primary_servers,
+ const char *backup_servers,
+ const char *realm,
+ bool use_kdcinfo,
+ size_t n_lookahead_primary,
+ size_t n_lookahead_backup,
+ struct krb5_service **_service);
+
+void remove_krb5_info_files_callback(void *pvt);
+
+errno_t remove_krb5_info_files(TALLOC_CTX *mem_ctx, const char *realm);
+
+errno_t krb5_get_simple_upn(TALLOC_CTX *mem_ctx, struct krb5_ctx *krb5_ctx,
+ struct sss_domain_info *dom, const char *username,
+ const char *user_dom, char **_upn);
+
+errno_t compare_principal_realm(const char *upn, const char *realm,
+ bool *different_realm);
+
+/* from krb5_keytab.c */
+
+/**
+ * @brief Copy given keytab into a MEMORY keytab
+ *
+ * @param[in] mem_ctx Talloc memory context the new keytab name should be
+ * allocated on
+ * @param[in] kctx Kerberos context
+ * @param[in] inp_keytab_file Existing keytab, if set to NULL the default
+ * keytab will be used
+ * @param[out] _mem_name Name of the new MEMORY keytab
+ * @param[out] _mem_keytab Krb5 keytab handle for the new MEMORY keytab, NULL
+ * may be passed here if the caller has no use for the
+ * handle
+ *
+ * The memory for the MEMORY keytab is handled by libkrb5 internally and
+ * a reference counter is used. If the reference counter of the specific
+ * MEMORY keytab reaches 0, i.e. no open ones are left, the memory is free.
+ * This means we cannot call krb5_kt_close() for the new MEMORY keytab in
+ * copy_keytab_into_memory() because this would destroy it immediately. Hence
+ * we have to return the handle so that the caller can safely remove the
+ * MEMORY keytab if the is not needed anymore. Since libkrb5 frees the
+ * internal memory when the library is unloaded short running processes can
+ * safely pass NULL as the 5th argument because on exit all memory is freed.
+ * Long running processes which need more control over the memory consumption
+ * should close the handle for free the memory at runtime.
+ */
+krb5_error_code copy_keytab_into_memory(TALLOC_CTX *mem_ctx, krb5_context kctx,
+ const char *inp_keytab_file,
+ char **_mem_name,
+ krb5_keytab *_mem_keytab);
+
+errno_t set_extra_args(TALLOC_CTX *mem_ctx, struct krb5_ctx *krb5_ctx,
+ struct sss_domain_info *domain,
+ const char ***krb5_child_extra_args);
+#endif /* __KRB5_COMMON_H__ */
diff --git a/src/providers/krb5/krb5_delayed_online_authentication.c b/src/providers/krb5/krb5_delayed_online_authentication.c
new file mode 100644
index 0000000..f88d8ab
--- /dev/null
+++ b/src/providers/krb5/krb5_delayed_online_authentication.c
@@ -0,0 +1,386 @@
+/*
+ SSSD
+
+ Kerberos 5 Backend Module -- Request a TGT when the system gets online
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2010 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 <security/pam_modules.h>
+#ifdef USE_KEYRING
+#include <sys/types.h>
+#include <keyutils.h>
+#endif
+#include <dhash.h>
+
+#include "providers/krb5/krb5_auth.h"
+#include "util/util.h"
+#include "util/find_uid.h"
+
+struct deferred_auth_ctx {
+ hash_table_t *user_table;
+ struct be_ctx *be_ctx;
+ struct tevent_context *ev;
+ struct krb5_ctx *krb5_ctx;
+};
+
+struct auth_data {
+ struct be_ctx *be_ctx;
+ struct krb5_ctx *krb5_ctx;
+ struct pam_data *pd;
+};
+
+static void *hash_talloc(const size_t size, void *pvt)
+{
+ return talloc_size(pvt, size);
+}
+
+static void hash_talloc_free(void *ptr, void *pvt)
+{
+ talloc_free(ptr);
+}
+
+static void authenticate_user_done(struct tevent_req *req);
+static void authenticate_user(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval current_time,
+ void *private_data)
+{
+ struct auth_data *auth_data = talloc_get_type(private_data,
+ struct auth_data);
+ struct pam_data *pd = auth_data->pd;
+ struct tevent_req *req;
+
+ DEBUG_PAM_DATA(SSSDBG_TRACE_ALL, pd);
+
+#ifdef USE_KEYRING
+ char *password;
+ long keysize;
+ long keyrevoke;
+ errno_t ret;
+
+ keysize = keyctl_read_alloc(pd->key_serial, (void **)&password);
+ if (keysize == -1) {
+ ret = errno;
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "keyctl_read failed [%d][%s].\n", ret, strerror(ret));
+ return;
+ }
+
+ ret = sss_authtok_set_password(pd->authtok, password, keysize);
+ sss_erase_mem_securely(password, keysize);
+ free(password);
+ if (ret) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "failed to set password in auth token [%d][%s].\n",
+ ret, strerror(ret));
+ return;
+ }
+
+ keyrevoke = keyctl_revoke(pd->key_serial);
+ if (keyrevoke == -1) {
+ ret = errno;
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "keyctl_revoke failed [%d][%s].\n", ret, strerror(ret));
+ }
+#endif
+
+ req = krb5_auth_queue_send(auth_data, ev, auth_data->be_ctx,
+ auth_data->pd, auth_data->krb5_ctx);
+ if (req == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "krb5_auth_send failed.\n");
+ talloc_free(auth_data);
+ return;
+ }
+
+ tevent_req_set_callback(req, authenticate_user_done, auth_data);
+}
+
+static void authenticate_user_done(struct tevent_req *req)
+{
+ struct auth_data *auth_data = tevent_req_callback_data(req,
+ struct auth_data);
+ int ret;
+ int pam_status = PAM_SYSTEM_ERR;
+ int dp_err = DP_ERR_OK;
+
+ ret = krb5_auth_queue_recv(req, &pam_status, &dp_err);
+ talloc_free(req);
+ if (ret) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "krb5_auth request failed.\n");
+ } else {
+ if (pam_status == PAM_SUCCESS) {
+ DEBUG(SSSDBG_CONF_SETTINGS,
+ "Successfully authenticated user [%s].\n",
+ auth_data->pd->user);
+ } else {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Failed to authenticate user [%s].\n",
+ auth_data->pd->user);
+ }
+ }
+
+ talloc_free(auth_data);
+}
+
+static errno_t authenticate_stored_users(
+ struct deferred_auth_ctx *deferred_auth_ctx)
+{
+ int ret;
+ hash_table_t *uid_table;
+ struct hash_iter_context_t *iter;
+ hash_entry_t *entry;
+ hash_key_t key;
+ hash_value_t value;
+ struct pam_data *pd;
+ struct auth_data *auth_data;
+ struct tevent_timer *te;
+
+ ret = get_uid_table(deferred_auth_ctx, &uid_table);
+ if (ret != HASH_SUCCESS) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "get_uid_table failed.\n");
+ return ret;
+ }
+
+ iter = new_hash_iter_context(deferred_auth_ctx->user_table);
+ if (iter == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "new_hash_iter_context failed.\n");
+ return EINVAL;
+ }
+
+ while ((entry = iter->next(iter)) != NULL) {
+ key.type = HASH_KEY_ULONG;
+ key.ul = entry->key.ul;
+ pd = talloc_get_type(entry->value.ptr, struct pam_data);
+
+ ret = hash_lookup(uid_table, &key, &value);
+
+ if (ret == HASH_SUCCESS) {
+ DEBUG(SSSDBG_FUNC_DATA, "User [%s] is still logged in, "
+ "trying online authentication.\n", pd->user);
+
+ auth_data = talloc_zero(deferred_auth_ctx->be_ctx,
+ struct auth_data);
+ if (auth_data == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n");
+ } else {
+ auth_data->pd = talloc_steal(auth_data, pd);
+ auth_data->krb5_ctx = deferred_auth_ctx->krb5_ctx;
+ auth_data->be_ctx = deferred_auth_ctx->be_ctx;
+
+ te = tevent_add_timer(deferred_auth_ctx->ev,
+ auth_data, tevent_timeval_current(),
+ authenticate_user, auth_data);
+ if (te == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "tevent_add_timer failed.\n");
+ }
+ }
+ } else {
+ DEBUG(SSSDBG_FUNC_DATA, "User [%s] is not logged in anymore, "
+ "discarding online authentication.\n", pd->user);
+ talloc_free(pd);
+ }
+
+ ret = hash_delete(deferred_auth_ctx->user_table,
+ &entry->key);
+ if (ret != HASH_SUCCESS) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "hash_delete failed [%s].\n",
+ hash_error_string(ret));
+ }
+ }
+
+ talloc_free(iter);
+
+ return EOK;
+}
+
+static void delayed_online_authentication_callback(void *private_data)
+{
+ struct deferred_auth_ctx *deferred_auth_ctx =
+ talloc_get_type(private_data, struct deferred_auth_ctx);
+ int ret;
+
+ if (deferred_auth_ctx->user_table == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Delayed online authentication activated, "
+ "but user table does not exists.\n");
+ return;
+ }
+
+ DEBUG(SSSDBG_FUNC_DATA,
+ "Backend is online, starting delayed online authentication.\n");
+ ret = authenticate_stored_users(deferred_auth_ctx);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "authenticate_stored_users failed.\n");
+ }
+
+ return;
+}
+
+errno_t add_user_to_delayed_online_authentication(struct krb5_ctx *krb5_ctx,
+ struct sss_domain_info *domain,
+ struct pam_data *pd,
+ uid_t uid)
+{
+ int ret;
+ hash_key_t key;
+ hash_value_t value;
+ struct pam_data *new_pd;
+
+ if (domain->type != DOM_TYPE_POSIX) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "Domain type does not support delayed authentication\n");
+ return ENOTSUP;
+ }
+
+ if (krb5_ctx->deferred_auth_ctx == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Missing context for delayed online authentication.\n");
+ return EINVAL;
+ }
+
+ if (krb5_ctx->deferred_auth_ctx->user_table == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "user_table not available.\n");
+ return EINVAL;
+ }
+
+ if (sss_authtok_get_type(pd->authtok) != SSS_AUTHTOK_TYPE_PASSWORD) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Invalid authtok for user [%s].\n", pd->user);
+ return EINVAL;
+ }
+
+ ret = copy_pam_data(krb5_ctx->deferred_auth_ctx, pd, &new_pd);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "copy_pam_data failed\n");
+ return ENOMEM;
+ }
+
+
+#ifdef USE_KEYRING
+ const char *password;
+ size_t len;
+
+ ret = sss_authtok_get_password(new_pd->authtok, &password, &len);
+ if (ret) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Failed to get password [%d][%s].\n", ret, strerror(ret));
+ sss_authtok_set_empty(new_pd->authtok);
+ talloc_free(new_pd);
+ return ret;
+ }
+
+ new_pd->key_serial = add_key("user", new_pd->user, password, len,
+ KEY_SPEC_SESSION_KEYRING);
+ if (new_pd->key_serial == -1) {
+ ret = errno;
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "add_key failed [%d][%s].\n", ret, strerror(ret));
+ sss_authtok_set_empty(new_pd->authtok);
+ talloc_free(new_pd);
+ return ret;
+ }
+ DEBUG(SSSDBG_TRACE_ALL,
+ "Saved authtok of user [%s] with serial [%"SPRIkey_ser"].\n",
+ new_pd->user, new_pd->key_serial);
+ sss_authtok_set_empty(new_pd->authtok);
+#endif
+
+ key.type = HASH_KEY_ULONG;
+ key.ul = uid;
+ value.type = HASH_VALUE_PTR;
+ value.ptr = new_pd;
+
+ ret = hash_enter(krb5_ctx->deferred_auth_ctx->user_table,
+ &key, &value);
+ if (ret != HASH_SUCCESS) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Cannot add user [%s] to table [%s], "
+ "delayed online authentication not possible.\n",
+ pd->user, hash_error_string(ret));
+ talloc_free(new_pd);
+ return ENOMEM;
+ }
+
+ DEBUG(SSSDBG_TRACE_ALL, "Added user [%s] successfully to "
+ "delayed online authentication.\n", pd->user);
+
+ return EOK;
+}
+
+errno_t init_delayed_online_authentication(struct krb5_ctx *krb5_ctx,
+ struct be_ctx *be_ctx,
+ struct tevent_context *ev)
+{
+ int ret;
+ hash_table_t *tmp_table;
+
+ ret = get_uid_table(krb5_ctx, &tmp_table);
+ if (ret != EOK) {
+ if (ret == ENOSYS) {
+ DEBUG(SSSDBG_FATAL_FAILURE, "Delayed online auth was requested "
+ "on an unsupported system.\n");
+ } else {
+ DEBUG(SSSDBG_FATAL_FAILURE, "Delayed online auth was requested "
+ "but initialisation failed.\n");
+ }
+ return ret;
+ }
+ ret = hash_destroy(tmp_table);
+ if (ret != HASH_SUCCESS) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "hash_destroy failed [%s].\n", hash_error_string(ret));
+ return EFAULT;
+ }
+
+ krb5_ctx->deferred_auth_ctx = talloc_zero(krb5_ctx,
+ struct deferred_auth_ctx);
+ if (krb5_ctx->deferred_auth_ctx == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n");
+ return ENOMEM;
+ }
+
+ ret = hash_create_ex(0,
+ &krb5_ctx->deferred_auth_ctx->user_table,
+ 0, 0, 0, 0, hash_talloc, hash_talloc_free,
+ krb5_ctx->deferred_auth_ctx,
+ NULL, NULL);
+ if (ret != HASH_SUCCESS) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "hash_create_ex failed [%s]\n", hash_error_string(ret));
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ krb5_ctx->deferred_auth_ctx->be_ctx = be_ctx;
+ krb5_ctx->deferred_auth_ctx->krb5_ctx = krb5_ctx;
+ krb5_ctx->deferred_auth_ctx->ev = ev;
+
+ ret = be_add_online_cb(krb5_ctx, be_ctx,
+ delayed_online_authentication_callback,
+ krb5_ctx->deferred_auth_ctx, NULL);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "be_add_online_cb failed.\n");
+ goto fail;
+ }
+
+ /* TODO: add destructor */
+
+ return EOK;
+fail:
+ talloc_zfree(krb5_ctx->deferred_auth_ctx);
+ return ret;
+}
diff --git a/src/providers/krb5/krb5_init.c b/src/providers/krb5/krb5_init.c
new file mode 100644
index 0000000..d2c927e
--- /dev/null
+++ b/src/providers/krb5/krb5_init.c
@@ -0,0 +1,229 @@
+/*
+ SSSD
+
+ Kerberos 5 Backend Module
+
+ Authors:
+ 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/>.
+*/
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include "util/child_common.h"
+#include "providers/krb5/krb5_auth.h"
+#include "providers/krb5/krb5_common.h"
+#include "providers/krb5/krb5_init_shared.h"
+#include "providers/data_provider.h"
+
+static errno_t krb5_init_kpasswd(struct krb5_ctx *ctx,
+ struct be_ctx *be_ctx)
+{
+ const char *realm;
+ const char *primary_servers;
+ const char *backup_servers;
+ const char *kdc_servers;
+ bool use_kdcinfo;
+ size_t n_lookahead_primary;
+ size_t n_lookahead_backup;
+ errno_t ret;
+
+ realm = dp_opt_get_string(ctx->opts, KRB5_REALM);
+ if (realm == NULL) {
+ DEBUG(SSSDBG_FATAL_FAILURE, "Missing krb5_realm option!\n");
+ return EINVAL;
+ }
+
+ kdc_servers = dp_opt_get_string(ctx->opts, KRB5_KDC);
+ primary_servers = dp_opt_get_string(ctx->opts, KRB5_KPASSWD);
+ backup_servers = dp_opt_get_string(ctx->opts, KRB5_BACKUP_KPASSWD);
+ use_kdcinfo = dp_opt_get_bool(ctx->opts, KRB5_USE_KDCINFO);
+ sss_krb5_parse_lookahead(dp_opt_get_string(ctx->opts, KRB5_KDCINFO_LOOKAHEAD),
+ &n_lookahead_primary, &n_lookahead_backup);
+
+
+ if (primary_servers == NULL && backup_servers != NULL) {
+ DEBUG(SSSDBG_CONF_SETTINGS, "kpasswd server wasn't specified but "
+ "backup_servers kpasswd given. Using it as primary_servers\n");
+ primary_servers = backup_servers;
+ backup_servers = NULL;
+ }
+
+ if (primary_servers == NULL && kdc_servers != NULL) {
+ DEBUG(SSSDBG_FATAL_FAILURE, "Missing krb5_kpasswd option and KDC set "
+ "explicitly, will use KDC for password change operations!\n");
+ ctx->kpasswd_service = NULL;
+ } else {
+ ret = krb5_service_init(ctx, be_ctx, SSS_KRB5KPASSWD_FO_SRV,
+ primary_servers, backup_servers, realm,
+ use_kdcinfo,
+ n_lookahead_primary,
+ n_lookahead_backup,
+ &ctx->kpasswd_service);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_FATAL_FAILURE,
+ "Failed to init KRB5KPASSWD failover service!\n");
+ return ret;
+ }
+ }
+
+ return EOK;
+}
+
+static errno_t krb5_init_kdc(struct krb5_ctx *ctx, struct be_ctx *be_ctx)
+{
+ const char *primary_servers;
+ const char *backup_servers;
+ const char *realm;
+ bool use_kdcinfo;
+ size_t n_lookahead_primary;
+ size_t n_lookahead_backup;
+ errno_t ret;
+
+ realm = dp_opt_get_string(ctx->opts, KRB5_REALM);
+ if (realm == NULL) {
+ DEBUG(SSSDBG_FATAL_FAILURE, "Missing krb5_realm option!\n");
+ return EINVAL;
+ }
+
+ primary_servers = dp_opt_get_string(ctx->opts, KRB5_KDC);
+ backup_servers = dp_opt_get_string(ctx->opts, KRB5_BACKUP_KDC);
+
+ use_kdcinfo = dp_opt_get_bool(ctx->opts, KRB5_USE_KDCINFO);
+ sss_krb5_parse_lookahead(dp_opt_get_string(ctx->opts, KRB5_KDCINFO_LOOKAHEAD),
+ &n_lookahead_primary, &n_lookahead_backup);
+
+ ret = krb5_service_init(ctx, be_ctx, SSS_KRB5KDC_FO_SRV,
+ primary_servers, backup_servers, realm,
+ use_kdcinfo,
+ n_lookahead_primary,
+ n_lookahead_backup,
+ &ctx->service);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_FATAL_FAILURE, "Failed to init KRB5 failover service!\n");
+ return ret;
+ }
+
+ return EOK;
+}
+
+errno_t sssm_krb5_init(TALLOC_CTX *mem_ctx,
+ struct be_ctx *be_ctx,
+ struct data_provider *provider,
+ const char *module_name,
+ void **_module_data)
+{
+ struct krb5_ctx *ctx;
+ errno_t ret;
+
+ ctx = talloc_zero(mem_ctx, struct krb5_ctx);
+ if (ctx == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero() failed\n");
+ return ENOMEM;
+ }
+
+ ret = sss_krb5_get_options(ctx, be_ctx->cdb, be_ctx->conf_path, &ctx->opts);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get krb5 options [%d]: %s\n",
+ ret, sss_strerror(ret));
+ goto done;
+ }
+
+ ctx->action = INIT_PW;
+ ctx->config_type = K5C_GENERIC;
+
+ ret = krb5_init_kdc(ctx, be_ctx);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ ret = krb5_init_kpasswd(ctx, be_ctx);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ ret = krb5_child_init(ctx, be_ctx);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_FATAL_FAILURE, "Could not initialize krb5_child settings "
+ "[%d]: %s\n", ret, sss_strerror(ret));
+ goto done;
+ }
+
+ ret = sss_regexp_new(ctx, ILLEGAL_PATH_PATTERN, 0, &(ctx->illegal_path_re));
+ if (ret != EOK) {
+ ret = EFAULT;
+ goto done;
+ }
+
+ ret = be_fo_set_dns_srv_lookup_plugin(be_ctx, NULL);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unable to set SRV lookup plugin "
+ "[%d]: %s\n", ret, sss_strerror(ret));
+ goto done;
+ }
+
+ *_module_data = ctx;
+
+ ret = EOK;
+
+done:
+ if (ret != EOK) {
+ talloc_free(ctx);
+ }
+
+ return ret;
+}
+
+errno_t sssm_krb5_auth_init(TALLOC_CTX *mem_ctx,
+ struct be_ctx *be_ctx,
+ void *module_data,
+ struct dp_method *dp_methods)
+{
+ struct krb5_ctx *ctx;
+
+ ctx = talloc_get_type(module_data, struct krb5_ctx);
+ dp_set_method(dp_methods, DPM_AUTH_HANDLER,
+ krb5_pam_handler_send, krb5_pam_handler_recv, ctx,
+ struct krb5_ctx, struct pam_data, struct pam_data *);
+
+ return EOK;
+}
+
+errno_t sssm_krb5_chpass_init(TALLOC_CTX *mem_ctx,
+ struct be_ctx *be_ctx,
+ void *module_data,
+ struct dp_method *dp_methods)
+{
+ return sssm_krb5_auth_init(mem_ctx, be_ctx, module_data, dp_methods);
+}
+
+errno_t sssm_krb5_access_init(TALLOC_CTX *mem_ctx,
+ struct be_ctx *be_ctx,
+ void *module_data,
+ struct dp_method *dp_methods)
+{
+ struct krb5_ctx *ctx;
+
+ ctx = talloc_get_type(module_data, struct krb5_ctx);
+ dp_set_method(dp_methods, DPM_ACCESS_HANDLER,
+ krb5_pam_handler_send, krb5_pam_handler_recv, ctx,
+ struct krb5_ctx, struct pam_data, struct pam_data *);
+
+ return EOK;
+}
diff --git a/src/providers/krb5/krb5_init_shared.c b/src/providers/krb5/krb5_init_shared.c
new file mode 100644
index 0000000..3e6ebe2
--- /dev/null
+++ b/src/providers/krb5/krb5_init_shared.c
@@ -0,0 +1,105 @@
+/*
+ SSSD
+
+ Authors:
+ Stephen Gallagher <sgallagh@redhat.com>
+
+ Copyright (C) 2012 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 <fcntl.h>
+
+#include "providers/krb5/krb5_common.h"
+#include "providers/krb5/krb5_auth.h"
+#include "providers/krb5/krb5_utils.h"
+#include "providers/krb5/krb5_init_shared.h"
+
+errno_t krb5_child_init(struct krb5_ctx *krb5_auth_ctx,
+ struct be_ctx *bectx)
+{
+ errno_t ret;
+ time_t renew_intv = 0;
+ krb5_deltat renew_interval_delta;
+ char *renew_interval_str;
+
+ if (dp_opt_get_bool(krb5_auth_ctx->opts, KRB5_STORE_PASSWORD_IF_OFFLINE)) {
+ ret = init_delayed_online_authentication(krb5_auth_ctx, bectx,
+ bectx->ev);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "init_delayed_online_authentication failed.\n");
+ goto done;
+ }
+ }
+ renew_interval_str = dp_opt_get_string(krb5_auth_ctx->opts,
+ KRB5_RENEW_INTERVAL);
+ if (renew_interval_str != NULL) {
+ ret = krb5_string_to_deltat(renew_interval_str, &renew_interval_delta);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "Reading krb5_renew_interval failed.\n");
+ renew_interval_delta = 0;
+ }
+ renew_intv = renew_interval_delta;
+ }
+
+ if (renew_intv > 0) {
+ ret = init_renew_tgt(krb5_auth_ctx, bectx, bectx->ev, renew_intv);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "init_renew_tgt failed.\n");
+ goto done;
+ }
+ }
+
+ ret = sss_krb5_check_options(krb5_auth_ctx->opts, bectx->domain,
+ krb5_auth_ctx);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "sss_krb5_check_options failed.\n");
+ goto done;
+ }
+
+ ret = get_pac_check_config(bectx->cdb, &krb5_auth_ctx->check_pac_flags);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Failed to get pac_check option.\n");
+ goto done;
+ }
+
+ if (krb5_auth_ctx->check_pac_flags != 0
+ && !dp_opt_get_bool(krb5_auth_ctx->opts, KRB5_VALIDATE)) {
+ DEBUG(SSSDBG_IMPORTANT_INFO,
+ "PAC check is requested but krb5_validate is set to false. "
+ "PAC checks will be skipped.\n");
+ sss_log(SSS_LOG_WARNING,
+ "PAC check is requested but krb5_validate is set to false. "
+ "PAC checks will be skipped.");
+ }
+
+ ret = parse_krb5_map_user(krb5_auth_ctx,
+ dp_opt_get_cstring(krb5_auth_ctx->opts,
+ KRB5_MAP_USER),
+ bectx->domain->name,
+ &krb5_auth_ctx->name_to_primary);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "parse_krb5_map_user failed: %s:[%d]\n",
+ sss_strerror(ret), ret);
+ goto done;
+ }
+
+ ret = EOK;
+
+done:
+ return ret;
+}
diff --git a/src/providers/krb5/krb5_init_shared.h b/src/providers/krb5/krb5_init_shared.h
new file mode 100644
index 0000000..883b84f
--- /dev/null
+++ b/src/providers/krb5/krb5_init_shared.h
@@ -0,0 +1,29 @@
+/*
+ SSSD
+
+ Authors:
+ Stephen Gallagher <sgallagh@redhat.com>
+
+ Copyright (C) 2012 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 KRB5_INIT_SHARED_H_
+#define KRB5_INIT_SHARED_H_
+
+errno_t krb5_child_init(struct krb5_ctx *krb5_auth_ctx,
+ struct be_ctx *bectx);
+
+#endif /* KRB5_INIT_SHARED_H_ */
diff --git a/src/providers/krb5/krb5_keytab.c b/src/providers/krb5/krb5_keytab.c
new file mode 100644
index 0000000..db383d4
--- /dev/null
+++ b/src/providers/krb5/krb5_keytab.c
@@ -0,0 +1,231 @@
+/*
+ SSSD
+
+ Kerberos 5 Backend Module -- keytab related utilities
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2014 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 "util/util.h"
+#include "util/sss_krb5.h"
+#include "providers/krb5/krb5_common.h"
+
+static krb5_error_code do_keytab_copy(krb5_context kctx, krb5_keytab s_keytab,
+ krb5_keytab d_keytab)
+{
+ krb5_error_code kerr;
+ krb5_error_code kt_err;
+ krb5_kt_cursor cursor;
+ krb5_keytab_entry entry;
+
+ memset(&cursor, 0, sizeof(cursor));
+ kerr = krb5_kt_start_seq_get(kctx, s_keytab, &cursor);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "error reading keytab.\n");
+ return kerr;
+ }
+
+ memset(&entry, 0, sizeof(entry));
+ while ((kt_err = krb5_kt_next_entry(kctx, s_keytab, &entry,
+ &cursor)) == 0) {
+ kerr = krb5_kt_add_entry(kctx, d_keytab, &entry);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_OP_FAILURE, "krb5_kt_add_entry failed.\n");
+ kt_err = krb5_kt_end_seq_get(kctx, s_keytab, &cursor);
+ if (kt_err != 0) {
+ DEBUG(SSSDBG_TRACE_ALL,
+ "krb5_kt_end_seq_get failed with [%d], ignored.\n",
+ kt_err);
+ }
+ return kerr;
+ }
+
+ kerr = sss_krb5_free_keytab_entry_contents(kctx, &entry);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_MINOR_FAILURE, "Failed to free keytab entry.\n");
+ kt_err = krb5_kt_end_seq_get(kctx, s_keytab, &cursor);
+ if (kt_err != 0) {
+ DEBUG(SSSDBG_TRACE_ALL,
+ "krb5_kt_end_seq_get failed with [%d], ignored.\n",
+ kt_err);
+ }
+ return kerr;
+ }
+ memset(&entry, 0, sizeof(entry));
+ }
+
+ kerr = krb5_kt_end_seq_get(kctx, s_keytab, &cursor);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "krb5_kt_end_seq_get failed.\n");
+ return kerr;
+ }
+
+ /* check if we got any errors from krb5_kt_next_entry */
+ if (kt_err != 0 && kt_err != KRB5_KT_END) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "error reading keytab.\n");
+ return kt_err;
+ }
+
+ return 0;
+}
+
+krb5_error_code copy_keytab_into_memory(TALLOC_CTX *mem_ctx, krb5_context kctx,
+ const char *inp_keytab_file,
+ char **_mem_name,
+ krb5_keytab *_mem_keytab)
+{
+ krb5_error_code kerr;
+ krb5_keytab keytab = NULL;
+ krb5_keytab mem_keytab = NULL;
+ krb5_keytab tmp_mem_keytab = NULL;
+ char keytab_name[MAX_KEYTAB_NAME_LEN];
+ char *sep;
+ char *mem_name = NULL;
+ char *tmp_mem_name = NULL;
+ const char *keytab_file;
+ char default_keytab_name[MAX_KEYTAB_NAME_LEN];
+
+ keytab_file = inp_keytab_file;
+ if (keytab_file == NULL) {
+ kerr = krb5_kt_default_name(kctx, default_keytab_name,
+ sizeof(default_keytab_name));
+ if (kerr != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "krb5_kt_default_name failed.\n");
+ return kerr;
+ }
+
+ keytab_file = default_keytab_name;
+ }
+
+ kerr = krb5_kt_resolve(kctx, keytab_file, &keytab);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "error resolving keytab [%s].\n",
+ keytab_file);
+ return kerr;
+ }
+
+ kerr = sss_krb5_kt_have_content(kctx, keytab);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "keytab [%s] has not entries.\n",
+ keytab_file);
+ goto done;
+ }
+
+ kerr = krb5_kt_get_name(kctx, keytab, keytab_name, sizeof(keytab_name));
+ if (kerr != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Failed to read name for keytab [%s].\n",
+ keytab_file);
+ goto done;
+ }
+
+ sep = strchr(keytab_name, ':');
+ if (sep == NULL || sep[1] == '\0') {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Keytab name [%s] does not have delimiter[:] .\n", keytab_name);
+ kerr = KRB5KRB_ERR_GENERIC;
+ goto done;
+ }
+
+ if (strncmp(keytab_name, "MEMORY:", sizeof("MEMORY:") -1) == 0) {
+ DEBUG(SSSDBG_TRACE_FUNC, "Keytab [%s] is already memory keytab.\n",
+ keytab_name);
+ *_mem_name = talloc_strdup(mem_ctx, keytab_name);
+ if(*_mem_name == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n");
+ kerr = KRB5KRB_ERR_GENERIC;
+ goto done;
+ }
+ kerr = 0;
+ goto done;
+ }
+
+ mem_name = talloc_asprintf(mem_ctx, "MEMORY:%s", sep + 1);
+ if (mem_name == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n");
+ kerr = KRB5KRB_ERR_GENERIC;
+ goto done;
+ }
+
+ tmp_mem_name = talloc_asprintf(mem_ctx, "MEMORY:%s.tmp", sep + 1);
+ if (tmp_mem_name == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n");
+ kerr = KRB5KRB_ERR_GENERIC;
+ goto done;
+ }
+
+ kerr = krb5_kt_resolve(kctx, mem_name, &mem_keytab);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "error resolving keytab [%s].\n",
+ mem_name);
+ goto done;
+ }
+
+ kerr = krb5_kt_resolve(kctx, tmp_mem_name, &tmp_mem_keytab);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "error resolving keytab [%s].\n",
+ tmp_mem_name);
+ goto done;
+ }
+
+ kerr = do_keytab_copy(kctx, keytab, tmp_mem_keytab);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Failed to copy keytab [%s] into [%s].\n",
+ keytab_file, tmp_mem_name);
+ goto done;
+ }
+
+ /* krb5_kt_add_entry() adds new entries into MEMORY keytabs at the
+ * beginning and not at the end as for FILE keytabs. Since we want to keep
+ * the processing order we have to copy the MEMORY keytab again to retain
+ * the order from the FILE keytab. */
+
+ kerr = do_keytab_copy(kctx, tmp_mem_keytab, mem_keytab);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Failed to copy keytab [%s] into [%s].\n",
+ tmp_mem_name, mem_name);
+ goto done;
+ }
+
+ *_mem_name = mem_name;
+ if (_mem_keytab != NULL) {
+ *_mem_keytab = mem_keytab;
+ }
+
+ kerr = 0;
+done:
+
+ talloc_free(tmp_mem_name);
+
+ if (kerr != 0) {
+ talloc_free(mem_name);
+ if ((mem_keytab != NULL) && krb5_kt_close(kctx, mem_keytab) != 0) {
+ DEBUG(SSSDBG_MINOR_FAILURE, "krb5_kt_close failed.\n");
+ }
+ }
+
+ if (tmp_mem_keytab != NULL && krb5_kt_close(kctx, tmp_mem_keytab) != 0) {
+ DEBUG(SSSDBG_MINOR_FAILURE, "krb5_kt_close failed.\n");
+ }
+
+ if (keytab != NULL && krb5_kt_close(kctx, keytab) != 0) {
+ DEBUG(SSSDBG_MINOR_FAILURE, "krb5_kt_close failed.\n");
+ }
+
+ return kerr;
+}
diff --git a/src/providers/krb5/krb5_opts.c b/src/providers/krb5/krb5_opts.c
new file mode 100644
index 0000000..20dcd58
--- /dev/null
+++ b/src/providers/krb5/krb5_opts.c
@@ -0,0 +1,50 @@
+/*
+ SSSD
+
+ Authors:
+ Stephen Gallagher <sgallagh@redhat.com>
+
+ Copyright (C) 2012 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/providers/data_provider.h"
+
+struct dp_option default_krb5_opts[] = {
+ { "krb5_server", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "krb5_backup_server", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "krb5_realm", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "krb5_ccachedir", DP_OPT_STRING, { DEFAULT_CCACHE_DIR }, NULL_STRING },
+ { "krb5_ccname_template", DP_OPT_STRING, NULL_STRING, NULL_STRING},
+ { "krb5_auth_timeout", DP_OPT_NUMBER, { .number = 6 }, NULL_NUMBER },
+ { "krb5_keytab", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "krb5_validate", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE },
+ { "krb5_kpasswd", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "krb5_backup_kpasswd", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "krb5_store_password_if_offline", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE },
+ { "krb5_renewable_lifetime", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "krb5_lifetime", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "krb5_renew_interval", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "krb5_use_fast", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "krb5_fast_principal", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "krb5_fast_use_anonymous_pkinit", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE },
+ { "krb5_canonicalize", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE },
+ { "krb5_use_enterprise_principal", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE },
+ { "krb5_use_kdcinfo", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE },
+ { "krb5_kdcinfo_lookahead", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "krb5_map_user", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "krb5_use_subdomain_realm", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE },
+ DP_OPTION_TERMINATOR
+};
diff --git a/src/providers/krb5/krb5_opts.h b/src/providers/krb5/krb5_opts.h
new file mode 100644
index 0000000..798008d
--- /dev/null
+++ b/src/providers/krb5/krb5_opts.h
@@ -0,0 +1,30 @@
+/*
+ SSSD
+
+ Authors:
+ Stephen Gallagher <sgallagh@redhat.com>
+
+ Copyright (C) 2012 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 KRB5_OPTS_H_
+#define KRB5_OPTS_H_
+
+#include "src/providers/data_provider.h"
+
+extern struct dp_option default_krb5_opts[];
+
+#endif /* KRB5_OPTS_H_ */
diff --git a/src/providers/krb5/krb5_renew_tgt.c b/src/providers/krb5/krb5_renew_tgt.c
new file mode 100644
index 0000000..9435555
--- /dev/null
+++ b/src/providers/krb5/krb5_renew_tgt.c
@@ -0,0 +1,631 @@
+/*
+ SSSD
+
+ Kerberos 5 Backend Module -- Renew a TGT automatically
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2010 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 <security/pam_modules.h>
+
+#include "util/util.h"
+#include "providers/krb5/krb5_common.h"
+#include "providers/krb5/krb5_auth.h"
+#include "providers/krb5/krb5_utils.h"
+#include "providers/krb5/krb5_ccache.h"
+
+struct renew_tgt_ctx {
+ hash_table_t *tgt_table;
+ struct be_ctx *be_ctx;
+ struct tevent_context *ev;
+ struct krb5_ctx *krb5_ctx;
+ time_t timer_interval;
+ struct tevent_timer *te;
+};
+
+struct renew_data {
+ const char *ccfile;
+ time_t start_time;
+ time_t lifetime;
+ time_t start_renew_at;
+ struct pam_data *pd;
+};
+
+struct auth_data {
+ struct be_ctx *be_ctx;
+ struct krb5_ctx *krb5_ctx;
+ struct pam_data *pd;
+ struct renew_data *renew_data;
+ hash_table_t *table;
+ hash_key_t key;
+};
+
+
+static void renew_tgt_done(struct tevent_req *req);
+static void renew_tgt(struct tevent_context *ev, struct tevent_timer *te,
+ struct timeval current_time, void *private_data)
+{
+ struct auth_data *auth_data = talloc_get_type(private_data,
+ struct auth_data);
+ struct tevent_req *req;
+
+ req = krb5_auth_queue_send(auth_data, ev, auth_data->be_ctx, auth_data->pd,
+ auth_data->krb5_ctx);
+ if (req == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "krb5_auth_send failed.\n");
+/* Give back the pam data to the renewal item to be able to retry at the next
+ * time the renewals re run. */
+ auth_data->renew_data->pd = talloc_steal(auth_data->renew_data,
+ auth_data->pd);
+ talloc_free(auth_data);
+ return;
+ }
+
+ tevent_req_set_callback(req, renew_tgt_done, auth_data);
+}
+
+static void renew_tgt_done(struct tevent_req *req)
+{
+ struct auth_data *auth_data = tevent_req_callback_data(req,
+ struct auth_data);
+ int ret;
+ int pam_status = PAM_SYSTEM_ERR;
+ int dp_err;
+ hash_value_t value;
+
+ ret = krb5_auth_queue_recv(req, &pam_status, &dp_err);
+ talloc_free(req);
+ if (ret) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "krb5_auth request failed.\n");
+ if (auth_data->renew_data != NULL) {
+ DEBUG(SSSDBG_FUNC_DATA, "Giving back pam data.\n");
+ auth_data->renew_data->pd = talloc_steal(auth_data->renew_data,
+ auth_data->pd);
+ }
+ } else {
+ switch (pam_status) {
+ case PAM_SUCCESS:
+ DEBUG(SSSDBG_CONF_SETTINGS,
+ "Successfully renewed TGT for user [%s].\n",
+ auth_data->pd->user);
+/* In general a successful renewal will update the renewal item and free the
+ * old data. But if the TGT has reached the end of his renewable lifetime it
+ * will not be put into the list of renewable tickets again. In this case the
+ * renewal item is not updated and the value from the hash and the one we have
+ * stored are the same. Since the TGT cannot be renewed anymore we want to
+ * remove it from the list of renewable tickets. */
+ ret = hash_lookup(auth_data->table, &auth_data->key, &value);
+ if (ret == HASH_SUCCESS) {
+ if (value.type == HASH_VALUE_PTR &&
+ auth_data->renew_data == talloc_get_type(value.ptr,
+ struct renew_data)) {
+ DEBUG(SSSDBG_FUNC_DATA,
+ "New TGT was not added for renewal, "
+ "removing list entry for user [%s].\n",
+ auth_data->pd->user);
+ ret = hash_delete(auth_data->table, &auth_data->key);
+ if (ret != HASH_SUCCESS) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "hash_delete failed.\n");
+ }
+ }
+ }
+ break;
+ case PAM_AUTHINFO_UNAVAIL:
+ case PAM_AUTHTOK_LOCK_BUSY:
+ DEBUG(SSSDBG_CONF_SETTINGS,
+ "Cannot renewed TGT for user [%s] while offline, "
+ "will retry later.\n",
+ auth_data->pd->user);
+ if (auth_data->renew_data != NULL) {
+ DEBUG(SSSDBG_FUNC_DATA, "Giving back pam data.\n");
+ auth_data->renew_data->pd = talloc_steal(auth_data->renew_data,
+ auth_data->pd);
+ }
+ break;
+ default:
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Failed to renew TGT for user [%s].\n",
+ auth_data->pd->user);
+ ret = hash_delete(auth_data->table, &auth_data->key);
+ if (ret != HASH_SUCCESS) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "hash_delete failed.\n");
+ }
+ }
+ }
+
+ talloc_zfree(auth_data);
+}
+
+static errno_t renew_all_tgts(struct renew_tgt_ctx *renew_tgt_ctx)
+{
+ int ret;
+ hash_entry_t *entries;
+ unsigned long count;
+ size_t c;
+ time_t now;
+ struct auth_data *auth_data;
+ struct renew_data *renew_data;
+ struct tevent_timer *te = NULL;
+
+ ret = hash_entries(renew_tgt_ctx->tgt_table, &count, &entries);
+ if (ret != HASH_SUCCESS) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "hash_entries failed.\n");
+ return ENOMEM;
+ }
+
+ now = time(NULL);
+
+ for (c = 0; c < count; c++) {
+ renew_data = talloc_get_type(entries[c].value.ptr, struct renew_data);
+ DEBUG(SSSDBG_TRACE_ALL,
+ "Checking [%s] for renewal at [%.24s].\n", renew_data->ccfile,
+ ctime(&renew_data->start_renew_at));
+ /* If renew_data->pd == NULL a renewal request for this data is
+ * currently running so we skip it. */
+ if (renew_data->start_renew_at < now && renew_data->pd != NULL) {
+ auth_data = talloc_zero(renew_tgt_ctx, struct auth_data);
+ if (auth_data == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n");
+ } else {
+/* We need to steal the pam_data here, because a successful renewal of the
+ * ticket might add a new renewal item to the list with the same key (upn).
+ * This would delete renew_data and all its children. But we cannot be sure
+ * that adding the new renewal item is the last operation of the renewal
+ * process with access the pam_data. To be on the safe side we steal the
+ * pam_data and make it a child of auth_data which is only freed after the
+ * renewal process is finished. In the case of an error during renewal we
+ * might want to steal the pam_data back to renew_data before freeing
+ * auth_data to allow a new renewal attempt. */
+ auth_data->pd = talloc_move(auth_data, &renew_data->pd);
+ auth_data->krb5_ctx = renew_tgt_ctx->krb5_ctx;
+ auth_data->be_ctx = renew_tgt_ctx->be_ctx;
+ auth_data->table = renew_tgt_ctx->tgt_table;
+ auth_data->renew_data = renew_data;
+ auth_data->key.type = entries[c].key.type;
+ auth_data->key.str = talloc_strdup(auth_data,
+ entries[c].key.str);
+ if (auth_data->key.str == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup failed.\n");
+ } else {
+ te = tevent_add_timer(renew_tgt_ctx->ev,
+ auth_data, tevent_timeval_current(),
+ renew_tgt, auth_data);
+ if (te == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "tevent_add_timer failed.\n");
+ }
+ }
+ }
+
+ if (auth_data == NULL || te == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Failed to renew TGT in [%s].\n", renew_data->ccfile);
+ ret = hash_delete(renew_tgt_ctx->tgt_table, &entries[c].key);
+ if (ret != HASH_SUCCESS) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "hash_delete failed.\n");
+ }
+ }
+ }
+ }
+
+ talloc_free(entries);
+
+ return EOK;
+}
+
+static void renew_handler(struct renew_tgt_ctx *renew_tgt_ctx);
+
+static void renew_tgt_offline_callback(void *private_data)
+{
+ struct renew_tgt_ctx *renew_tgt_ctx = talloc_get_type(private_data,
+ struct renew_tgt_ctx);
+
+ talloc_zfree(renew_tgt_ctx->te);
+}
+
+static void renew_tgt_online_callback(void *private_data)
+{
+ struct renew_tgt_ctx *renew_tgt_ctx = talloc_get_type(private_data,
+ struct renew_tgt_ctx);
+
+ renew_handler(renew_tgt_ctx);
+}
+
+static void renew_tgt_timer_handler(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval current_time, void *data)
+{
+ struct renew_tgt_ctx *renew_tgt_ctx = talloc_get_type(data,
+ struct renew_tgt_ctx);
+
+ /* forget the timer event, it will be freed by the tevent timer loop */
+ renew_tgt_ctx->te = NULL;
+
+ renew_handler(renew_tgt_ctx);
+}
+
+static void renew_handler(struct renew_tgt_ctx *renew_tgt_ctx)
+{
+ struct timeval next;
+ int ret;
+
+ if (be_is_offline(renew_tgt_ctx->be_ctx)) {
+ DEBUG(SSSDBG_CONF_SETTINGS, "Offline, disable renew timer.\n");
+ return;
+ }
+
+ ret = renew_all_tgts(renew_tgt_ctx);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "renew_all_tgts failed. "
+ "Disabling automatic TGT renewal\n");
+ sss_log(SSS_LOG_ERR, "Disabling automatic TGT renewal.");
+ talloc_zfree(renew_tgt_ctx);
+ return;
+ }
+
+ if (renew_tgt_ctx->te != NULL) {
+ DEBUG(SSSDBG_TRACE_LIBS,
+ "There is an active renewal timer, doing nothing.\n");
+ return;
+ }
+
+ DEBUG(SSSDBG_TRACE_LIBS, "Adding new renew timer.\n");
+
+ next = sss_tevent_timeval_current_ofs_time_t(renew_tgt_ctx->timer_interval);
+ renew_tgt_ctx->te = tevent_add_timer(renew_tgt_ctx->ev, renew_tgt_ctx,
+ next, renew_tgt_timer_handler,
+ renew_tgt_ctx);
+ if (renew_tgt_ctx->te == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "tevent_add_timer failed.\n");
+ sss_log(SSS_LOG_ERR, "Disabling automatic TGT renewal.");
+ talloc_zfree(renew_tgt_ctx);
+ }
+
+ return;
+}
+
+static void renew_del_cb(hash_entry_t *entry, hash_destroy_enum type, void *pvt)
+{
+ struct renew_data *renew_data;
+
+ if (entry->value.type == HASH_VALUE_PTR) {
+ renew_data = talloc_get_type(entry->value.ptr, struct renew_data);
+ talloc_zfree(renew_data);
+ return;
+ }
+
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Unexpected value type [%d].\n", entry->value.type);
+}
+
+static errno_t check_ccache_file(struct renew_tgt_ctx *renew_tgt_ctx,
+ const char *ccache_file, const char *upn,
+ const char *user_name)
+{
+ int ret;
+ struct stat stat_buf;
+ struct tgt_times tgtt;
+ struct pam_data pd;
+ time_t now;
+ const char *filename;
+
+ if (ccache_file == NULL || upn == NULL || user_name == NULL) {
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "Missing one of the needed attributes: [%s][%s][%s].\n",
+ ccache_file == NULL ? "cache file missing" : ccache_file,
+ upn == NULL ? "principal missing" : upn,
+ user_name == NULL ? "user name missing" : user_name);
+ return EINVAL;
+ }
+
+ if (strncmp(ccache_file, "FILE:", 5) == 0) {
+ filename = ccache_file + 5;
+ } else {
+ filename = ccache_file;
+ }
+
+ ret = stat(filename, &stat_buf);
+ if (ret != EOK) {
+ if (ret == ENOENT) {
+ return EOK;
+ }
+ return ret;
+ }
+
+ DEBUG(SSSDBG_TRACE_ALL, "Found ccache file [%s].\n", ccache_file);
+
+ memset(&tgtt, 0, sizeof(tgtt));
+ ret = get_ccache_file_data(ccache_file, upn, &tgtt);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "get_ccache_file_data failed.\n");
+ return ret;
+ }
+
+ memset(&pd, 0, sizeof(pd));
+ pd.cmd = SSS_CMD_RENEW;
+ pd.user = discard_const_p(char, user_name);
+ now = time(NULL);
+ if (tgtt.renew_till > tgtt.endtime && tgtt.renew_till > now &&
+ tgtt.endtime > now) {
+ DEBUG(SSSDBG_TRACE_LIBS,
+ "Adding [%s] for automatic renewal.\n", ccache_file);
+ ret = add_tgt_to_renew_table(renew_tgt_ctx->krb5_ctx, ccache_file,
+ &tgtt, &pd, upn);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "add_tgt_to_renew_table failed, "
+ "automatic renewal not possible.\n");
+ }
+ } else {
+ DEBUG(SSSDBG_TRACE_ALL,
+ "TGT in [%s] for [%s] is too old.\n", ccache_file, upn);
+ }
+
+ return EOK;
+}
+
+static errno_t check_ccache_files(struct renew_tgt_ctx *renew_tgt_ctx)
+{
+ TALLOC_CTX *tmp_ctx;
+ int ret;
+ const char *ccache_filter = SYSDB_CCACHE_FILE"=*";
+ const char *ccache_attrs[] = { SYSDB_CCACHE_FILE, SYSDB_UPN, SYSDB_NAME,
+ SYSDB_CANONICAL_UPN, NULL };
+ size_t msgs_count = 0;
+ struct ldb_message **msgs = NULL;
+ size_t c;
+ const char *ccache_file;
+ char *upn;
+ const char *user_name;
+ struct ldb_dn *base_dn;
+ char *user_dom;
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new failed.\n");
+ return ENOMEM;
+ }
+
+ base_dn = sysdb_user_base_dn(tmp_ctx, renew_tgt_ctx->be_ctx->domain);
+ if (base_dn == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_base_dn failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = sysdb_search_entry(tmp_ctx, renew_tgt_ctx->be_ctx->domain->sysdb, base_dn,
+ LDB_SCOPE_SUBTREE, ccache_filter, ccache_attrs,
+ &msgs_count, &msgs);
+ if (ret == ENOENT) {
+ msgs_count = 0; /* Fall through */
+ } else if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_search_entry failed.\n");
+ goto done;
+ }
+
+ if (msgs_count == 0) {
+ DEBUG(SSSDBG_TRACE_ALL,
+ "No entries with ccache file found in cache.\n");
+ ret = EOK;
+ goto done;
+ }
+ DEBUG(SSSDBG_TRACE_ALL,
+ "Found [%zu] entries with ccache file in cache.\n", msgs_count);
+
+ for (c = 0; c < msgs_count; c++) {
+ user_name = ldb_msg_find_attr_as_string(msgs[c], SYSDB_NAME, NULL);
+ if (user_name == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "No user name found, this is a severe error, "
+ "but we ignore it here.\n");
+ continue;
+ }
+
+ ret = sss_parse_internal_fqname(tmp_ctx, user_name, NULL, &user_dom);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Cannot parse internal fqname [%d]: %s\n",
+ ret, sss_strerror(ret));
+ goto done;
+ }
+
+ ret = find_or_guess_upn(tmp_ctx, msgs[c], renew_tgt_ctx->krb5_ctx,
+ renew_tgt_ctx->be_ctx->domain,
+ user_name, user_dom, &upn);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "find_or_guess_upn failed.\n");
+ goto done;
+ }
+
+ ccache_file = ldb_msg_find_attr_as_string(msgs[c], SYSDB_CCACHE_FILE,
+ NULL);
+
+ ret = check_ccache_file(renew_tgt_ctx, ccache_file, upn, user_name);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_FUNC_DATA,
+ "Failed to check ccache file [%s].\n", ccache_file);
+ }
+ }
+
+ ret = EOK;
+
+done:
+ talloc_free(tmp_ctx);
+
+ return ret;
+}
+
+errno_t init_renew_tgt(struct krb5_ctx *krb5_ctx, struct be_ctx *be_ctx,
+ struct tevent_context *ev, time_t renew_intv)
+{
+ int ret;
+ struct timeval next;
+
+ krb5_ctx->renew_tgt_ctx = talloc_zero(krb5_ctx, struct renew_tgt_ctx);
+ if (krb5_ctx->renew_tgt_ctx == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n");
+ return ENOMEM;
+ }
+
+ ret = sss_hash_create_ex(krb5_ctx->renew_tgt_ctx, 0,
+ &krb5_ctx->renew_tgt_ctx->tgt_table, 0, 0, 0, 0,
+ renew_del_cb, NULL);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "sss_hash_create failed.\n");
+ goto fail;
+ }
+
+ krb5_ctx->renew_tgt_ctx->be_ctx = be_ctx;
+ krb5_ctx->renew_tgt_ctx->krb5_ctx = krb5_ctx;
+ krb5_ctx->renew_tgt_ctx->ev = ev;
+ krb5_ctx->renew_tgt_ctx->timer_interval = renew_intv;
+
+ ret = check_ccache_files(krb5_ctx->renew_tgt_ctx);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Failed to read ccache files, continuing ...\n");
+ }
+
+ next = sss_tevent_timeval_current_ofs_time_t(krb5_ctx->renew_tgt_ctx->timer_interval);
+ krb5_ctx->renew_tgt_ctx->te = tevent_add_timer(ev, krb5_ctx->renew_tgt_ctx,
+ next, renew_tgt_timer_handler,
+ krb5_ctx->renew_tgt_ctx);
+ if (krb5_ctx->renew_tgt_ctx->te == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "tevent_add_timer failed.\n");
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ DEBUG(SSSDBG_TRACE_LIBS,
+ "Adding offline callback to remove renewal timer.\n");
+ ret = be_add_offline_cb(krb5_ctx->renew_tgt_ctx, be_ctx,
+ renew_tgt_offline_callback, krb5_ctx->renew_tgt_ctx,
+ NULL);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Failed to add offline callback.\n");
+ goto fail;
+ }
+
+ DEBUG(SSSDBG_TRACE_LIBS, "Adding renewal task to online callbacks.\n");
+ ret = be_add_online_cb(krb5_ctx->renew_tgt_ctx, be_ctx,
+ renew_tgt_online_callback, krb5_ctx->renew_tgt_ctx,
+ NULL);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Failed to add renewal task to online callbacks.\n");
+ goto fail;
+ }
+
+ return EOK;
+
+fail:
+ talloc_zfree(krb5_ctx->renew_tgt_ctx);
+ return ret;
+}
+
+errno_t add_tgt_to_renew_table(struct krb5_ctx *krb5_ctx, const char *ccfile,
+ struct tgt_times *tgtt, struct pam_data *pd,
+ const char *upn)
+{
+ int ret;
+ hash_key_t key;
+ hash_value_t value;
+ struct renew_data *renew_data = NULL;
+
+ if (krb5_ctx->renew_tgt_ctx == NULL) {
+ DEBUG(SSSDBG_TRACE_LIBS ,"Renew context not initialized, "
+ "automatic renewal not available.\n");
+ return EOK;
+ }
+
+ if (pd->cmd != SSS_PAM_AUTHENTICATE && pd->cmd != SSS_CMD_RENEW &&
+ pd->cmd != SSS_PAM_CHAUTHTOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected pam task [%d].\n", pd->cmd);
+ return EINVAL;
+ }
+
+ if (upn == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Missing user principal name.\n");
+ return EINVAL;
+ }
+
+ /* hash_enter copies the content of the hash string, so it is safe to use
+ * discard_const_p here. */
+ key.type = HASH_KEY_STRING;
+ key.str = discard_const_p(char, upn);
+
+ renew_data = talloc_zero(krb5_ctx->renew_tgt_ctx, struct renew_data);
+ if (renew_data == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ if (ccfile[0] == '/') {
+ renew_data->ccfile = talloc_asprintf(renew_data, "FILE:%s", ccfile);
+ if (renew_data->ccfile == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+ } else {
+ renew_data->ccfile = talloc_strdup(renew_data, ccfile);
+ }
+
+ renew_data->start_time = tgtt->starttime;
+ renew_data->lifetime = tgtt->endtime;
+ renew_data->start_renew_at = (time_t) (tgtt->starttime +
+ 0.5 *(tgtt->endtime - tgtt->starttime));
+
+ ret = copy_pam_data(renew_data, pd, &renew_data->pd);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "copy_pam_data failed.\n");
+ goto done;
+ }
+
+ sss_authtok_set_empty(renew_data->pd->newauthtok);
+
+ ret = sss_authtok_set_ccfile(renew_data->pd->authtok, renew_data->ccfile, 0);
+ if (ret) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Failed to store ccfile in auth token.\n");
+ goto done;
+ }
+
+ renew_data->pd->cmd = SSS_CMD_RENEW;
+
+ value.type = HASH_VALUE_PTR;
+ value.ptr = renew_data;
+
+ ret = hash_enter(krb5_ctx->renew_tgt_ctx->tgt_table, &key, &value);
+ if (ret != HASH_SUCCESS) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "hash_enter failed.\n");
+ ret = EFAULT;
+ goto done;
+ }
+
+ DEBUG(SSSDBG_TRACE_LIBS,
+ "Added [%s] for renewal at [%.24s].\n", renew_data->ccfile,
+ ctime(&renew_data->start_renew_at));
+
+ ret = EOK;
+
+done:
+ if (ret != EOK) {
+ talloc_free(renew_data);
+ }
+ return ret;
+}
diff --git a/src/providers/krb5/krb5_utils.c b/src/providers/krb5/krb5_utils.c
new file mode 100644
index 0000000..b890745
--- /dev/null
+++ b/src/providers/krb5/krb5_utils.c
@@ -0,0 +1,605 @@
+/*
+ SSSD
+
+ Kerberos 5 Backend Module -- Utilities
+
+ Authors:
+ 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/>.
+*/
+#include <string.h>
+#include <stdlib.h>
+#include <libgen.h>
+
+#include "providers/krb5/krb5_utils.h"
+#include "providers/krb5/krb5_ccache.h"
+#include "providers/krb5/krb5_auth.h"
+#include "src/util/find_uid.h"
+#include "util/util.h"
+
+errno_t find_or_guess_upn(TALLOC_CTX *mem_ctx, struct ldb_message *msg,
+ struct krb5_ctx *krb5_ctx,
+ struct sss_domain_info *dom, const char *user,
+ const char *user_dom, char **_upn)
+{
+ const char *upn = NULL;
+ int ret;
+
+ if (krb5_ctx == NULL || dom == NULL || user == NULL || _upn == NULL) {
+ return EINVAL;
+ }
+
+ if (msg != NULL) {
+ upn = ldb_msg_find_attr_as_string(msg, SYSDB_CANONICAL_UPN, NULL);
+ if (upn != NULL) {
+ ret = EOK;
+ goto done;
+ }
+
+ upn = ldb_msg_find_attr_as_string(msg, SYSDB_UPN, NULL);
+ if (upn != NULL) {
+ ret = EOK;
+ goto done;
+ }
+ }
+
+ ret = krb5_get_simple_upn(mem_ctx, krb5_ctx, dom, user,
+ user_dom, _upn);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "krb5_get_simple_upn failed.\n");
+ return ret;
+ }
+
+done:
+ if (ret == EOK && upn != NULL) {
+ *_upn = talloc_strdup(mem_ctx, upn);
+ if (*_upn == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n");
+ return ENOMEM;
+ }
+ }
+
+ return ret;
+}
+
+errno_t check_if_cached_upn_needs_update(struct sysdb_ctx *sysdb,
+ struct sss_domain_info *domain,
+ const char *user,
+ const char *upn)
+{
+ TALLOC_CTX *tmp_ctx;
+ int ret;
+ int sret;
+ const char *attrs[] = {SYSDB_UPN, SYSDB_CANONICAL_UPN, NULL};
+ struct sysdb_attrs *new_attrs;
+ struct ldb_result *res;
+ bool in_transaction = false;
+ const char *cached_upn;
+ const char *cached_canonical_upn;
+
+ if (sysdb == NULL || user == NULL || upn == NULL) {
+ return EINVAL;
+ }
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n");
+ return ENOMEM;
+ }
+
+ ret = sysdb_get_user_attr(tmp_ctx, domain, user, attrs, &res);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "sysdb_get_user_attr failed.\n");
+ goto done;
+ }
+
+ if (res->count != 1) {
+ DEBUG(SSSDBG_OP_FAILURE, "[%d] user objects for name [%s] found, " \
+ "expected 1.\n", res->count, user);
+ ret = EINVAL;
+ goto done;
+ }
+
+ cached_upn = ldb_msg_find_attr_as_string(res->msgs[0], SYSDB_UPN, NULL);
+
+ if (cached_upn != NULL && strcmp(cached_upn, upn) == 0) {
+ DEBUG(SSSDBG_TRACE_ALL, "Cached UPN and new one match, "
+ "nothing to do.\n");
+ ret = EOK;
+ goto done;
+ }
+
+ cached_canonical_upn = ldb_msg_find_attr_as_string(res->msgs[0],
+ SYSDB_CANONICAL_UPN,
+ NULL);
+
+ if (cached_canonical_upn != NULL
+ && strcmp(cached_canonical_upn, upn) == 0) {
+ DEBUG(SSSDBG_TRACE_ALL, "Cached canonical UPN and new one match, "
+ "nothing to do.\n");
+ ret = EOK;
+ goto done;
+ }
+
+ DEBUG(SSSDBG_TRACE_LIBS, "Replacing canonical UPN [%s] with [%s] " \
+ "for user [%s].\n",
+ cached_canonical_upn == NULL ?
+ "empty" : cached_canonical_upn,
+ upn, user);
+
+ new_attrs = sysdb_new_attrs(tmp_ctx);
+ if (new_attrs == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "sysdb_new_attrs failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = sysdb_attrs_add_string(new_attrs, SYSDB_CANONICAL_UPN, upn);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_add_string failed.\n");
+ goto done;
+ }
+
+ ret = sysdb_transaction_start(sysdb);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Error %d starting transaction (%s)\n", ret, strerror(ret));
+ goto done;
+ }
+ in_transaction = true;
+
+ ret = sysdb_set_entry_attr(sysdb, res->msgs[0]->dn, new_attrs,
+ cached_canonical_upn == NULL ? SYSDB_MOD_ADD :
+ SYSDB_MOD_REP);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "sysdb_set_entry_attr failed [%d][%s].\n",
+ ret, strerror(ret));
+ goto done;
+ }
+
+ ret = sysdb_transaction_commit(sysdb);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "Failed to commit transaction!\n");
+ goto done;
+ }
+ in_transaction = false;
+
+ ret = EOK;
+
+done:
+ if (in_transaction) {
+ sret = sysdb_transaction_cancel(sysdb);
+ if (sret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Failed to cancel transaction\n");
+ }
+ }
+
+ talloc_free(tmp_ctx);
+
+ return ret;
+}
+
+#define S_EXP_UID "{uid}"
+#define L_EXP_UID (sizeof(S_EXP_UID) - 1)
+#define S_EXP_USERID "{USERID}"
+#define L_EXP_USERID (sizeof(S_EXP_USERID) - 1)
+#define S_EXP_EUID "{euid}"
+#define L_EXP_EUID (sizeof(S_EXP_EUID) - 1)
+#define S_EXP_USERNAME "{username}"
+#define L_EXP_USERNAME (sizeof(S_EXP_USERNAME) - 1)
+
+static errno_t
+check_ccache_re(const char *filename, sss_regexp_t *illegal_re)
+{
+ errno_t ret;
+
+ ret = sss_regexp_match(illegal_re, filename, 0, 0);
+
+ if (ret == 0) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Illegal pattern in ccache directory name [%s].\n", filename);
+ return EINVAL;
+ } else if (ret == SSS_REGEXP_ERROR_NOMATCH) {
+ DEBUG(SSSDBG_TRACE_LIBS,
+ "Ccache directory name [%s] does not contain "
+ "illegal patterns.\n", filename);
+ return EOK;
+ }
+
+ DEBUG(SSSDBG_CRIT_FAILURE, "regexp match failed [%d].\n", ret);
+ return EFAULT;
+}
+
+char *expand_ccname_template(TALLOC_CTX *mem_ctx, struct krb5child_req *kr,
+ const char *template, sss_regexp_t *illegal_re,
+ bool file_mode, bool case_sensitive)
+{
+ char *copy;
+ char *p;
+ char *n;
+ char *result = NULL;
+ char *dummy;
+ char *name;
+ char *res = NULL;
+ const char *cache_dir_tmpl;
+ TALLOC_CTX *tmp_ctx = NULL;
+ char action;
+ bool rerun;
+ int ret;
+
+ if (template == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Missing template.\n");
+ return NULL;
+ }
+
+ tmp_ctx = talloc_new(NULL);
+ if (!tmp_ctx) return NULL;
+
+ copy = talloc_strdup(tmp_ctx, template);
+ if (copy == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup failed.\n");
+ goto done;
+ }
+
+ result = talloc_strdup(tmp_ctx, "");
+ if (result == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup failed.\n");
+ goto done;
+ }
+
+ p = copy;
+ while ( (n = strchr(p, '%')) != NULL) {
+ *n = '\0';
+ n++;
+ if ( *n == '\0' ) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "format error, single %% at the end of the template.\n");
+ goto done;
+ }
+
+ rerun = true;
+ action = *n;
+ while (rerun) {
+ rerun = false;
+ switch (action) {
+ case 'u':
+ if (kr->pd->user == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Cannot expand user name template "
+ "because user name is empty.\n");
+ goto done;
+ }
+
+ name = sss_output_name(tmp_ctx, kr->pd->user, case_sensitive, 0);
+ if (name == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "sss_output_name failed\n");
+ goto done;
+ }
+
+ result = talloc_asprintf_append(result, "%s%s", p,
+ name);
+ break;
+ case 'U':
+ if (kr->uid <= 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Cannot expand uid template "
+ "because uid is invalid.\n");
+ goto done;
+ }
+ result = talloc_asprintf_append(result, "%s%"SPRIuid, p,
+ kr->uid);
+ break;
+ case 'p':
+ if (kr->upn == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Cannot expand user principal name template "
+ "because upn is empty.\n");
+ goto done;
+ }
+ result = talloc_asprintf_append(result, "%s%s", p, kr->upn);
+ break;
+ case '%':
+ result = talloc_asprintf_append(result, "%s%%", p);
+ break;
+ case 'r':
+ dummy = dp_opt_get_string(kr->krb5_ctx->opts, KRB5_REALM);
+ if (dummy == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Missing kerberos realm.\n");
+ goto done;
+ }
+ result = talloc_asprintf_append(result, "%s%s", p, dummy);
+ break;
+ case 'h':
+ if (kr->homedir == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Cannot expand home directory template "
+ "because the path is not available.\n");
+ goto done;
+ }
+ result = talloc_asprintf_append(result, "%s%s", p, kr->homedir);
+ break;
+ case 'd':
+ if (file_mode) {
+ cache_dir_tmpl = dp_opt_get_string(kr->krb5_ctx->opts,
+ KRB5_CCACHEDIR);
+ if (cache_dir_tmpl == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Missing credential cache directory.\n");
+ goto done;
+ }
+
+ dummy = expand_ccname_template(tmp_ctx, kr, cache_dir_tmpl,
+ illegal_re, false, case_sensitive);
+ if (dummy == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Expanding credential cache directory "
+ "template failed.\n");
+ goto done;
+ }
+ result = talloc_asprintf_append(result, "%s%s", p, dummy);
+ talloc_zfree(dummy);
+ } else {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "'%%d' is not allowed in this template.\n");
+ goto done;
+ }
+ break;
+ case 'P':
+ if (!file_mode) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "'%%P' is not allowed in this template.\n");
+ goto done;
+ }
+ if (kr->pd->cli_pid == 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Cannot expand PID template "
+ "because PID is not available.\n");
+ goto done;
+ }
+ result = talloc_asprintf_append(result, "%s%d", p,
+ kr->pd->cli_pid);
+ break;
+
+ /* Additional syntax from krb5.conf default_ccache_name */
+ case '{':
+ if (strncmp(n , S_EXP_UID, L_EXP_UID) == 0) {
+ action = 'U';
+ n += L_EXP_UID - 1;
+ rerun = true;
+ continue;
+ } else if (strncmp(n , S_EXP_USERID, L_EXP_USERID) == 0) {
+ action = 'U';
+ n += L_EXP_USERID - 1;
+ rerun = true;
+ continue;
+ } else if (strncmp(n , S_EXP_EUID, L_EXP_EUID) == 0) {
+ /* SSSD does not distinguish between uid and euid,
+ * so we treat both the same way */
+ action = 'U';
+ n += L_EXP_EUID - 1;
+ rerun = true;
+ continue;
+ } else if (strncmp(n , S_EXP_USERNAME, L_EXP_USERNAME) == 0) {
+ action = 'u';
+ n += L_EXP_USERNAME - 1;
+ rerun = true;
+ continue;
+ } else {
+ /* ignore any expansion variable we do not understand and
+ * let libkrb5 hndle it or fail */
+ name = n;
+ n = strchr(name, '}');
+ if (!n) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Invalid substitution sequence in cache "
+ "template. Missing closing '}' in [%s].\n",
+ template);
+ goto done;
+ }
+ result = talloc_asprintf_append(result, "%s%%%.*s", p,
+ (int)(n - name + 1), name);
+ }
+ break;
+ default:
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "format error, unknown template [%%%c].\n", *n);
+ goto done;
+ }
+ }
+
+ if (result == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf_append failed.\n");
+ goto done;
+ }
+
+ p = n + 1;
+ }
+
+ result = talloc_asprintf_append(result, "%s", p);
+ if (result == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf_append failed.\n");
+ goto done;
+ }
+
+ if (illegal_re != NULL) {
+ ret = check_ccache_re(result, illegal_re);
+ if (ret != EOK) {
+ goto done;
+ }
+ }
+
+ res = talloc_move(mem_ctx, &result);
+done:
+ talloc_zfree(tmp_ctx);
+ return res;
+}
+
+errno_t get_domain_or_subdomain(struct be_ctx *be_ctx,
+ char *domain_name,
+ struct sss_domain_info **dom)
+{
+
+ if (domain_name != NULL &&
+ strcasecmp(domain_name, be_ctx->domain->name) != 0) {
+ *dom = find_domain_by_name(be_ctx->domain, domain_name, true);
+ if (*dom == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "find_domain_by_name failed.\n");
+ return ENOMEM;
+ }
+ } else {
+ *dom = be_ctx->domain;
+ }
+
+ return EOK;
+}
+
+static errno_t split_tuple(TALLOC_CTX *mem_ctx, const char *tuple,
+ const char **_first, const char **_second)
+{
+ errno_t ret;
+ char **list;
+ int n;
+
+ ret = split_on_separator(mem_ctx, tuple, ':', true, true, &list, &n);
+
+ if (ret != EOK) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "split_on_separator failed - %s:[%d]\n",
+ sss_strerror(ret), ret);
+ goto done;
+ } else if (n != 2) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "split_on_separator failed - Expected format is: "
+ "'username:primary' but got: '%s'.\n", tuple);
+ ret = EINVAL;
+ goto done;
+ }
+
+ *_first = list[0];
+ *_second = list[1];
+
+done:
+ return ret;
+}
+
+static errno_t
+fill_name_to_primary_map(TALLOC_CTX *mem_ctx, char **map,
+ struct map_id_name_to_krb_primary *name_to_primary,
+ size_t size)
+{
+ int i;
+ errno_t ret;
+
+ for (i = 0; i < size; i++) {
+ ret = split_tuple(mem_ctx, map[i],
+ &name_to_primary[i].id_name,
+ &name_to_primary[i].krb_primary);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "split_tuple failed - %s:[%d]\n", sss_strerror(ret), ret);
+ goto done;
+ }
+ }
+
+ ret = EOK;
+
+done:
+ return ret;
+}
+
+errno_t
+parse_krb5_map_user(TALLOC_CTX *mem_ctx,
+ const char *krb5_map_user,
+ const char *dom_name,
+ struct map_id_name_to_krb_primary **_name_to_primary)
+{
+ int size;
+ char **map = NULL;
+ errno_t ret;
+ TALLOC_CTX *tmp_ctx;
+ struct map_id_name_to_krb_primary *name_to_primary;
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ if (krb5_map_user == NULL || strlen(krb5_map_user) == 0) {
+ DEBUG(SSSDBG_CONF_SETTINGS, "krb5_map_user is empty!\n");
+ size = 0;
+ } else {
+ ret = split_on_separator(tmp_ctx, krb5_map_user, ',', true, true,
+ &map, &size);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "Failed to parse krb5_map_user!\n");
+ goto done;
+ }
+ }
+
+ name_to_primary = talloc_zero_array(tmp_ctx,
+ struct map_id_name_to_krb_primary,
+ size + 1);
+ if (name_to_primary == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ /* sentinel */
+ name_to_primary[size].id_name = NULL;
+ name_to_primary[size].krb_primary = NULL;
+
+ if (size > 0) {
+ ret = fill_name_to_primary_map(name_to_primary, map, name_to_primary,
+ size);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "fill_name_to_primary_map failed: %s:[%d]\n",
+ sss_strerror(ret), ret);
+ goto done;
+ }
+ }
+
+ /* conversion names to fully-qualified names */
+ for (int i = 0; i < size; i++) {
+ name_to_primary[i].id_name = sss_create_internal_fqname(
+ name_to_primary,
+ name_to_primary[i].id_name,
+ dom_name);
+ if (name_to_primary[i].id_name == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "sss_create_internal_fqname failed\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ name_to_primary[i].krb_primary = sss_create_internal_fqname(
+ name_to_primary,
+ name_to_primary[i].krb_primary,
+ dom_name);
+ if (name_to_primary[i].krb_primary == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "sss_create_internal_fqname failed\n");
+ ret = ENOMEM;
+ goto done;
+ }
+ }
+ ret = EOK;
+
+done:
+ if (ret == EOK) {
+ *_name_to_primary = talloc_steal(mem_ctx, name_to_primary);
+ }
+ talloc_free(tmp_ctx);
+ return ret;
+}
diff --git a/src/providers/krb5/krb5_utils.h b/src/providers/krb5/krb5_utils.h
new file mode 100644
index 0000000..473006b
--- /dev/null
+++ b/src/providers/krb5/krb5_utils.h
@@ -0,0 +1,59 @@
+/*
+ SSSD
+
+ Kerberos Backend, header file for utilities
+
+ Authors:
+ 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 __KRB5_UTILS_H__
+#define __KRB5_UTILS_H__
+
+#include <talloc.h>
+#include "config.h"
+
+#include "providers/krb5/krb5_auth.h"
+#include "providers/data_provider.h"
+
+errno_t find_or_guess_upn(TALLOC_CTX *mem_ctx, struct ldb_message *msg,
+ struct krb5_ctx *krb5_ctx,
+ struct sss_domain_info *dom, const char *user,
+ const char *user_dom, char **_upn);
+
+errno_t check_if_cached_upn_needs_update(struct sysdb_ctx *sysdb,
+ struct sss_domain_info *domain,
+ const char *user,
+ const char *upn);
+
+char *expand_ccname_template(TALLOC_CTX *mem_ctx, struct krb5child_req *kr,
+ const char *template, sss_regexp_t *illegal_re,
+ bool file_mode, bool case_sensitive);
+
+errno_t get_domain_or_subdomain(struct be_ctx *be_ctx,
+ char *domain_name,
+ struct sss_domain_info **dom);
+
+errno_t
+parse_krb5_map_user(TALLOC_CTX *mem_ctx,
+ const char *krb5_map_user,
+ const char *dom_name,
+ struct map_id_name_to_krb_primary **_name_to_primary);
+
+#endif /* __KRB5_UTILS_H__ */
diff --git a/src/providers/krb5/krb5_wait_queue.c b/src/providers/krb5/krb5_wait_queue.c
new file mode 100644
index 0000000..06d7a98
--- /dev/null
+++ b/src/providers/krb5/krb5_wait_queue.c
@@ -0,0 +1,373 @@
+/*
+ SSSD
+
+ Kerberos 5 Backend Module - Serialize the request of a user
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2010 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 <tevent.h>
+#include <dhash.h>
+
+#include <security/pam_modules.h>
+
+#include "src/providers/krb5/krb5_auth.h"
+
+struct queue_entry {
+ struct queue_entry *prev;
+ struct queue_entry *next;
+
+ struct be_ctx *be_ctx;
+ struct be_req *be_req;
+ struct tevent_req *parent_req;
+ struct pam_data *pd;
+ struct krb5_ctx *krb5_ctx;
+};
+
+static void wait_queue_auth_done(struct tevent_req *req);
+
+static void krb5_auth_queue_finish(struct tevent_req *req, errno_t ret,
+ int pam_status, int dp_err);
+
+static void wait_queue_auth(struct tevent_context *ev, struct tevent_timer *te,
+ struct timeval current_time, void *private_data)
+{
+ struct queue_entry *qe = talloc_get_type(private_data, struct queue_entry);
+ struct tevent_req *req;
+
+ req = krb5_auth_send(qe->parent_req, qe->be_ctx->ev,
+ qe->be_ctx, qe->pd, qe->krb5_ctx);
+ if (req == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "krb5_auth_send failed.\n");
+ } else {
+ tevent_req_set_callback(req, wait_queue_auth_done,
+ qe->parent_req);
+ }
+
+ talloc_zfree(qe);
+}
+
+static void wait_queue_auth_done(struct tevent_req *req)
+{
+ struct tevent_req *parent_req = \
+ tevent_req_callback_data(req, struct tevent_req);
+ int pam_status;
+ int dp_err;
+ errno_t ret;
+
+ ret = krb5_auth_recv(req, &pam_status, &dp_err);
+ talloc_zfree(req);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "krb5_auth_recv failed: %d\n", ret);
+ }
+
+ krb5_auth_queue_finish(parent_req, ret, pam_status, dp_err);
+}
+
+static void wait_queue_del_cb(hash_entry_t *entry, hash_destroy_enum type,
+ void *pvt)
+{
+ struct queue_entry *head;
+
+ if (entry->value.type == HASH_VALUE_PTR) {
+ head = talloc_get_type(entry->value.ptr, struct queue_entry);
+ talloc_zfree(head);
+ return;
+ }
+
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Unexpected value type [%d].\n", entry->value.type);
+}
+
+static errno_t add_to_wait_queue(struct be_ctx *be_ctx,
+ struct tevent_req *parent_req,
+ struct pam_data *pd,
+ struct krb5_ctx *krb5_ctx)
+{
+ int ret;
+ hash_key_t key;
+ hash_value_t value;
+ struct queue_entry *head;
+ struct queue_entry *queue_entry;
+
+ if (krb5_ctx->wait_queue_hash == NULL) {
+ ret = sss_hash_create_ex(krb5_ctx, 0,
+ &krb5_ctx->wait_queue_hash, 0, 0, 0, 0,
+ wait_queue_del_cb, NULL);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "sss_hash_create failed\n");
+ return ret;
+ }
+ }
+
+ key.type = HASH_KEY_STRING;
+ key.str = pd->user;
+
+ ret = hash_lookup(krb5_ctx->wait_queue_hash, &key, &value);
+ switch (ret) {
+ case HASH_SUCCESS:
+ if (value.type != HASH_VALUE_PTR) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected hash value type.\n");
+ return EINVAL;
+ }
+
+ head = talloc_get_type(value.ptr, struct queue_entry);
+
+ queue_entry = talloc_zero(head, struct queue_entry);
+ if (queue_entry == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n");
+ return ENOMEM;
+ }
+
+ queue_entry->be_ctx = be_ctx;
+ queue_entry->parent_req = parent_req;
+ queue_entry->pd = pd;
+ queue_entry->krb5_ctx = krb5_ctx;
+
+ DLIST_ADD_END(head, queue_entry, struct queue_entry *);
+
+ break;
+ case HASH_ERROR_KEY_NOT_FOUND:
+ value.type = HASH_VALUE_PTR;
+ head = talloc_zero(krb5_ctx->wait_queue_hash, struct queue_entry);
+ if (head == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n");
+ return ENOMEM;
+ }
+ value.ptr = head;
+
+ ret = hash_enter(krb5_ctx->wait_queue_hash, &key, &value);
+ if (ret != HASH_SUCCESS) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "hash_enter failed.\n");
+ talloc_free(head);
+ return EIO;
+ }
+
+ break;
+ default:
+ DEBUG(SSSDBG_CRIT_FAILURE, "hash_lookup failed.\n");
+ return EIO;
+ }
+
+ if (head->next == NULL) {
+ return ENOENT;
+ } else {
+ return EOK;
+ }
+}
+
+static void check_wait_queue(struct krb5_ctx *krb5_ctx, char *username)
+{
+ int ret;
+ hash_key_t key;
+ hash_value_t value;
+ struct queue_entry *head;
+ struct queue_entry *queue_entry;
+ struct tevent_timer *te;
+
+ if (krb5_ctx->wait_queue_hash == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "No wait queue available.\n");
+ return;
+ }
+
+ key.type = HASH_KEY_STRING;
+ key.str = username;
+
+ ret = hash_lookup(krb5_ctx->wait_queue_hash, &key, &value);
+
+ switch (ret) {
+ case HASH_SUCCESS:
+ if (value.type != HASH_VALUE_PTR) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected hash value type.\n");
+ return;
+ }
+
+ head = talloc_get_type(value.ptr, struct queue_entry);
+
+ if (head->next == NULL) {
+ DEBUG(SSSDBG_TRACE_LIBS,
+ "Wait queue for user [%s] is empty.\n", username);
+ } else {
+ queue_entry = head->next;
+
+ DLIST_REMOVE(head, queue_entry);
+
+ te = tevent_add_timer(queue_entry->be_ctx->ev, krb5_ctx,
+ tevent_timeval_current(), wait_queue_auth,
+ queue_entry);
+ if (te == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "tevent_add_timer failed.\n");
+ } else {
+ return;
+ }
+ }
+
+ ret = hash_delete(krb5_ctx->wait_queue_hash, &key);
+ if (ret != HASH_SUCCESS) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Failed to remove wait queue for user [%s].\n",
+ username);
+ }
+
+ break;
+ case HASH_ERROR_KEY_NOT_FOUND:
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "No wait queue for user [%s] found.\n", username);
+ break;
+ default:
+ DEBUG(SSSDBG_CRIT_FAILURE, "hash_lookup failed.\n");
+ }
+
+ return;
+}
+
+struct krb5_auth_queue_state {
+ struct krb5_ctx *krb5_ctx;
+ struct pam_data *pd;
+
+ int pam_status;
+ int dp_err;
+};
+
+static void krb5_auth_queue_done(struct tevent_req *subreq);
+
+struct tevent_req *krb5_auth_queue_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct be_ctx *be_ctx,
+ struct pam_data *pd,
+ struct krb5_ctx *krb5_ctx)
+{
+ errno_t ret;
+ struct tevent_req *req;
+ struct tevent_req *subreq;
+ struct krb5_auth_queue_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct krb5_auth_queue_state);
+ if (req == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create failed.\n");
+ return NULL;
+ }
+ state->krb5_ctx = krb5_ctx;
+ state->pd = pd;
+
+ ret = add_to_wait_queue(be_ctx, req, pd, krb5_ctx);
+ if (ret == EOK) {
+ DEBUG(SSSDBG_TRACE_LIBS,
+ "Request [%p] successfully added to wait queue "
+ "of user [%s].\n", req, pd->user);
+ ret = EOK;
+ goto immediate;
+ } else if (ret == ENOENT) {
+ DEBUG(SSSDBG_TRACE_LIBS, "Wait queue of user [%s] is empty, "
+ "running request [%p] immediately.\n", pd->user, req);
+ } else {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "Failed to add request to wait queue of user [%s], "
+ "running request [%p] immediately.\n", pd->user, req);
+ }
+
+ subreq = krb5_auth_send(req, ev, be_ctx, pd, krb5_ctx);
+ if (req == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "krb5_auth_send failed.\n");
+ ret = ENOMEM;
+ goto immediate;
+ }
+
+ tevent_req_set_callback(subreq, krb5_auth_queue_done, req);
+
+ ret = EOK;
+
+immediate:
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ }
+ return req;
+}
+
+static void krb5_auth_queue_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = \
+ tevent_req_callback_data(subreq, struct tevent_req);
+ struct krb5_auth_queue_state *state = \
+ tevent_req_data(req, struct krb5_auth_queue_state);
+ errno_t ret;
+
+ ret = krb5_auth_recv(subreq, &state->pam_status, &state->dp_err);
+ talloc_zfree(subreq);
+
+ check_wait_queue(state->krb5_ctx, state->pd->user);
+
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "krb5_auth_recv failed with: %d\n", ret);
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ DEBUG(SSSDBG_TRACE_LIBS, "krb5_auth_queue request [%p] done.\n", req);
+ tevent_req_done(req);
+}
+
+/* This is a violation of the tevent_req style. Ideally, the wait queue would
+ * be rewritten to the tevent_req style in the future, expose per-request recv
+ * and not hide the request underneath. But this function allows us to expose
+ * a tevent_req API for users of this module
+ */
+static void krb5_auth_queue_finish(struct tevent_req *req,
+ errno_t ret,
+ int pam_status,
+ int dp_err)
+{
+ struct krb5_auth_queue_state *state = \
+ tevent_req_data(req, struct krb5_auth_queue_state);
+
+ check_wait_queue(state->krb5_ctx, state->pd->user);
+
+ state->pam_status = pam_status;
+ state->dp_err = dp_err;
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ } else {
+ DEBUG(SSSDBG_TRACE_LIBS, "krb5_auth_queue request [%p] done.\n", req);
+ tevent_req_done(req);
+ }
+}
+
+int krb5_auth_queue_recv(struct tevent_req *req,
+ int *_pam_status,
+ int *_dp_err)
+{
+ struct krb5_auth_queue_state *state = \
+ tevent_req_data(req, struct krb5_auth_queue_state);
+
+ /* Returning values even on failure is not typical, but IPA password migration
+ * relies on receiving PAM_CRED_ERR even if the request fails..
+ */
+ if (_pam_status) {
+ *_pam_status = state->pam_status;
+ }
+
+ if (_dp_err) {
+ *_dp_err = state->dp_err;
+ }
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}