/* SSSD Kerberos 5 Backend Module - Serialize the request of a user Authors: Sumit Bose 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 . */ #include #include #include #include "src/providers/krb5/krb5_auth.h" #define INIT_HASH_SIZE 5 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, INIT_HASH_SIZE, &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; }