/* 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 "db/sysdb.h" #include "providers/child_common.h" #include "providers/krb5/krb5_auth.h" #include "providers/krb5/krb5_utils.h" #define TIME_T_MAX LONG_MAX #define int64_to_time_t(val) ((time_t)((val) < TIME_T_MAX ? val : TIME_T_MAX)) static errno_t safe_remove_old_ccache_file(const char *old_ccache_file, const char *new_ccache_file) { int ret; size_t old_offset = 0; size_t new_offset = 0; if (new_ccache_file == NULL) { DEBUG(1, ("Missing new ccache file, " "old ccache file is not deleted.\n")); return EINVAL; } if (old_ccache_file != NULL) { if (strncmp(old_ccache_file, "FILE:", 5) == 0) { old_offset = 5; } if (strncmp(new_ccache_file, "FILE:", 5) == 0) { new_offset = 5; } if (strcmp(old_ccache_file + old_offset, new_ccache_file + new_offset) == 0) { DEBUG(7, ("New and old ccache file are the same, " "no one will be deleted.\n")); return EOK; } if (old_ccache_file[old_offset] != '/') { DEBUG(1, ("Ccache file name [%s] is not an absolute path.\n", old_ccache_file + old_offset)); return EINVAL; } ret = unlink(old_ccache_file + old_offset); if (ret == -1 && errno != ENOENT) { ret = errno; DEBUG(1, ("unlink [%s] failed [%d][%s].\n", old_ccache_file, ret, strerror(ret))); return ret; } } return EOK; } static errno_t check_if_ccache_file_is_used(uid_t uid, const char *ccname, bool *result) { int ret; size_t offset = 0; struct stat stat_buf; const char *filename; bool active; *result = false; if (ccname == NULL || *ccname == '\0') { return EINVAL; } if (strncmp(ccname, "FILE:", 5) == 0) { offset = 5; } filename = ccname + offset; if (filename[0] != '/') { DEBUG(1, ("Only absolute path names are allowed.\n")); return EINVAL; } ret = lstat(filename, &stat_buf); if (ret == -1 && errno != ENOENT) { DEBUG(1, ("stat failed [%d][%s].\n", errno, strerror(errno))); return errno; } else if (ret == EOK) { if (stat_buf.st_uid != uid) { DEBUG(1, ("Cache file [%s] exists, but is owned by [%d] instead of " "[%d].\n", filename, stat_buf.st_uid, uid)); return EINVAL; } if (!S_ISREG(stat_buf.st_mode)) { DEBUG(1, ("Cache file [%s] exists, but is not a regular file.\n", filename)); return EINVAL; } } ret = check_if_uid_is_active(uid, &active); if (ret != EOK) { DEBUG(1, ("check_if_uid_is_active failed.\n")); return ret; } if (!active) { DEBUG(5, ("User [%d] is not active\n", uid)); } else { DEBUG(9, ("User [%d] is still active, reusing ccache file [%s].\n", uid, filename)); *result = true; } return EOK; } static int krb5_save_ccname(TALLOC_CTX *mem_ctx, struct sysdb_ctx *sysdb, struct sss_domain_info *domain, const char *name, const char *ccname) { TALLOC_CTX *tmpctx; struct sysdb_attrs *attrs; int ret; if (name == NULL || ccname == NULL) { DEBUG(1, ("Missing user or ccache name.\n")); return EINVAL; } DEBUG(9, ("Save ccname [%s] for user [%s].\n", ccname, name)); tmpctx = talloc_new(mem_ctx); if (!tmpctx) { return ENOMEM; } attrs = sysdb_new_attrs(mem_ctx); 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(6, ("Error %d starting transaction (%s)\n", ret, strerror(ret))); goto done; } ret = sysdb_set_user_attr(tmpctx, sysdb, domain, name, attrs, SYSDB_MOD_REP); if (ret != EOK) { DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret))); sysdb_transaction_cancel(sysdb); goto done; } ret = sysdb_transaction_commit(sysdb); if (ret != EOK) { DEBUG(1, ("Failed to commit transaction!\n")); } done: talloc_zfree(tmpctx); return ret; } static struct krb5_ctx *get_krb5_ctx(struct be_req *be_req) { struct pam_data *pd; pd = talloc_get_type(be_req->req_data, struct pam_data); switch (pd->cmd) { case SSS_PAM_AUTHENTICATE: case SSS_CMD_RENEW: return talloc_get_type(be_req->be_ctx->bet_info[BET_AUTH].pvt_bet_data, struct krb5_ctx); break; case SSS_PAM_ACCT_MGMT: return talloc_get_type(be_req->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_req->be_ctx->bet_info[BET_CHPASS].pvt_bet_data, struct krb5_ctx); break; default: DEBUG(1, ("Unsupported PAM task.\n")); return NULL; } } static void krb_reply(struct be_req *req, int dp_err, int result); 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_present = 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_resolve_kdc_done(struct tevent_req *subreq); static void krb5_resolve_kpasswd_done(struct tevent_req *subreq); static void krb5_find_ccache_step(struct tevent_req *req); static void krb5_save_ccname_done(struct tevent_req *req); static void krb5_child_done(struct tevent_req *req); static void krb5_pam_handler_cache_auth_step(struct tevent_req *req); struct krb5_auth_state { struct tevent_context *ev; struct be_ctx *be_ctx; struct pam_data *pd; struct krb5_ctx *krb5_ctx; struct krb5child_req *kr; int pam_status; int dp_err; }; 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 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; krb5_error_code kerr; struct tevent_req *req; struct tevent_req *subreq; 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; switch (pd->cmd) { case SSS_PAM_AUTHENTICATE: case SSS_CMD_RENEW: case SSS_PAM_CHAUTHTOK: break; case SSS_PAM_CHAUTHTOK_PRELIM: if (pd->priv == 1 && pd->authtok_size == 0) { DEBUG(4, ("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; 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 *, 6); 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] = 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, be_ctx->sysdb, be_ctx->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: kr->upn = ldb_msg_find_attr_as_string(res->msgs[0], SYSDB_UPN, NULL); if (kr->upn == NULL) { ret = krb5_get_simple_upn(state, krb5_ctx, pd->user, &kr->upn); if (ret != EOK) { DEBUG(1, ("krb5_get_simple_upn 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_if_ccache_file_is_used(kr->uid, ccache_file, &kr->active_ccache_present); if (ret != EOK) { DEBUG(1, ("check_if_ccache_file_is_used failed.\n")); goto done; } kerr = check_for_valid_tgt(ccache_file, realm, kr->upn, &kr->valid_tgt_present); if (kerr != 0) { DEBUG(1, ("check_for_valid_tgt failed.\n")); ret = kerr; goto done; } } else { kr->active_ccache_present = false; kr->valid_tgt_present = 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_present ? "" : "not", kr->valid_tgt_present ? "" : "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; subreq = be_resolve_server_send(state, state->ev, state->be_ctx, krb5_ctx->service->name); if (subreq == NULL) { DEBUG(1, ("be_resolve_server_send failed.\n")); ret = ENOMEM; goto done; } tevent_req_set_callback(subreq, krb5_resolve_kdc_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_resolve_kdc_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; ret = be_resolve_server_recv(subreq, &kr->srv); talloc_zfree(subreq); if (ret) { /* 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. */ be_mark_offline(state->be_ctx); kr->is_offline = true; } else { if (kr->krb5_ctx->kpasswd_service != NULL) { subreq = be_resolve_server_send(state, state->ev, state->be_ctx, kr->krb5_ctx->kpasswd_service->name); if (subreq == NULL) { DEBUG(1, ("be_resolve_server_send failed.\n")); ret = ENOMEM; goto failed; } tevent_req_set_callback(subreq, krb5_resolve_kpasswd_done, req); return; } } krb5_find_ccache_step(req); return; failed: tevent_req_error(req, ret); } static void krb5_resolve_kpasswd_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); int ret; ret = be_resolve_server_recv(subreq, &state->kr->kpasswd_srv); talloc_zfree(subreq); if (ret) { /* all kpasswd servers have been tried and none was found good, but the * kdc seems ok. Password changes are not possible but * authentication. 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; tevent_req_done(req); return; } krb5_find_ccache_step(req); } static void krb5_find_ccache_step(struct tevent_req *req) { struct krb5_auth_state *state = tevent_req_data(req, struct krb5_auth_state); int ret; struct krb5child_req *kr = state->kr; struct pam_data *pd = kr->pd; char *msg; bool private_path = false; struct tevent_req *subreq = NULL; if (!kr->is_offline) { kr->is_offline = be_is_offline(state->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_present && * 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_present && !kr->valid_tgt_present) */ if (kr->ccname == NULL || (kr->is_offline && !kr->active_ccache_present && !kr->valid_tgt_present) || (!kr->is_offline && !kr->active_ccache_present && pd->cmd != SSS_CMD_RENEW)) { DEBUG(9, ("Recreating ccache file.\n")); kr->ccname = expand_ccname_template(kr, kr, dp_opt_get_cstring(kr->krb5_ctx->opts, KRB5_CCNAME_TMPL), true, &private_path); if (kr->ccname == NULL) { DEBUG(1, ("expand_ccname_template failed.\n")); ret = ENOMEM; goto done; } ret = create_ccache_dir(kr, kr->ccname, kr->krb5_ctx->illegal_path_re, kr->uid, kr->gid, private_path); if (ret != EOK) { DEBUG(1, ("create_ccache_dir failed.\n")); goto done; } } if (kr->is_offline) { DEBUG(9, ("Preparing for offline operation.\n")); if (kr->valid_tgt_present || kr->active_ccache_present) { DEBUG(9, ("Valid TGT available or " "ccache file is already in use.\n")); kr->ccname = kr->old_ccname; msg = talloc_asprintf(pd, "%s=%s", CCACHE_ENV_NAME, kr->ccname); if (msg == NULL) { DEBUG(1, ("talloc_asprintf failed.\n")); } else { ret = pam_add_response(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_pam_handler_cache_auth_step(req); return; } 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_child_done, req); return; done: if (ret == EOK) { tevent_req_done(req); } else { tevent_req_error(req, ret); } } static struct tevent_req *krb5_next_server(struct tevent_req *req); static struct tevent_req *krb5_next_kdc(struct tevent_req *req); static struct tevent_req *krb5_next_kpasswd(struct tevent_req *req); static void krb5_child_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; ssize_t pref_len; size_t p; int32_t msg_status; int32_t msg_type; int32_t msg_len; int64_t time_data; struct tgt_times tgtt; memset(&tgtt, 0, sizeof(tgtt)); ret = handle_child_recv(subreq, pd, &buf, &len); talloc_zfree(subreq); if (ret != EOK) { DEBUG(1, ("child failed (%d [%s])\n", ret, strerror(ret))); if (ret == ETIMEDOUT) { if (krb5_next_server(req) == NULL) { tevent_req_error(req, ENOMEM); } } else { tevent_req_error(req, ret); } return; } /* 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 */ if ((size_t) len < sizeof(int32_t)) { DEBUG(1, ("message too short.\n")); ret = EINVAL; goto done; } p=0; SAFEALIGN_COPY_INT32(&msg_status, buf+p, &p); while (p < len) { SAFEALIGN_COPY_INT32(&msg_type, buf+p, &p); SAFEALIGN_COPY_INT32(&msg_len, buf+p, &p); DEBUG(9, ("child response [%d][%d][%d].\n", msg_status, msg_type, msg_len)); if ((p + msg_len) > len) { DEBUG(1, ("message format error [%d] > [%d].\n", p+msg_len, len)); ret = EINVAL; goto done; } /* 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_len > pref_len && strncmp((const char *) &buf[p], CCACHE_ENV_NAME"=", pref_len) == 0) { kr->ccname = talloc_strndup(kr, (char *) &buf[p+pref_len], msg_len-pref_len); if (kr->ccname == NULL) { DEBUG(1, ("talloc_strndup failed.\n")); ret = ENOMEM; goto done; } } 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(7, ("TGT times are [%d][%d][%d][%d].\n", tgtt.authtime, tgtt.starttime, tgtt.endtime, tgtt.renew_till)); } ret = pam_add_response(pd, msg_type, msg_len, &buf[p]); if (ret != EOK) { /* This is not a fatal error */ DEBUG(1, ("pam_add_response failed.\n")); } p += msg_len; if ((p < len) && (p + 2*sizeof(int32_t) >= len)) { DEBUG(1, ("The remainder of the message is too short.\n")); ret = EINVAL; goto done; } } /* If the child request failed, but did not return an offline error code, * return with the status */ if (msg_status != PAM_SUCCESS && msg_status != PAM_AUTHINFO_UNAVAIL && msg_status != PAM_AUTHTOK_LOCK_BUSY) { state->pam_status = msg_status; state->dp_err = DP_ERR_OK; ret = EOK; goto done; } else { state->pam_status = msg_status; } /* If the child request was successful and we run the first pass of the * change password request just return success. */ if (msg_status == PAM_SUCCESS && pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM) { state->pam_status = PAM_SUCCESS; state->dp_err = DP_ERR_OK; ret = EOK; goto done; } /* if using a dedicated kpasswd server.. */ if (kr->kpasswd_srv != NULL) { /* ..which is unreachable by now.. */ if (msg_status == PAM_AUTHTOK_LOCK_BUSY) { fo_set_port_status(kr->kpasswd_srv, PORT_NOT_WORKING); /* ..try to resolve next kpasswd server */ if (krb5_next_kpasswd(req) == NULL) { tevent_req_error(req, ENOMEM); } return; } else { fo_set_port_status(kr->kpasswd_srv, PORT_WORKING); } } /* if the KDC for auth (PAM_AUTHINFO_UNAVAIL) or * chpass (PAM_AUTHTOK_LOCK_BUSY) was not available while using KDC * also for chpass operation... */ if (msg_status == PAM_AUTHINFO_UNAVAIL || (kr->kpasswd_srv == NULL && msg_status == PAM_AUTHTOK_LOCK_BUSY)) { if (kr->srv != NULL) { fo_set_port_status(kr->srv, PORT_NOT_WORKING); /* ..try to resolve next KDC */ if (krb5_next_kdc(req) == NULL) { tevent_req_error(req, ENOMEM); } return; } } else if (kr->srv != NULL) { fo_set_port_status(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; } if (kr->old_ccname != NULL) { ret = safe_remove_old_ccache_file(kr->old_ccname, kr->ccname); if (ret != EOK) { DEBUG(1, ("Failed to remove old ccache file [%s], please remove it manually.\n")); } } struct sysdb_attrs *attrs; attrs = sysdb_new_attrs(state); ret = sysdb_attrs_add_string(attrs, SYSDB_CCACHE_FILE, kr->ccname); if (ret != EOK) { DEBUG(1, ("sysdb_attrs_add_string failed.\n")); goto done; } ret = krb5_save_ccname(state, state->be_ctx->sysdb, state->be_ctx->domain, pd->user, kr->ccname); if (ret) { DEBUG(1, ("krb5_save_ccname_send failed.\n")); goto done; } if (msg_status == PAM_SUCCESS && dp_opt_get_int(kr->krb5_ctx->opts, KRB5_RENEW_INTERVAL) > 0 && (pd->cmd == SSS_PAM_AUTHENTICATE || pd->cmd == SSS_CMD_RENEW || pd->cmd == SSS_PAM_CHAUTHTOK) && tgtt.renew_till > 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, &tgtt, pd, kr->upn); if (ret != EOK) { DEBUG(1, ("add_tgt_to_renew_table failed, " "automatic renewal not possible.\n")); } } krb5_save_ccname_done(req); return; done: if (ret == EOK) { tevent_req_done(req); } else { tevent_req_error(req, ret); } } static struct tevent_req *krb5_next_server(struct tevent_req *req) { struct krb5_auth_state *state = tevent_req_data(req, struct krb5_auth_state); struct pam_data *pd = state->pd; struct tevent_req *next_req = NULL; switch (pd->cmd) { case SSS_PAM_AUTHENTICATE: case SSS_CMD_RENEW: fo_set_port_status(state->kr->srv, PORT_NOT_WORKING); next_req = krb5_next_kdc(req); break; case SSS_PAM_CHAUTHTOK: case SSS_PAM_CHAUTHTOK_PRELIM: if (state->kr->kpasswd_srv) { fo_set_port_status(state->kr->kpasswd_srv, PORT_NOT_WORKING); next_req = krb5_next_kpasswd(req); break; } else { fo_set_port_status(state->kr->srv, PORT_NOT_WORKING); next_req = krb5_next_kdc(req); break; } default: DEBUG(1, ("Unexpected PAM task\n")); } return next_req; } static struct tevent_req *krb5_next_kdc(struct tevent_req *req) { struct tevent_req *next_req; struct krb5_auth_state *state = tevent_req_data(req, struct krb5_auth_state); next_req = be_resolve_server_send(state, state->ev, state->be_ctx, state->krb5_ctx->service->name); if (next_req == NULL) { DEBUG(1, ("be_resolve_server_send failed.\n")); return NULL; } tevent_req_set_callback(next_req, krb5_resolve_kdc_done, req); return next_req; } static struct tevent_req *krb5_next_kpasswd(struct tevent_req *req) { struct tevent_req *next_req; struct krb5_auth_state *state = tevent_req_data(req, struct krb5_auth_state); next_req = be_resolve_server_send(state, state->ev, state->be_ctx, state->krb5_ctx->kpasswd_service->name); if (next_req == NULL) { DEBUG(1, ("be_resolve_server_send failed.\n")); return NULL; } tevent_req_set_callback(next_req, krb5_resolve_kpasswd_done, req); return next_req; } static void krb5_save_ccname_done(struct tevent_req *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; char *password = NULL; if (kr->is_offline) { if (dp_opt_get_bool(kr->krb5_ctx->opts,KRB5_STORE_PASSWORD_IF_OFFLINE)) { krb5_pam_handler_cache_auth_step(req); return; } 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) { /* password caching failures are not fatal errors */ state->pam_status = PAM_SUCCESS; state->dp_err = DP_ERR_OK; 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: password = talloc_size(state, pd->authtok_size + 1); if (password != NULL) { memcpy(password, pd->authtok, pd->authtok_size); password[pd->authtok_size] = '\0'; } break; case SSS_PAM_CHAUTHTOK: password = talloc_size(state, pd->newauthtok_size + 1); if (password != NULL) { memcpy(password, pd->newauthtok, pd->newauthtok_size); password[pd->newauthtok_size] = '\0'; } break; default: DEBUG(0, ("unsupported PAM command [%d].\n", pd->cmd)); } 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 */ } ret = EOK; goto done; } talloc_set_destructor((TALLOC_CTX *)password, password_destructor); ret = sysdb_cache_password(state, state->be_ctx->sysdb, state->be_ctx->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 */ } } 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); } } static void krb5_pam_handler_cache_auth_step(struct tevent_req *req) { struct krb5_auth_state *state = tevent_req_data(req, struct krb5_auth_state); struct pam_data *pd = state->pd; struct krb5_ctx *krb5_ctx = state->kr->krb5_ctx; int ret; ret = sysdb_cache_auth(state, state->be_ctx->sysdb, state->be_ctx->domain, pd->user, pd->authtok, pd->authtok_size, state->be_ctx->cdb, true, NULL, NULL); if (ret != EOK) { DEBUG(1, ("Offline authentication failed\n")); state->pam_status = PAM_SYSTEM_ERR; state->dp_err = DP_ERR_OK; } else { ret = add_user_to_delayed_online_authentication(krb5_ctx, pd, state->kr->uid); if (ret != EOK) { /* This error is not fatal */ DEBUG(1, ("add_user_to_delayed_online_authentication failed.\n")); } state->pam_status = PAM_AUTHINFO_UNAVAIL; state->dp_err = DP_ERR_OFFLINE; } tevent_req_done(req); } static void krb_reply(struct be_req *req, int dp_err, int result) { req->fn(req, dp_err, result, NULL); } void krb5_auth_done(struct tevent_req *req); static void krb5_access_done(struct tevent_req *req); void krb5_pam_handler(struct be_req *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->req_data, 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_req->be_ctx->ev, be_req->be_ctx, pd, krb5_ctx); if (req == NULL) { DEBUG(1, ("krb5_auth_send failed.\n")); goto done; } tevent_req_set_callback(req, krb5_auth_done, be_req); break; case SSS_PAM_ACCT_MGMT: req = krb5_access_send(be_req, be_req->be_ctx->ev, be_req->be_ctx, pd, krb5_ctx); if (req == NULL) { DEBUG(1, ("krb5_access_send failed.\n")); goto done; } tevent_req_set_callback(req, krb5_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: krb_reply(be_req, dp_err, pd->pam_status); } void krb5_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->req_data, 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")); } krb_reply(be_req, dp_err, pd->pam_status); } static void krb5_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->req_data, 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: krb_reply(be_req, dp_err, pd->pam_status); }