krb5_delayed_online_authentication.c revision 9acfb09f7969a69f58bd45c856b01700541853ca
/*
SSSD
Kerberos 5 Backend Module -- Request a TGT when the system gets online
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>
#ifdef USE_KEYRING
#include <sys/types.h>
#include <keyutils.h>
#endif
#include "providers/krb5/krb5_auth.h"
#include "dhash.h"
#include "util/util.h"
#include "util/find_uid.h"
#define INITIAL_USER_TABLE_SIZE 10
struct deferred_auth_ctx {
hash_table_t *user_table;
struct be_ctx *be_ctx;
struct tevent_context *ev;
struct krb5_ctx *krb5_ctx;
};
struct auth_data {
struct be_ctx *be_ctx;
struct krb5_ctx *krb5_ctx;
struct pam_data *pd;
};
static void *hash_talloc(const size_t size, void *pvt)
{
return talloc_size(pvt, size);
}
static void hash_talloc_free(void *ptr, void *pvt)
{
talloc_free(ptr);
}
static void authenticate_user_done(struct tevent_req *req);
static void authenticate_user(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 pam_data *pd = auth_data->pd;
struct tevent_req *req;
DEBUG_PAM_DATA(9, pd);
#ifdef USE_KEYRING
char *password;
long keysize;
long keyrevoke;
errno_t ret;
keysize = keyctl_read_alloc(pd->key_serial, (void **)&password);
if (keysize == -1) {
ret = errno;
DEBUG(1, ("keyctl_read failed [%d][%s].\n", ret, strerror(ret)));
return;
}
ret = sss_authtok_set_password(pd->authtok, password, keysize);
safezero(password, keysize);
free(password);
if (ret) {
DEBUG(1, ("failed to set password in auth token [%d][%s].\n",
ret, strerror(ret)));
return;
}
keyrevoke = keyctl_revoke(pd->key_serial);
if (keyrevoke == -1) {
ret = errno;
DEBUG(1, ("keyctl_revoke failed [%d][%s].\n", ret, strerror(ret)));
}
#endif
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, authenticate_user_done, auth_data);
}
static void authenticate_user_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 {
if (pam_status == PAM_SUCCESS) {
DEBUG(4, ("Successfully authenticated user [%s].\n",
auth_data->pd->user));
} else {
DEBUG(1, ("Failed to authenticate user [%s].\n",
auth_data->pd->user));
}
}
talloc_free(auth_data);
}
static errno_t authenticate_stored_users(
struct deferred_auth_ctx *deferred_auth_ctx)
{
int ret;
hash_table_t *uid_table;
struct hash_iter_context_t *iter;
hash_entry_t *entry;
hash_key_t key;
hash_value_t value;
struct pam_data *pd;
struct auth_data *auth_data;
struct tevent_timer *te;
ret = get_uid_table(deferred_auth_ctx, &uid_table);
if (ret != HASH_SUCCESS) {
DEBUG(1, ("get_uid_table failed.\n"));
return ret;
}
iter = new_hash_iter_context(deferred_auth_ctx->user_table);
if (iter == NULL) {
DEBUG(1, ("new_hash_iter_context failed.\n"));
return EINVAL;
}
while ((entry = iter->next(iter)) != NULL) {
key.type = HASH_KEY_ULONG;
key.ul = entry->key.ul;
pd = talloc_get_type(entry->value.ptr, struct pam_data);
ret = hash_lookup(uid_table, &key, &value);
if (ret == HASH_SUCCESS) {
DEBUG(1, ("User [%s] is still logged in, "
"trying online authentication.\n", pd->user));
auth_data = talloc_zero(deferred_auth_ctx->be_ctx,
struct auth_data);
if (auth_data == NULL) {
DEBUG(1, ("talloc_zero failed.\n"));
} else {
auth_data->pd = talloc_steal(auth_data, pd);
auth_data->krb5_ctx = deferred_auth_ctx->krb5_ctx;
auth_data->be_ctx = deferred_auth_ctx->be_ctx;
te = tevent_add_timer(deferred_auth_ctx->ev,
auth_data, tevent_timeval_current(),
authenticate_user, auth_data);
if (te == NULL) {
DEBUG(1, ("tevent_add_timer failed.\n"));
}
}
} else {
DEBUG(1, ("User [%s] is not logged in anymore, "
"discarding online authentication.\n", pd->user));
talloc_free(pd);
}
ret = hash_delete(deferred_auth_ctx->user_table,
&entry->key);
if (ret != HASH_SUCCESS) {
DEBUG(1, ("hash_delete failed [%s].\n",
hash_error_string(ret)));
}
}
talloc_free(iter);
return EOK;
}
static void delayed_online_authentication_callback(void *private_data)
{
struct deferred_auth_ctx *deferred_auth_ctx =
talloc_get_type(private_data, struct deferred_auth_ctx);
int ret;
if (deferred_auth_ctx->user_table == NULL) {
DEBUG(1, ("Delayed online authentication activated, "
"but user table does not exists.\n"));
return;
}
DEBUG(5, ("Backend is online, starting delayed online authentication.\n"));
ret = authenticate_stored_users(deferred_auth_ctx);
if (ret != EOK) {
DEBUG(1, ("authenticate_stored_users failed.\n"));
}
return;
}
errno_t add_user_to_delayed_online_authentication(struct krb5_ctx *krb5_ctx,
struct pam_data *pd,
uid_t uid)
{
int ret;
hash_key_t key;
hash_value_t value;
struct pam_data *new_pd;
if (krb5_ctx->deferred_auth_ctx == NULL) {
DEBUG(1, ("Missing context for delayed online authentication.\n"));
return EINVAL;
}
if (krb5_ctx->deferred_auth_ctx->user_table == NULL) {
DEBUG(1, ("user_table not available.\n"));
return EINVAL;
}
if (sss_authtok_get_type(pd->authtok) != SSS_AUTHTOK_TYPE_PASSWORD) {
DEBUG(1, ("Invalid authtok for user [%s].\n", pd->user));
return EINVAL;
}
ret = copy_pam_data(krb5_ctx->deferred_auth_ctx, pd, &new_pd);
if (ret != EOK) {
DEBUG(1, ("copy_pam_data failed\n"));
return ENOMEM;
}
#ifdef USE_KEYRING
const char *password;
size_t len;
ret = sss_authtok_get_password(new_pd->authtok, &password, &len);
if (ret) {
DEBUG(1, ("Failed to get password [%d][%s].\n", ret, strerror(ret)));
sss_authtok_set_empty(new_pd->authtok);
talloc_free(new_pd);
return ret;
}
new_pd->key_serial = add_key("user", new_pd->user, password, len,
KEY_SPEC_SESSION_KEYRING);
if (new_pd->key_serial == -1) {
ret = errno;
DEBUG(1, ("add_key failed [%d][%s].\n", ret, strerror(ret)));
sss_authtok_set_empty(new_pd->authtok);
talloc_free(new_pd);
return ret;
}
DEBUG(9, ("Saved authtok of user [%s] with serial [%ld].\n",
new_pd->user, new_pd->key_serial));
sss_authtok_set_empty(new_pd->authtok);
#endif
key.type = HASH_KEY_ULONG;
key.ul = uid;
value.type = HASH_VALUE_PTR;
value.ptr = new_pd;
ret = hash_enter(krb5_ctx->deferred_auth_ctx->user_table,
&key, &value);
if (ret != HASH_SUCCESS) {
DEBUG(1, ("Cannot add user [%s] to table [%s], "
"delayed online authentication not possible.\n",
pd->user, hash_error_string(ret)));
talloc_free(new_pd);
return ENOMEM;
}
DEBUG(9, ("Added user [%s] successfully to "
"delayed online authentication.\n", pd->user));
return EOK;
}
errno_t init_delayed_online_authentication(struct krb5_ctx *krb5_ctx,
struct be_ctx *be_ctx,
struct tevent_context *ev)
{
int ret;
hash_table_t *tmp_table;
ret = get_uid_table(krb5_ctx, &tmp_table);
if (ret != EOK) {
if (ret == ENOSYS) {
DEBUG(0, ("Delayed online auth was requested "
"on an unsupported system.\n"));
} else {
DEBUG(0, ("Delayed online auth was requested "
"but initialisation failed.\n"));
}
return ret;
}
ret = hash_destroy(tmp_table);
if (ret != HASH_SUCCESS) {
DEBUG(1, ("hash_destroy failed [%s].\n", hash_error_string(ret)));
return EFAULT;
}
krb5_ctx->deferred_auth_ctx = talloc_zero(krb5_ctx,
struct deferred_auth_ctx);
if (krb5_ctx->deferred_auth_ctx == NULL) {
DEBUG(1, ("talloc_zero failed.\n"));
return ENOMEM;
}
ret = hash_create_ex(INITIAL_USER_TABLE_SIZE,
&krb5_ctx->deferred_auth_ctx->user_table,
0, 0, 0, 0, hash_talloc, hash_talloc_free,
krb5_ctx->deferred_auth_ctx,
NULL, NULL);
if (ret != HASH_SUCCESS) {
DEBUG(1, ("hash_create_ex failed [%s]\n", hash_error_string(ret)));
ret = ENOMEM;
goto fail;
}
krb5_ctx->deferred_auth_ctx->be_ctx = be_ctx;
krb5_ctx->deferred_auth_ctx->krb5_ctx = krb5_ctx;
krb5_ctx->deferred_auth_ctx->ev = ev;
ret = be_add_online_cb(krb5_ctx, be_ctx,
delayed_online_authentication_callback,
krb5_ctx->deferred_auth_ctx, NULL);
if (ret != EOK) {
DEBUG(1, ("be_add_online_cb failed.\n"));
goto fail;
}
/* TODO: add destructor */
return EOK;
fail:
talloc_zfree(krb5_ctx->deferred_auth_ctx);
return ret;
}