/* SSSD Kerberos 5 Backend Module Authors: Sumit Bose 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 . */ #include #include #include #include #include #include #include #include "util/util.h" #include "util/find_uid.h" #include "util/auth_utils.h" #include "db/sysdb.h" #include "util/child_common.h" #include "providers/krb5/krb5_auth.h" #include "providers/krb5/krb5_utils.h" static 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); } static errno_t check_old_ccache(const char *old_ccache, struct krb5child_req *kr, const char *realm, bool *active, bool *valid) { errno_t ret; *active = false; *valid = false; ret = sss_krb5_cc_verify_ccache(old_ccache, kr->uid, kr->gid, realm, kr->upn); switch (ret) { case ERR_NOT_FOUND: case ENOENT: DEBUG(SSSDBG_TRACE_FUNC, ("Saved ccache %s doesn't exist.\n", old_ccache)); return ENOENT; 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", old_ccache)); return ret; } ret = check_if_uid_is_active(kr->uid, active); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, ("check_if_uid_is_active failed.\n")); return ret; } return EOK; } 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(1, ("Missing user or ccache name.\n")); return EINVAL; } if (mod_op != SYSDB_MOD_REP && mod_op != SYSDB_MOD_DEL) { DEBUG(1, ("Unsupported operation [%d].\n", mod_op)); return EINVAL; } DEBUG(9, ("%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(1, ("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(sysdb, domain, name, attrs, mod_op); if (ret != EOK) { DEBUG(6, ("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 struct krb5_ctx *get_krb5_ctx(struct be_req *be_req) { struct be_ctx *be_ctx = be_req_get_be_ctx(be_req); struct pam_data *pd; pd = talloc_get_type(be_req_get_data(be_req), struct pam_data); switch (pd->cmd) { case SSS_PAM_AUTHENTICATE: case SSS_CMD_RENEW: return talloc_get_type(be_ctx->bet_info[BET_AUTH].pvt_bet_data, struct krb5_ctx); break; case SSS_PAM_ACCT_MGMT: return talloc_get_type(be_ctx->bet_info[BET_ACCESS].pvt_bet_data, struct krb5_ctx); break; case SSS_PAM_CHAUTHTOK: case SSS_PAM_CHAUTHTOK_PRELIM: return talloc_get_type(be_ctx->bet_info[BET_CHPASS].pvt_bet_data, struct krb5_ctx); break; default: DEBUG(1, ("Unsupported PAM task.\n")); return NULL; } } 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; } errno_t krb5_setup(TALLOC_CTX *mem_ctx, struct pam_data *pd, struct krb5_ctx *krb5_ctx, struct krb5child_req **krb5_req) { struct krb5child_req *kr = NULL; kr = talloc_zero(mem_ctx, struct krb5child_req); if (kr == NULL) { DEBUG(1, ("talloc failed.\n")); return ENOMEM; } kr->is_offline = false; kr->active_ccache = true; kr->run_as_user = true; talloc_set_destructor((TALLOC_CTX *) kr, krb5_cleanup); kr->pd = pd; kr->krb5_ctx = krb5_ctx; *krb5_req = kr; return EOK; } static void krb5_auth_cache_creds(struct krb5_ctx *krb5_ctx, struct sysdb_ctx *sysdb, 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(0, ("Failed to get password [%d] %s\n", ret, strerror(ret))); *pam_status = PAM_SYSTEM_ERR; *dp_err = DP_ERR_OK; return; } ret = sysdb_cache_auth(sysdb, domain, pd->user, password, cdb, true, NULL, NULL); if (ret != EOK) { DEBUG(1, ("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, pd, uid); if (ret != EOK) { /* This error is not fatal */ DEBUG(1, ("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 be_ctx *be_ctx) { const char *ccname_template; errno_t ret; if (!kr->is_offline) { kr->is_offline = be_is_offline(be_ctx); } /* The ccache file should be (re)created if one of the following conditions * is true: * - it doesn't exist (kr->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 * (!kr->is_offline && !kr->active_ccache && 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 * (kr->is_offline && !kr->active_ccache && !kr->valid_tgt) */ if (kr->ccname == NULL || (kr->is_offline && !kr->active_ccache && !kr->valid_tgt) || (!kr->is_offline && !kr->active_ccache && kr->pd->cmd != SSS_CMD_RENEW)) { DEBUG(9, ("Recreating ccache file.\n")); ccname_template = dp_opt_get_cstring(kr->krb5_ctx->opts, KRB5_CCNAME_TMPL); kr->ccname = expand_ccname_template(kr, kr, ccname_template, true, be_ctx->domain->case_sensitive); if (kr->ccname == NULL) { DEBUG(1, ("expand_ccname_template failed.\n")); return ENOMEM; } ret = sss_krb5_precreate_ccache(kr->ccname, kr->krb5_ctx->illegal_path_re, kr->uid, kr->gid); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, ("ccache creation failed.\n")); return ret; } } return EOK; } static void krb5_auth_store_creds(struct sysdb_ctx *sysdb, struct sss_domain_info *domain, struct pam_data *pd) { const char *password = NULL; 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_AUTHENTICATE: case SSS_PAM_CHAUTHTOK_PRELIM: ret = sss_authtok_get_password(pd->authtok, &password, NULL); break; case SSS_PAM_CHAUTHTOK: ret = sss_authtok_get_password(pd->newauthtok, &password, NULL); break; default: DEBUG(0, ("unsupported PAM command [%d].\n", pd->cmd)); } if (ret != EOK) { DEBUG(0, ("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) { DEBUG(0, ("password not available, offline auth may not work.\n")); /* password caching failures are not fatal errors */ } return; } ret = sysdb_cache_password(sysdb, domain, pd->user, password); if (ret) { DEBUG(2, ("Failed to cache password, offline auth may not work." " (%d)[%s]!?\n", ret, strerror(ret))); /* password caching failures are not fatal errors */ } } /* 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 *ccache_file = NULL; const char *realm; struct tevent_req *req; struct tevent_req *subreq; int authtok_type; int ret; req = tevent_req_create(mem_ctx, &state, struct krb5_auth_state); if (req == NULL) { DEBUG(1, ("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) { /* handle empty password gracefully */ if (authtok_type == SSS_AUTHTOK_TYPE_EMPTY) { DEBUG(SSSDBG_CRIT_FAILURE, ("Illegal zero-length 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; default: DEBUG(4, ("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(9, ("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 *, 7); 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] = NULL; ret = krb5_setup(state, pd, krb5_ctx, &state->kr); if (ret != EOK) { DEBUG(1, ("krb5_setup failed.\n")); goto done; } kr = state->kr; ret = sysdb_get_user_attr(state, state->sysdb, state->domain, state->pd->user, attrs, &res); if (ret) { DEBUG(5, ("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(1, ("Missing Kerberos realm.\n")); ret = ENOENT; goto done; } switch (res->count) { case 0: DEBUG(5, ("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, pd->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 = ldb_msg_find_attr_as_string(res->msgs[0], SYSDB_HOMEDIR, NULL); if (kr->homedir == NULL) { DEBUG(4, ("Home directory for user [%s] not known.\n", pd->user)); } kr->uid = ldb_msg_find_attr_as_uint64(res->msgs[0], SYSDB_UIDNUM, 0); if (kr->uid == 0) { DEBUG(4, ("UID for user [%s] not known.\n", pd->user)); ret = ENOENT; goto done; } kr->gid = ldb_msg_find_attr_as_uint64(res->msgs[0], SYSDB_GIDNUM, 0); if (kr->gid == 0) { DEBUG(4, ("GID for user [%s] not known.\n", pd->user)); ret = ENOENT; goto done; } ccache_file = ldb_msg_find_attr_as_string(res->msgs[0], SYSDB_CCACHE_FILE, NULL); if (ccache_file != NULL) { ret = check_old_ccache(ccache_file, kr, realm, &kr->active_ccache, &kr->valid_tgt); if (ret == ENOENT) { DEBUG(SSSDBG_FUNC_DATA, ("Ignoring ccache attribute [%s], because it doesn't" "exist.\n", ccache_file)); ccache_file = NULL; } else if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, ("check_if_ccache_file_is_used failed.\n")); ccache_file = NULL; } } else { kr->active_ccache = false; kr->valid_tgt = false; DEBUG(4, ("No ccache file for user [%s] found.\n", pd->user)); } DEBUG(9, ("Ccache_file is [%s] and is %s active and TGT is %s valid.\n", ccache_file ? ccache_file : "not set", kr->active_ccache ? "" : "not", kr->valid_tgt ? "" : "not")); if (ccache_file != NULL) { kr->ccname = ccache_file; kr->old_ccname = talloc_strdup(kr, ccache_file); if (kr->old_ccname == NULL) { DEBUG(1, ("talloc_strdup failed.\n")); ret = ENOMEM; goto done; } } else { kr->ccname = NULL; kr->old_ccname = NULL; } break; default: DEBUG(1, ("User search for (%s) returned > 1 results!\n", pd->user)); ret = EINVAL; goto done; break; } 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; char *msg; int ret; if (!state->search_kpasswd) { ret = be_resolve_server_recv(subreq, &kr->srv); } else { ret = be_resolve_server_recv(subreq, &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_offline(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; } } } ret = krb5_auth_prepare_ccache_name(kr, state->be_ctx); if (ret) { goto done; } if (kr->is_offline) { DEBUG(9, ("Preparing for offline operation.\n")); if (kr->valid_tgt || kr->active_ccache) { DEBUG(9, ("Valid TGT available or " "ccache file is already in use.\n")); kr->ccname = kr->old_ccname; msg = talloc_asprintf(kr->pd, "%s=%s", CCACHE_ENV_NAME, kr->ccname); if (msg == NULL) { DEBUG(1, ("talloc_asprintf failed.\n")); } else { ret = pam_add_response(kr->pd, SSS_PAM_ENV_ITEM, strlen(msg) + 1, (uint8_t *) msg); if (ret != EOK) { DEBUG(1, ("pam_add_response failed.\n")); } } if (dp_opt_get_bool(kr->krb5_ctx->opts, KRB5_STORE_PASSWORD_IF_OFFLINE)) { krb5_auth_cache_creds(state->kr->krb5_ctx, state->domain->sysdb, state->domain, state->be_ctx->cdb, kr->pd, kr->uid, &state->pam_status, &state->dp_err); } else { state->pam_status = PAM_AUTHINFO_UNAVAIL; state->dp_err = DP_ERR_OFFLINE; } ret = EOK; goto done; } } /* We need to keep the root privileges to read the keytab file if * validation or FAST is enabled, otherwise we can drop them and run * krb5_child with user privileges. * If we are offline we want to create an empty ccache file. In this * case we can drop the privileges, too. */ if ((dp_opt_get_bool(kr->krb5_ctx->opts, KRB5_VALIDATE) || kr->krb5_ctx->use_fast) && (!kr->is_offline)) { kr->run_as_user = false; } else { kr->run_as_user = true; } subreq = handle_child_send(state, state->ev, kr); if (subreq == NULL) { DEBUG(1, ("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; ret = handle_child_recv(subreq, pd, &buf, &len); talloc_zfree(subreq); if (ret == ETIMEDOUT) { DEBUG(1, ("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; } default: DEBUG(1, ("Unexpected PAM task\n")); 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(1, ("Failed resolved request.\n")); ret = ENOMEM; goto done; } tevent_req_set_callback(subreq, krb5_auth_resolve_done, req); return; } else if (ret != EOK) { DEBUG(1, ("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_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); /* 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 || 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(1, ("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(1, ("Resolver request failed.\n")); ret = ENOMEM; goto done; } tevent_req_set_callback(subreq, krb5_auth_resolve_done, req); return; } break; 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 = safe_remove_old_ccache_file(kr->old_ccname, NULL, kr->uid, kr->gid); if (ret != EOK) { DEBUG(1, ("Failed to remove old ccache file [%s], " "please remove it manually.\n", kr->old_ccname)); } ret = krb5_delete_ccname(state, state->sysdb, state->domain, pd->user, kr->old_ccname); if (ret != EOK) { DEBUG(1, ("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_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; default: 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); } /* 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(1, ("Missing ccache name in child response.\n")); ret = EINVAL; goto done; } ret = sss_krb5_check_ccache_princ(kr->uid, kr->gid, kr->ccname, kr->upn); if (ret) { DEBUG(SSSDBG_CRIT_FAILURE, ("No ccache for %s in %s?\n", kr->upn, kr->ccname)); goto done; } if (kr->old_ccname) { ret = safe_remove_old_ccache_file(kr->old_ccname, kr->ccname, kr->uid, kr->gid); if (ret != EOK) { DEBUG(SSSDBG_MINOR_FAILURE, ("Failed to remove old ccache file [%s], " "please remove it manually.\n", kr->old_ccname)); } } ret = krb5_save_ccname(state, state->sysdb, state->domain, pd->user, kr->ccname); if (ret) { DEBUG(1, ("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(7, ("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(1, ("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)) { krb5_auth_cache_creds(state->kr->krb5_ctx, state->domain->sysdb, state->domain, state->be_ctx->cdb, state->pd, state->kr->uid, &state->pam_status, &state->dp_err); } else { DEBUG(4, ("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) { krb5_auth_store_creds(state->sysdb, state->domain, pd); } 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; } void krb5_pam_handler_auth_done(struct tevent_req *req); static void krb5_pam_handler_access_done(struct tevent_req *req); void krb5_pam_handler(struct be_req *be_req) { struct be_ctx *be_ctx = be_req_get_be_ctx(be_req); struct tevent_req *req; struct pam_data *pd; struct krb5_ctx *krb5_ctx; int dp_err = DP_ERR_FATAL; int ret; pd = talloc_get_type(be_req_get_data(be_req), struct pam_data); pd->pam_status = PAM_SYSTEM_ERR; krb5_ctx = get_krb5_ctx(be_req); if (krb5_ctx == NULL) { DEBUG(1, ("Kerberos context not available.\n")); goto done; } switch (pd->cmd) { case SSS_PAM_AUTHENTICATE: case SSS_CMD_RENEW: case SSS_PAM_CHAUTHTOK_PRELIM: case SSS_PAM_CHAUTHTOK: ret = add_to_wait_queue(be_req, pd, krb5_ctx); if (ret == EOK) { DEBUG(7, ("Request successfully added to wait queue " "of user [%s].\n", pd->user)); return; } else if (ret == ENOENT) { DEBUG(7, ("Wait queue of user [%s] is empty, " "running request immediately.\n", pd->user)); } else { DEBUG(7, ("Failed to add request to wait queue of user [%s], " "running request immediately.\n", pd->user)); } req = krb5_auth_send(be_req, be_ctx->ev, be_ctx, pd, krb5_ctx); if (req == NULL) { DEBUG(1, ("krb5_auth_send failed.\n")); goto done; } tevent_req_set_callback(req, krb5_pam_handler_auth_done, be_req); break; case SSS_PAM_ACCT_MGMT: req = krb5_access_send(be_req, be_ctx->ev, be_ctx, pd, krb5_ctx); if (req == NULL) { DEBUG(1, ("krb5_access_send failed.\n")); goto done; } tevent_req_set_callback(req, krb5_pam_handler_access_done, be_req); break; case SSS_PAM_SETCRED: case SSS_PAM_OPEN_SESSION: case SSS_PAM_CLOSE_SESSION: pd->pam_status = PAM_SUCCESS; dp_err = DP_ERR_OK; goto done; break; default: DEBUG(4, ("krb5 does not handles pam task %d.\n", pd->cmd)); pd->pam_status = PAM_MODULE_UNKNOWN; dp_err = DP_ERR_OK; goto done; } return; done: be_req_terminate(be_req, dp_err, pd->pam_status, NULL); } void krb5_pam_handler_auth_done(struct tevent_req *req) { int ret; struct be_req *be_req = tevent_req_callback_data(req, struct be_req); int pam_status; int dp_err; struct pam_data *pd; struct krb5_ctx *krb5_ctx; pd = talloc_get_type(be_req_get_data(be_req), struct pam_data); ret = krb5_auth_recv(req, &pam_status, &dp_err); talloc_zfree(req); if (ret) { pd->pam_status = PAM_SYSTEM_ERR; dp_err = DP_ERR_OK; } else { pd->pam_status = pam_status; } krb5_ctx = get_krb5_ctx(be_req); if (krb5_ctx != NULL) { check_wait_queue(krb5_ctx, pd->user); } else { DEBUG(1, ("Kerberos context not available.\n")); } be_req_terminate(be_req, dp_err, pd->pam_status, NULL); } static void krb5_pam_handler_access_done(struct tevent_req *req) { int ret; struct be_req *be_req = tevent_req_callback_data(req, struct be_req); bool access_allowed; struct pam_data *pd; int dp_err = DP_ERR_OK; pd = talloc_get_type(be_req_get_data(be_req), struct pam_data); pd->pam_status = PAM_SYSTEM_ERR; ret = krb5_access_recv(req, &access_allowed); talloc_zfree(req); if (ret != EOK) { DEBUG(1, ("krb5_access request failed [%d][%s]\n", ret, strerror(ret))); goto done; } DEBUG(7, ("Access %s for user [%s].\n", access_allowed ? "allowed" : "denied", pd->user)); pd->pam_status = access_allowed ? PAM_SUCCESS : PAM_PERM_DENIED; dp_err = DP_ERR_OK; done: be_req_terminate(be_req, dp_err, pd->pam_status, NULL); }