From 06c03627c81a5252420931383a68eb67ba551667 Mon Sep 17 00:00:00 2001 From: Sumit Bose Date: Fri, 23 Apr 2010 13:37:22 +0200 Subject: Handle Krb5 password expiration warning --- src/providers/krb5/krb5_auth.c | 192 +++++++++++++++------------------------- src/providers/krb5/krb5_child.c | 177 ++++++++++++++++++++++++------------ src/sss_client/pam_sss.c | 14 ++- src/sss_client/sss_cli.h | 6 +- 4 files changed, 213 insertions(+), 176 deletions(-) diff --git a/src/providers/krb5/krb5_auth.c b/src/providers/krb5/krb5_auth.c index c98a5f78..48173ba7 100644 --- a/src/providers/krb5/krb5_auth.c +++ b/src/providers/krb5/krb5_auth.c @@ -45,74 +45,6 @@ #define KRB5_CHILD SSSD_LIBEXEC_PATH"/krb5_child" #endif -static errno_t add_krb5_env(struct dp_option *opts, const char *ccname, - struct pam_data *pd) -{ - int ret; - const char *dummy; - char *env; - TALLOC_CTX *tmp_ctx = NULL; - - tmp_ctx = talloc_new(NULL); - if (tmp_ctx == NULL) { - DEBUG(1, ("talloc_new failed.\n")); - return ENOMEM; - } - - if (ccname != NULL) { - env = talloc_asprintf(tmp_ctx, "%s=%s",CCACHE_ENV_NAME, ccname); - if (env == NULL) { - DEBUG(1, ("talloc_asprintf failed.\n")); - ret = ENOMEM; - goto done; - } - ret = pam_add_response(pd, SSS_PAM_ENV_ITEM, strlen(env)+1, - (uint8_t *) env); - if (ret != EOK) { - DEBUG(1, ("pam_add_response failed.\n")); - goto done; - } - } - - dummy = dp_opt_get_cstring(opts, KRB5_REALM); - if (dummy != NULL) { - env = talloc_asprintf(tmp_ctx, "%s=%s", SSSD_KRB5_REALM, dummy); - if (env == NULL) { - DEBUG(1, ("talloc_asprintf failed.\n")); - ret = ENOMEM; - goto done; - } - ret = pam_add_response(pd, SSS_PAM_ENV_ITEM, strlen(env)+1, - (uint8_t *) env); - if (ret != EOK) { - DEBUG(1, ("pam_add_response failed.\n")); - goto done; - } - } - - dummy = dp_opt_get_cstring(opts, KRB5_KDC); - if (dummy != NULL) { - env = talloc_asprintf(tmp_ctx, "%s=%s", SSSD_KRB5_KDC, dummy); - if (env == NULL) { - DEBUG(1, ("talloc_asprintf failed.\n")); - ret = ENOMEM; - goto done; - } - ret = pam_add_response(pd, SSS_PAM_ENV_ITEM, strlen(env)+1, - (uint8_t *) env); - if (ret != EOK) { - DEBUG(1, ("pam_add_response failed.\n")); - goto done; - } - } - - ret = EOK; - -done: - talloc_free(tmp_ctx); - return ret; -} - static errno_t check_if_ccache_file_is_used(uid_t uid, const char *ccname, bool *result) { @@ -1035,10 +967,10 @@ static void krb5_child_done(struct tevent_req *subreq) uint8_t *buf = NULL; ssize_t len = -1; ssize_t pref_len; - int p; - int32_t *msg_status; - int32_t *msg_type; - int32_t *msg_len; + size_t p; + int32_t msg_status; + int32_t msg_type; + int32_t msg_len; ret = handle_child_recv(subreq, pd, &buf, &len); talloc_zfree(kr->timeout_handler); @@ -1055,48 +987,83 @@ static void krb5_child_done(struct tevent_req *subreq) return; } - if ((size_t) len < 3*sizeof(int32_t)) { + /* 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; - msg_status = ((int32_t *)(buf+p)); - p += sizeof(int32_t); + SAFEALIGN_COPY_INT32(&msg_status, buf+p, &p); - msg_type = ((int32_t *)(buf+p)); - p += sizeof(int32_t); + while (p < len) { + SAFEALIGN_COPY_INT32(&msg_type, buf+p, &p); + SAFEALIGN_COPY_INT32(&msg_len, buf+p, &p); - msg_len = ((int32_t *)(buf+p)); - p += sizeof(int32_t); + DEBUG(9, ("child response [%d][%d][%d].\n", msg_status, msg_type, + msg_len)); - DEBUG(4, ("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)); - goto done; - } + if ((p + msg_len) > len) { + DEBUG(1, ("message format error [%d] > [%d].\n", p+msg_len, len)); + ret = EINVAL; + goto done; + } - 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; + /* 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; + } + } - ret = pam_add_response(pd, *msg_type, *msg_len, &buf[p]); + 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; + state->pam_status = msg_status; } - if (*msg_status == PAM_SUCCESS && pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM) { + /* 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; @@ -1106,7 +1073,7 @@ static void krb5_child_done(struct tevent_req *subreq) /* if using a dedicated kpasswd server.. */ if (kr->kpasswd_srv != NULL) { /* ..which is unreachable by now.. */ - if (*msg_status == PAM_AUTHTOK_LOCK_BUSY) { + 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) { @@ -1121,8 +1088,8 @@ static void krb5_child_done(struct tevent_req *subreq) /* 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 (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 */ @@ -1135,19 +1102,15 @@ static void krb5_child_done(struct tevent_req *subreq) fo_set_port_status(kr->srv, PORT_WORKING); } - pref_len = strlen(CCACHE_ENV_NAME)+1; - 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; - } - } else { - DEBUG(1, ("Missing ccache name in child response [%.*s].\n", *msg_len, - &buf[p])); + /* The following cases are left now: + * - offline (msg_status == PAM_AUTHINFO_UNAVAIL or + * msg_status == PAM_AUTHTOK_LOCK_BUSY) + * - successful authentication or password change + * + * For all these cases we expect that one of the messages for 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; } @@ -1246,19 +1209,10 @@ 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 = kr->pd; - struct krb5_ctx *krb5_ctx = kr->krb5_ctx; + struct pam_data *pd = state->pd; int ret; char *password = NULL; - if (pd->cmd == SSS_PAM_AUTHENTICATE || pd->cmd == SSS_PAM_CHAUTHTOK) { - ret = add_krb5_env(krb5_ctx->opts, kr->ccname, pd); - if (ret != EOK) { - DEBUG(1, ("add_krb5_env failed.\n")); - goto done; - } - } - if (kr->is_offline) { DEBUG(4, ("Backend is marked offline, retry later!\n")); state->pam_status = PAM_AUTHINFO_UNAVAIL; diff --git a/src/providers/krb5/krb5_child.c b/src/providers/krb5/krb5_child.c index 620e4d14..7249aeba 100644 --- a/src/providers/krb5/krb5_child.c +++ b/src/providers/krb5/krb5_child.c @@ -6,7 +6,7 @@ Authors: Sumit Bose - Copyright (C) 2009 Red Hat + 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 @@ -103,6 +103,36 @@ static const char *__krb5_error_msg; sss_krb5_free_error_message(krb5_error_ctx, __krb5_error_msg); \ } while(0); + +static krb5_error_code sss_krb5_prompter(krb5_context context, void *data, + const char *name, const char *banner, + int num_prompts, krb5_prompt prompts[]) +{ + int ret; + struct krb5_req *kr = talloc_get_type(data, struct krb5_req); + + if (num_prompts != 0) { + DEBUG(1, ("Cannot handle password prompts.\n")); + return KRB5_LIBOS_CANTREADPWD; + } + + if (banner == NULL || *banner == '\0') { + DEBUG(5, ("Prompter called with empty banner, nothing to do.\n")); + return EOK; + } + + DEBUG(9, ("Prompter called with [%s].\n", banner)); + + ret = pam_add_response(kr->pd, SSS_PAM_TEXT_MSG, strlen(banner)+1, + (const uint8_t *) banner); + if (ret != EOK) { + DEBUG(1, ("pam_add_response failed.\n")); + } + + return EOK; +} + + static krb5_error_code create_empty_cred(struct krb5_req *kr, krb5_creds **_cred) { krb5_error_code kerr; @@ -247,22 +277,49 @@ done: return kerr; } -static errno_t pack_response_packet(struct response *resp, int status, int type, - size_t len, const uint8_t *data) +static errno_t pack_response_packet(struct response *resp, int status, + struct pam_data *pd) { + size_t size = 0; size_t p = 0; + struct response_data *pdr; + + /* A buffer with the following structure must be created: + * 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 + */ - resp->buf = talloc_array(resp, uint8_t, - 3*sizeof(int32_t) + len); + size = sizeof(int32_t); + + pdr = pd->resp_list; + while (pdr != NULL) { + size += 2*sizeof(int32_t) + pdr->len; + pdr = pdr->next; + } + + + resp->buf = talloc_array(resp, uint8_t, size); if (!resp->buf) { DEBUG(1, ("Insufficient memory to create message.\n")); return ENOMEM; } SAFEALIGN_SET_INT32(&resp->buf[p], status, &p); - SAFEALIGN_SET_INT32(&resp->buf[p], type, &p); - SAFEALIGN_SET_INT32(&resp->buf[p], len, &p); - safealign_memcpy(&resp->buf[p], data, len, &p); + + pdr = pd->resp_list; + while(pdr != NULL) { + SAFEALIGN_SET_INT32(&resp->buf[p], pdr->type, &p); + SAFEALIGN_SET_INT32(&resp->buf[p], pdr->len, &p); + safealign_memcpy(&resp->buf[p], pdr->data, pdr->len, &p); + + pdr = pdr->next; + } + resp->size = p; @@ -271,15 +328,12 @@ static errno_t pack_response_packet(struct response *resp, int status, int type, static struct response *prepare_response_message(struct krb5_req *kr, krb5_error_code kerr, - char *user_error_message, int pam_status) { char *msg = NULL; const char *krb5_msg = NULL; int ret; struct response *resp; - size_t user_resp_len; - uint8_t *user_resp; resp = talloc_zero(kr, struct response); if (resp == NULL) { @@ -289,9 +343,8 @@ static struct response *prepare_response_message(struct krb5_req *kr, if (kerr == 0) { if(kr->pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM) { - ret = pack_response_packet(resp, PAM_SUCCESS, SSS_PAM_SYSTEM_INFO, - strlen("success") + 1, - (const uint8_t *) "success"); + pam_status = PAM_SUCCESS; + ret = EOK; } else { if (kr->ccname == NULL) { DEBUG(1, ("Error obtaining ccname.\n")); @@ -304,44 +357,28 @@ static struct response *prepare_response_message(struct krb5_req *kr, return NULL; } - ret = pack_response_packet(resp, PAM_SUCCESS, SSS_PAM_ENV_ITEM, - strlen(msg) + 1, (uint8_t *) msg); + pam_status = PAM_SUCCESS; + ret = pam_add_response(kr->pd, SSS_PAM_ENV_ITEM, strlen(msg) + 1, + (uint8_t *) msg); talloc_zfree(msg); } } else { - if (user_error_message != NULL) { - ret = pack_user_info_chpass_error(kr, user_error_message, - &user_resp_len, &user_resp); - if (ret != EOK) { - DEBUG(1, ("pack_user_info_chpass_error failed.\n")); - talloc_zfree(user_error_message); - } else { - ret = pack_response_packet(resp, pam_status, SSS_PAM_USER_INFO, - user_resp_len, user_resp); - if (ret != EOK) { - DEBUG(1, ("pack_response_packet failed.\n")); - talloc_zfree(user_error_message); - } - } - } - - if (user_error_message == NULL) { - krb5_msg = sss_krb5_get_error_message(krb5_error_ctx, kerr); - if (krb5_msg == NULL) { - DEBUG(1, ("sss_krb5_get_error_message failed.\n")); - return NULL; - } - - ret = pack_response_packet(resp, pam_status, SSS_PAM_SYSTEM_INFO, - strlen(krb5_msg) + 1, - (const uint8_t *) krb5_msg); - sss_krb5_free_error_message(krb5_error_ctx, krb5_msg); - } else { - + krb5_msg = sss_krb5_get_error_message(krb5_error_ctx, kerr); + if (krb5_msg == NULL) { + DEBUG(1, ("sss_krb5_get_error_message failed.\n")); + return NULL; } + ret = pam_add_response(kr->pd, SSS_PAM_SYSTEM_INFO, + strlen(krb5_msg) + 1, + (const uint8_t *) krb5_msg); + sss_krb5_free_error_message(krb5_error_ctx, krb5_msg); + } + if (ret != EOK) { + DEBUG(1, ("pam_add_response failed.\n")); } + ret = pack_response_packet(resp, pam_status, kr->pd); if (ret != EOK) { DEBUG(1, ("pack_response_packet failed.\n")); return NULL; @@ -350,15 +387,14 @@ static struct response *prepare_response_message(struct krb5_req *kr, return resp; } -static errno_t sendresponse(int fd, krb5_error_code kerr, - char *user_error_message, int pam_status, +static errno_t sendresponse(int fd, krb5_error_code kerr, int pam_status, struct krb5_req *kr) { struct response *resp; size_t written; int ret; - resp = prepare_response_message(kr, kerr, user_error_message, pam_status); + resp = prepare_response_message(kr, kerr, pam_status); if (resp == NULL) { DEBUG(1, ("prepare_response_message failed.\n")); return ENOMEM; @@ -481,8 +517,8 @@ static krb5_error_code get_and_save_tgt(struct krb5_req *kr, int ret; kerr = krb5_get_init_creds_password(kr->ctx, kr->creds, kr->princ, - password, NULL, NULL, 0, NULL, - kr->options); + password, sss_krb5_prompter, kr, 0, + NULL, kr->options); if (kerr != 0) { KRB5_DEBUG(1, kerr); return kerr; @@ -533,6 +569,9 @@ static errno_t changepw_child(int fd, struct krb5_req *kr) krb5_data result_code_string; krb5_data result_string; char *user_error_message = NULL; + size_t user_resp_len; + uint8_t *user_resp; + krb5_prompter_fct prompter = sss_krb5_prompter; pass_str = talloc_strndup(kr, (const char *) kr->pd->authtok, kr->pd->authtok_size); @@ -542,8 +581,13 @@ static errno_t changepw_child(int fd, struct krb5_req *kr) goto sendresponse; } + if (kr->pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM) { + /* We do not need a password expiration warning here. */ + prompter = NULL; + } + kerr = krb5_get_init_creds_password(kr->ctx, kr->creds, kr->princ, - pass_str, NULL, NULL, 0, + pass_str, prompter, kr, 0, kr->krb5_ctx->changepw_principle, kr->options); if (kerr != 0) { @@ -612,6 +656,20 @@ static errno_t changepw_child(int fd, struct krb5_req *kr) } } + if (user_error_message != NULL) { + ret = pack_user_info_chpass_error(kr->pd, user_error_message, + &user_resp_len, &user_resp); + if (ret != EOK) { + DEBUG(1, ("pack_user_info_chpass_error failed.\n")); + } else { + ret = pam_add_response(kr->pd, SSS_PAM_USER_INFO, user_resp_len, + user_resp); + if (ret != EOK) { + DEBUG(1, ("pack_response_packet failed.\n")); + } + } + } + pam_status = PAM_AUTHTOK_ERR; goto sendresponse; } @@ -631,7 +689,7 @@ static errno_t changepw_child(int fd, struct krb5_req *kr) } sendresponse: - ret = sendresponse(fd, kerr, user_error_message, pam_status, kr); + ret = sendresponse(fd, kerr, pam_status, kr); if (ret != EOK) { DEBUG(1, ("sendresponse failed.\n")); } @@ -662,7 +720,7 @@ static errno_t tgt_req_child(int fd, struct krb5_req *kr) So we validate the password by trying to get a changepw ticket. */ if (kerr == KRB5KDC_ERR_KEY_EXP) { kerr = krb5_get_init_creds_password(kr->ctx, kr->creds, kr->princ, - pass_str, NULL, NULL, 0, + pass_str, sss_krb5_prompter, kr, 0, kr->krb5_ctx->changepw_principle, kr->options); krb5_free_cred_contents(kr->ctx, kr->creds); @@ -693,7 +751,7 @@ static errno_t tgt_req_child(int fd, struct krb5_req *kr) } sendresponse: - ret = sendresponse(fd, kerr, NULL, pam_status, kr); + ret = sendresponse(fd, kerr, pam_status, kr); if (ret != EOK) { DEBUG(1, ("sendresponse failed.\n")); } @@ -712,7 +770,7 @@ static errno_t create_empty_ccache(int fd, struct krb5_req *kr) pam_status = PAM_SYSTEM_ERR; } - ret = sendresponse(fd, ret, NULL, pam_status, kr); + ret = sendresponse(fd, ret, pam_status, kr); if (ret != EOK) { DEBUG(1, ("sendresponse failed.\n")); } @@ -878,6 +936,15 @@ static int krb5_setup(struct krb5_req *kr, uint32_t offline) goto failed; } + /* A prompter is used to catch messages about when a password will + * expired. The library shall not use the prompter to ask for a new password + * but shall return KRB5KDC_ERR_KEY_EXP. */ + krb5_get_init_creds_opt_set_change_password_prompt(kr->options, 0); + if (kerr != 0) { + KRB5_DEBUG(1, kerr); + goto failed; + } + /* TODO: set options, e.g. * krb5_get_init_creds_opt_set_tkt_life * krb5_get_init_creds_opt_set_renew_life diff --git a/src/sss_client/pam_sss.c b/src/sss_client/pam_sss.c index 1b98cf01..d2758004 100644 --- a/src/sss_client/pam_sss.c +++ b/src/sss_client/pam_sss.c @@ -894,7 +894,7 @@ static int eval_response(pam_handle_t *pamh, size_t buflen, uint8_t *buf, switch(type) { case SSS_PAM_SYSTEM_INFO: if (buf[p + (len -1)] != '\0') { - D(("user info does not end with \\0.")); + D(("system info does not end with \\0.")); break; } logger(pamh, LOG_INFO, "system info: [%s]", &buf[p]); @@ -946,6 +946,18 @@ static int eval_response(pam_handle_t *pamh, size_t buflen, uint8_t *buf, D(("eval_user_info_response failed")); } break; + case SSS_PAM_TEXT_MSG: + if (buf[p + (len -1)] != '\0') { + D(("system info does not end with \\0.")); + break; + } + + ret = do_pam_conversation(pamh, PAM_TEXT_INFO, (char *) &buf[p], + NULL, NULL); + if (ret != PAM_SUCCESS) { + D(("do_pam_conversation failed.")); + } + break; default: D(("Unknown response type [%d]", type)); } diff --git a/src/sss_client/sss_cli.h b/src/sss_client/sss_cli.h index f7e58fe9..8712a6f9 100644 --- a/src/sss_client/sss_cli.h +++ b/src/sss_client/sss_cli.h @@ -316,9 +316,13 @@ enum response_type { * @param String, zero terminated, of the form * name=value. See putenv(3) and pam_putenv(3) for * details. */ - SSS_PAM_USER_INFO /**< A message which should be displayed to the user. + SSS_PAM_USER_INFO, /**< A message which should be displayed to the user. * @param User info message, see #user_info_type * for details. */ + SSS_PAM_TEXT_MSG, /**< A plain text message which should be displayed to + * the user.This should only be used in the case where + * it is not possile to use SSS_PAM_USER_INFO. + * @param A zero terminated string. */ }; /** -- cgit