From f3f9ce8024d7610439d6c70ddafab1ab025cf8a8 Mon Sep 17 00:00:00 2001 From: Sumit Bose Date: Mon, 15 Nov 2010 13:46:17 +0100 Subject: Add support for automatic Kerberos ticket renewal --- src/providers/krb5/krb5_renew_tgt.c | 380 ++++++++++++++++++++++++++++++++++++ 1 file changed, 380 insertions(+) create mode 100644 src/providers/krb5/krb5_renew_tgt.c (limited to 'src/providers/krb5/krb5_renew_tgt.c') diff --git a/src/providers/krb5/krb5_renew_tgt.c b/src/providers/krb5/krb5_renew_tgt.c new file mode 100644 index 00000000..be029fdc --- /dev/null +++ b/src/providers/krb5/krb5_renew_tgt.c @@ -0,0 +1,380 @@ +/* + SSSD + + Kerberos 5 Backend Module -- Renew a TGT automatically + + 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 "util/util.h" +#include "providers/krb5/krb5_common.h" +#include "providers/krb5/krb5_auth.h" + +#define INITIAL_TGT_TABLE_SIZE 10 + +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; + bool added_to_online_callbacks; +}; + +struct renew_data { + 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; + 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_send(auth_data, ev, auth_data->be_ctx, auth_data->pd, + auth_data->krb5_ctx); + if (req == NULL) { + DEBUG(1, ("krb5_auth_send failed.\n")); + 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; + + ret = krb5_auth_recv(req, &pam_status, &dp_err); + talloc_free(req); + if (ret) { + DEBUG(1, ("krb5_auth request failed.\n")); + } else { + switch (pam_status) { + case PAM_SUCCESS: + DEBUG(4, ("Successfully renewed TGT for user [%s].\n", + auth_data->pd->user)); + break; + case PAM_AUTHINFO_UNAVAIL: + case PAM_AUTHTOK_LOCK_BUSY: + DEBUG(4, ("Cannot renewed TGT for user [%s] while offline, " + "will retry later.\n", + auth_data->pd->user)); + break; + default: + DEBUG(1, ("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(1, ("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; + + ret = hash_entries(renew_tgt_ctx->tgt_table, &count, &entries); + if (ret != HASH_SUCCESS) { + DEBUG(1, ("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(9, ("Checking [%s] for renewal at [%.24s].\n", entries[c].key.str, + ctime(&renew_data->start_renew_at))); + if (renew_data->start_renew_at < now) { + auth_data = talloc_zero(renew_tgt_ctx, struct auth_data); + if (auth_data == NULL) { + DEBUG(1, ("talloc_zero failed.\n")); + } else { + auth_data->pd = 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->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(1, ("talloc_strdup failed.\n")); + te = NULL; + } else { + te = tevent_add_timer(renew_tgt_ctx->ev, + auth_data, tevent_timeval_current(), + renew_tgt, auth_data); + if (te == NULL) { + DEBUG(1, ("tevent_add_timer failed.\n")); + } + } + } + + if (auth_data == NULL || te == NULL) { + DEBUG(1, ("Failed to renew TGT in [%s].\n", entries[c].key.str)); + ret = hash_delete(renew_tgt_ctx->tgt_table, &entries[c].key); + if (ret != HASH_SUCCESS) { + DEBUG(1, ("hash_delete failed.\n")); + } + } + } + } + + talloc_free(entries); + + return EOK; +} + +static void renew_handler(struct renew_tgt_ctx *renew_tgt_ctx); + +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_tgt_ctx->added_to_online_callbacks = false; + 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); + + 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)) { + if (renew_tgt_ctx->added_to_online_callbacks) { + DEBUG(3, ("Renewal task was already added to online callbacks.\n")); + return; + } + DEBUG(7, ("Offline, adding renewal task to online callbacks.\n")); + ret = be_add_online_cb(renew_tgt_ctx->krb5_ctx, renew_tgt_ctx->be_ctx, + renew_tgt_online_callback, renew_tgt_ctx, NULL); + if (ret == EOK) { + renew_tgt_ctx->added_to_online_callbacks = true; + return; + } + + DEBUG(1, ("Failed to add the renewal task to online callbacks, " + "continue normal operation.\n")); + } else { + ret = renew_all_tgts(renew_tgt_ctx); + if (ret != EOK) { + DEBUG(1, ("renew_all_tgts failed. " + "Disabling automatic TGT renewal\n")); + sss_log(SSS_LOG_ERR, "Disabling automatic TGT renewal."); + talloc_zfree(renew_tgt_ctx); + return; + } + } + + DEBUG(7, ("Adding new renew timer.\n")); + + next = tevent_timeval_current_ofs(renew_tgt_ctx->timer_interval, + 0); + 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(1, ("tevent_add_timer failed.\n")); + sss_log(SSS_LOG_ERR, "Disabling automatic TGT renewal."); + talloc_zfree(renew_tgt_ctx); + } + + return; +} + +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(1, ("talloc_zero failed.\n")); + return ENOMEM; + } + + ret = sss_hash_create(krb5_ctx->renew_tgt_ctx, INITIAL_TGT_TABLE_SIZE, + &krb5_ctx->renew_tgt_ctx->tgt_table); + if (ret != EOK) { + DEBUG(1, ("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; + krb5_ctx->renew_tgt_ctx->added_to_online_callbacks = false; + + + next = tevent_timeval_current_ofs(krb5_ctx->renew_tgt_ctx->timer_interval, + 0); + 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(1, ("tevent_add_timer failed.\n")); + ret = ENOMEM; + 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) +{ + char *key_str = NULL; + int ret; + hash_key_t key; + hash_value_t value; + struct renew_data *renew_data = NULL; + + if (krb5_ctx->renew_tgt_ctx == NULL) { + DEBUG(7 ,("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(1, ("Unexpected pam task [%d].\n", pd->cmd)); + return EINVAL; + } + + key.type = HASH_KEY_STRING; + if (ccfile[0] == '/') { + key_str = talloc_asprintf(NULL, "FILE:%s", ccfile); + if (key_str == NULL) { + DEBUG(1, ("talloc_asprintf doneed.\n")); + ret = ENOMEM; + goto done; + } + } else { + key_str = talloc_strdup(NULL, ccfile); + } + key.str = key_str; + + renew_data = talloc_zero(krb5_ctx->renew_tgt_ctx, struct renew_data); + if (renew_data == NULL) { + DEBUG(1, ("talloc_zero doneed.\n")); + ret = ENOMEM; + goto done; + } + + 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(1, ("copy_pam_data doneed.\n")); + goto done; + } + + if (renew_data->pd->newauthtok_type != SSS_AUTHTOK_TYPE_EMPTY) { + talloc_zfree(renew_data->pd->newauthtok); + renew_data->pd->newauthtok_size = 0; + renew_data->pd->newauthtok_type = SSS_AUTHTOK_TYPE_EMPTY; + } + + talloc_zfree(renew_data->pd->authtok); + renew_data->pd->authtok = (uint8_t *) talloc_strdup(renew_data->pd, key.str); + if (renew_data->pd->authtok == NULL) { + DEBUG(1, ("talloc_strdup failed.\n")); + ret = ENOMEM; + goto done; + } + renew_data->pd->authtok_size = strlen((char *) renew_data->pd->authtok) + 1; + renew_data->pd->authtok_type = SSS_AUTHTOK_TYPE_CCFILE; + + 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(1, ("hash_enter failed.\n")); + ret = EFAULT; + goto done; + } + + DEBUG(7, ("Added [%s] for renewal at [%.24s].\n", key_str, + ctime(&renew_data->start_renew_at))); + + ret = EOK; + +done: + talloc_free(key_str); + if (ret != EOK) { + talloc_free(renew_data); + } + return ret; +} -- cgit