diff options
author | Sumit Bose <sbose@redhat.com> | 2010-11-15 13:46:17 +0100 |
---|---|---|
committer | Stephen Gallagher <sgallagh@redhat.com> | 2010-12-03 10:41:28 -0500 |
commit | f3f9ce8024d7610439d6c70ddafab1ab025cf8a8 (patch) | |
tree | 415d65170f362c2df65410084cadbcc016b4673d /src/providers/krb5 | |
parent | 1709edfb690bb4ffa4b96c64d08853f47390eda3 (diff) | |
download | sssd-f3f9ce8024d7610439d6c70ddafab1ab025cf8a8.tar.gz sssd-f3f9ce8024d7610439d6c70ddafab1ab025cf8a8.tar.bz2 sssd-f3f9ce8024d7610439d6c70ddafab1ab025cf8a8.zip |
Add support for automatic Kerberos ticket renewal
Diffstat (limited to 'src/providers/krb5')
-rw-r--r-- | src/providers/krb5/krb5_auth.c | 18 | ||||
-rw-r--r-- | src/providers/krb5/krb5_auth.h | 5 | ||||
-rw-r--r-- | src/providers/krb5/krb5_child_handler.c | 2 | ||||
-rw-r--r-- | src/providers/krb5/krb5_common.c | 3 | ||||
-rw-r--r-- | src/providers/krb5/krb5_common.h | 3 | ||||
-rw-r--r-- | src/providers/krb5/krb5_init.c | 10 | ||||
-rw-r--r-- | src/providers/krb5/krb5_renew_tgt.c | 380 |
7 files changed, 420 insertions, 1 deletions
diff --git a/src/providers/krb5/krb5_auth.c b/src/providers/krb5/krb5_auth.c index 974e7684..515c181b 100644 --- a/src/providers/krb5/krb5_auth.c +++ b/src/providers/krb5/krb5_auth.c @@ -210,6 +210,7 @@ static struct krb5_ctx *get_krb5_ctx(struct be_req *be_req) 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; @@ -327,6 +328,7 @@ struct tevent_req *krb5_auth_send(TALLOC_CTX *mem_ctx, switch (pd->cmd) { case SSS_PAM_AUTHENTICATE: + case SSS_CMD_RENEW: case SSS_PAM_CHAUTHTOK: break; case SSS_PAM_CHAUTHTOK_PRELIM: @@ -786,6 +788,19 @@ static void krb5_child_done(struct tevent_req *subreq) } } + 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); + if (ret != EOK) { + DEBUG(1, ("add_tgt_to_renew_table failed, " + "automatic renewal not possible.\n")); + } + } + /* 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 && @@ -891,6 +906,7 @@ static struct tevent_req *krb5_next_server(struct tevent_req *req) 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; @@ -975,6 +991,7 @@ static void krb5_save_ccname_done(struct tevent_req *req) switch(pd->cmd) { case SSS_PAM_AUTHENTICATE: + case SSS_CMD_RENEW: case SSS_PAM_CHAUTHTOK_PRELIM: password = talloc_size(state, pd->authtok_size + 1); if (password != NULL) { @@ -1077,6 +1094,7 @@ void krb5_pam_handler(struct be_req *be_req) switch (pd->cmd) { case SSS_PAM_AUTHENTICATE: + case SSS_CMD_RENEW: case SSS_PAM_CHAUTHTOK_PRELIM: case SSS_PAM_CHAUTHTOK: req = krb5_auth_send(be_req, be_req->be_ctx->ev, be_req->be_ctx, pd, diff --git a/src/providers/krb5/krb5_auth.h b/src/providers/krb5/krb5_auth.h index 130e85db..f40cbcb0 100644 --- a/src/providers/krb5/krb5_auth.h +++ b/src/providers/krb5/krb5_auth.h @@ -80,6 +80,11 @@ 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); + /* krb5_access.c */ struct tevent_req *krb5_access_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, diff --git a/src/providers/krb5/krb5_child_handler.c b/src/providers/krb5/krb5_child_handler.c index 9da8a37b..bafa0bbf 100644 --- a/src/providers/krb5/krb5_child_handler.c +++ b/src/providers/krb5/krb5_child_handler.c @@ -104,6 +104,7 @@ static errno_t create_send_buffer(struct krb5child_req *kr, buf->size = 6*sizeof(uint32_t) + strlen(kr->upn); if (kr->pd->cmd == SSS_PAM_AUTHENTICATE || + 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) + @@ -137,6 +138,7 @@ static errno_t create_send_buffer(struct krb5child_req *kr, safealign_memcpy(&buf->data[rp], kr->upn, strlen(kr->upn), &rp); if (kr->pd->cmd == SSS_PAM_AUTHENTICATE || + 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); diff --git a/src/providers/krb5/krb5_common.c b/src/providers/krb5/krb5_common.c index 7ee4d09f..cdc8437f 100644 --- a/src/providers/krb5/krb5_common.c +++ b/src/providers/krb5/krb5_common.c @@ -42,7 +42,8 @@ struct dp_option default_krb5_opts[] = { { "krb5_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_lifetime", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_renew_interval", DP_OPT_NUMBER, NULL_NUMBER, NULL_NUMBER } }; errno_t check_and_export_options(struct dp_option *opts, diff --git a/src/providers/krb5/krb5_common.h b/src/providers/krb5/krb5_common.h index 68e4426a..c20c7b33 100644 --- a/src/providers/krb5/krb5_common.h +++ b/src/providers/krb5/krb5_common.h @@ -56,6 +56,7 @@ enum krb5_opts { KRB5_STORE_PASSWORD_IF_OFFLINE, KRB5_RENEWABLE_LIFETIME, KRB5_LIFETIME, + KRB5_RENEW_INTERVAL, KRB5_OPTS }; @@ -77,6 +78,7 @@ struct krb5_service { struct fo_service; struct deferred_auth_ctx; +struct renew_tgt_ctx; struct krb5_ctx { /* opts taken from kinit */ @@ -111,6 +113,7 @@ struct krb5_ctx { pcre *illegal_path_re; struct deferred_auth_ctx *deferred_auth_ctx; + struct renew_tgt_ctx *renew_tgt_ctx; }; struct remove_info_files_ctx { diff --git a/src/providers/krb5/krb5_init.c b/src/providers/krb5/krb5_init.c index f2b5dd79..0f1ed41f 100644 --- a/src/providers/krb5/krb5_init.c +++ b/src/providers/krb5/krb5_init.c @@ -56,6 +56,7 @@ int sssm_krb5_auth_init(struct be_ctx *bectx, const char *errstr; int errval; int errpos; + time_t renew_intv; if (krb5_options == NULL) { krb5_options = talloc_zero(bectx, struct krb5_options); @@ -128,6 +129,15 @@ int sssm_krb5_auth_init(struct be_ctx *bectx, } } + renew_intv = dp_opt_get_int(ctx->opts, KRB5_RENEW_INTERVAL); + if (renew_intv > 0) { + ret = init_renew_tgt(ctx, bectx, bectx->ev, renew_intv); + if (ret != EOK) { + DEBUG(1, ("init_renew_tgt failed.\n")); + goto fail; + } + } + ret = check_and_export_options(ctx->opts, bectx->domain); if (ret != EOK) { DEBUG(1, ("check_and_export_options failed.\n")); 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 <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" + +#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; +} |