From 78027feeb56d6fe216f699be86a4716aaef3f628 Mon Sep 17 00:00:00 2001 From: Sumit Bose Date: Thu, 26 May 2016 13:20:59 +0200 Subject: PAM/KRB5: optional otp and password prompting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Depending on the available Kerberos pre-authentication methods pam_sss will prompt the user for a password, 2 authentication factors or both. Resolves https://fedorahosted.org/sssd/ticket/2988 Reviewed-by: Nathaniel McCallum Reviewed-by: Lukáš Slebodník --- src/providers/krb5/krb5_child.c | 85 +++++++++++++++++++++++++++++++++++++++-- src/sss_client/pam_message.h | 2 + src/sss_client/pam_sss.c | 14 ++++++- src/sss_client/sss_cli.h | 5 +++ 4 files changed, 101 insertions(+), 5 deletions(-) diff --git a/src/providers/krb5/krb5_child.c b/src/providers/krb5/krb5_child.c index 3b3ebd9a9..a0a0f74d7 100644 --- a/src/providers/krb5/krb5_child.c +++ b/src/providers/krb5/krb5_child.c @@ -54,6 +54,7 @@ struct krb5_req { char* name; krb5_creds *creds; bool otp; + bool password_prompting; char *otp_vendor; char *otp_token_id; char *otp_challenge; @@ -586,24 +587,87 @@ static krb5_error_code sss_krb5_responder(krb5_context ctx, krb5_responder_context rctx) { struct krb5_req *kr = talloc_get_type(data, struct krb5_req); + const char * const *question_list; + size_t c; + const char *pwd; + int ret; + krb5_error_code kerr; if (kr == NULL) { return EINVAL; } + question_list = krb5_responder_list_questions(ctx, rctx); + + if (question_list != NULL) { + for (c = 0; question_list[c] != NULL; c++) { + DEBUG(SSSDBG_TRACE_ALL, "Got question [%s].\n", question_list[c]); + + if (strcmp(question_list[c], + KRB5_RESPONDER_QUESTION_PASSWORD) == 0) { + kr->password_prompting = true; + + if ((kr->pd->cmd == SSS_PAM_AUTHENTICATE + || kr->pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM + || kr->pd->cmd == SSS_PAM_CHAUTHTOK) + && sss_authtok_get_type(kr->pd->authtok) + == SSS_AUTHTOK_TYPE_PASSWORD) { + ret = sss_authtok_get_password(kr->pd->authtok, &pwd, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sss_authtok_get_password failed.\n"); + return ret; + } + + kerr = krb5_responder_set_answer(ctx, rctx, + KRB5_RESPONDER_QUESTION_PASSWORD, + pwd); + if (kerr != 0) { + DEBUG(SSSDBG_OP_FAILURE, + "krb5_responder_set_answer failed.\n"); + } + + return kerr; + } + } + } + } + return answer_otp(ctx, kr, rctx); } +#endif /* HAVE_KRB5_GET_INIT_CREDS_OPT_SET_RESPONDER */ + +static char *password_or_responder(const char *password) +{ +#ifdef HAVE_KRB5_GET_INIT_CREDS_OPT_SET_RESPONDER + /* If the new responder interface is available we will handle even simple + * passwords in the responder. */ + return NULL; +#else + return discard_const(password); #endif +} 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; + size_t c; struct krb5_req *kr = talloc_get_type(data, struct krb5_req); + DEBUG(SSSDBG_TRACE_ALL, + "sss_krb5_prompter name [%s] banner [%s] num_prompts [%d] EINVAL.\n", + name, banner, num_prompts); + if (num_prompts != 0) { DEBUG(SSSDBG_CRIT_FAILURE, "Cannot handle password prompts.\n"); + if (DEBUG_IS_SET(SSSDBG_TRACE_ALL)) { + for (c = 0; c < num_prompts; c++) { + DEBUG(SSSDBG_TRACE_ALL, "Prompt [%zu][%s].\n", c, + prompts[c].prompt); + } + } return KRB5_LIBOS_CANTREADPWD; } @@ -1217,7 +1281,7 @@ static krb5_error_code get_and_save_tgt(struct krb5_req *kr, DEBUG(SSSDBG_TRACE_FUNC, "Attempting kinit for realm [%s]\n",realm_name); kerr = krb5_get_init_creds_password(kr->ctx, kr->creds, kr->princ, - discard_const(password), + password_or_responder(password), sss_krb5_prompter, kr, 0, NULL, kr->options); if (kr->pd->cmd == SSS_PAM_PREAUTH) { @@ -1396,7 +1460,7 @@ static errno_t changepw_child(struct krb5_req *kr, bool prelim) DEBUG(SSSDBG_TRACE_FUNC, "Attempting kinit for realm [%s]\n",realm_name); kerr = krb5_get_init_creds_password(kr->ctx, kr->creds, kr->princ, - discard_const(password), + password_or_responder(password), prompter, kr, 0, SSSD_KRB5_CHANGEPW_PRINCIPAL, kr->options); @@ -1518,8 +1582,15 @@ static errno_t changepw_child(struct krb5_req *kr, bool prelim) * to change them back to get a fresh TGT. */ revert_changepw_options(kr->options); + ret = sss_authtok_set_password(kr->pd->authtok, newpassword, 0); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set password for fresh TGT.\n"); + return ret; + } + kerr = get_and_save_tgt(kr, newpassword); + sss_authtok_set_empty(kr->pd->authtok); sss_authtok_set_empty(kr->pd->newauthtok); if (kerr == 0) { @@ -1564,6 +1635,14 @@ static errno_t tgt_req_child(struct krb5_req *kr) if (kr->otp) { kerr = k5c_attach_otp_info_msg(kr); } + + if (kr->password_prompting) { + ret = pam_add_response(kr->pd, SSS_PASSWORD_PROMPTING, 0, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + goto done; + } + } } else { if (kerr == 0) { kerr = k5c_attach_ccname_msg(kr); @@ -1589,7 +1668,7 @@ static errno_t tgt_req_child(struct krb5_req *kr) set_changepw_options(kr->options); kerr = krb5_get_init_creds_password(kr->ctx, kr->creds, kr->princ, - discard_const(password), + password_or_responder(password), sss_krb5_prompter, kr, 0, SSSD_KRB5_CHANGEPW_PRINCIPAL, kr->options); diff --git a/src/sss_client/pam_message.h b/src/sss_client/pam_message.h index f0a7a076c..34889e074 100644 --- a/src/sss_client/pam_message.h +++ b/src/sss_client/pam_message.h @@ -25,6 +25,7 @@ #include #include +#include #include "sss_client/sss_cli.h" @@ -56,6 +57,7 @@ struct pam_items { char *otp_token_id; char *otp_challenge; char *first_factor; + bool password_prompting; char *cert_user; char *token_name; diff --git a/src/sss_client/pam_sss.c b/src/sss_client/pam_sss.c index 1ba02ad0e..fdb9c9076 100644 --- a/src/sss_client/pam_sss.c +++ b/src/sss_client/pam_sss.c @@ -1014,6 +1014,10 @@ static int eval_response(pam_handle_t *pamh, size_t buflen, uint8_t *buf, D(("cert user: [%s] token name: [%s]", pi->cert_user, pi->token_name)); break; + case SSS_PASSWORD_PROMPTING: + D(("Password prompting available.")); + pi->password_prompting = true; + break; default: D(("Unknown response type [%d]", type)); } @@ -1102,6 +1106,7 @@ static int get_pam_items(pam_handle_t *pamh, uint32_t flags, pi->otp_vendor = NULL; pi->otp_token_id = NULL; pi->otp_challenge = NULL; + pi->password_prompting = false; pi->cert_user = NULL; pi->token_name = NULL; @@ -1571,8 +1576,13 @@ static int get_authtok_for_authentication(pam_handle_t *pamh, if (flags & FLAGS_USE_2FA || (pi->otp_vendor != NULL && pi->otp_token_id != NULL && pi->otp_challenge != NULL)) { - ret = prompt_2fa(pamh, pi, _("First Factor: "), - _("Second Factor: ")); + if (pi->password_prompting) { + ret = prompt_2fa(pamh, pi, _("First Factor: "), + _("Second Factor (optional): ")); + } else { + ret = prompt_2fa(pamh, pi, _("First Factor: "), + _("Second Factor: ")); + } } else if (pi->cert_user != NULL) { ret = prompt_sc_pin(pamh, pi); } else { diff --git a/src/sss_client/sss_cli.h b/src/sss_client/sss_cli.h index 17d8e4503..b6610bc6d 100644 --- a/src/sss_client/sss_cli.h +++ b/src/sss_client/sss_cli.h @@ -426,6 +426,11 @@ enum response_type { SSS_OTP, /**< Indicates that the autotok was a OTP, so don't * cache it. There is no message. * @param None. */ + SSS_PASSWORD_PROMPTING, /**< Indicates that password prompting is possible. + * This might be used together with + * SSS_PAM_OTP_INFO to determine the type of + * prompting. There is no message. + * @param None. */ }; /** -- cgit