diff options
Diffstat (limited to 'usr/src/lib/pam_modules/krb5/krb5_authenticate.c')
-rw-r--r-- | usr/src/lib/pam_modules/krb5/krb5_authenticate.c | 348 |
1 files changed, 295 insertions, 53 deletions
diff --git a/usr/src/lib/pam_modules/krb5/krb5_authenticate.c b/usr/src/lib/pam_modules/krb5/krb5_authenticate.c index 4f136321a2..77f97c9d54 100644 --- a/usr/src/lib/pam_modules/krb5/krb5_authenticate.c +++ b/usr/src/lib/pam_modules/krb5/krb5_authenticate.c @@ -20,7 +20,7 @@ */ /* - * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -67,7 +67,8 @@ char *appdef[] = { "appdefaults", "kinit", NULL }; #define krb_realm (*(realmdef + 1)) -int attempt_krb5_auth(krb5_module_data_t *, char *, char **, boolean_t); +int attempt_krb5_auth(pam_handle_t *, krb5_module_data_t *, char *, + char **, boolean_t); void krb5_cleanup(pam_handle_t *, void *, int); extern errcode_t profile_get_options_boolean(); @@ -105,6 +106,7 @@ pam_sm_authenticate( krb5_module_data_t *kmd = NULL; krb5_repository_data_t *krb5_data = NULL; pam_repository_t *rep_data = NULL; + boolean_t do_pkinit = FALSE; for (i = 0; i < argc; i++) { if (strcmp(argv[i], "debug") == 0) { @@ -113,10 +115,11 @@ pam_sm_authenticate( warn = 0; } else if (strcmp(argv[i], "err_on_exp") == 0) { err_on_exp = 1; + } else if (strcmp(argv[i], "pkinit") == 0) { + do_pkinit = TRUE; } else { __pam_log(LOG_AUTH | LOG_ERR, - "PAM-KRB5 (auth) unrecognized option %s", - argv[i]); + "PAM-KRB5 (auth) unrecognized option %s", argv[i]); } } if (flags & PAM_SILENT) warn = 0; @@ -126,27 +129,86 @@ pam_sm_authenticate( "PAM-KRB5 (auth): pam_sm_authenticate flags=%d", flags); + /* + * pam_get_data could fail if we are being called for the first time + * or if the module is not found, PAM_NO_MODULE_DATA is not an error + */ + err = pam_get_data(pamh, KRB5_DATA, (const void**)&kmd); + if (!(err == PAM_SUCCESS || err == PAM_NO_MODULE_DATA)) + return (PAM_SYSTEM_ERR); + + /* + * If pam_krb5 was stacked higher in the auth stack and did PKINIT + * preauth sucessfully then this instance is a fallback to password + * based preauth and should just return PAM_IGNORE. + * + * The else clause is handled further down. + */ + if (kmd != NULL) { + if (++(kmd->auth_calls) > 2) { + /* + * pam_krb5 has been stacked > 2 times in the auth + * stack. Clear out the current kmd and proceed as if + * this is the first time pam_krb5 auth has been called. + */ + if (debug) { + __pam_log(LOG_AUTH | LOG_DEBUG, + "PAM-KRB5 (auth): stacked more than" + " two times, clearing kmd"); + } + /* clear out/free current kmd */ + err = pam_set_data(pamh, KRB5_DATA, NULL, NULL); + if (err != PAM_SUCCESS) { + krb5_cleanup(pamh, kmd, err); + result = err; + goto out; + } + kmd = NULL; + } else if (kmd->auth_calls == 2 && + kmd->auth_status == PAM_SUCCESS) { + /* + * The previous instance of pam_krb5 succeeded and this + * instance was a fall back in case it didn't succeed so + * return ignore. + */ + if (debug) { + __pam_log(LOG_AUTH | LOG_DEBUG, + "PAM-KRB5 (auth): PKINIT succeeded " + "earlier so returning PAM_IGNORE"); + } + return (PAM_IGNORE); + } + } + (void) pam_get_item(pamh, PAM_USER, (void**) &user); if (user == NULL || *user == '\0') { - if (debug) - __pam_log(LOG_AUTH | LOG_DEBUG, - "PAM-KRB5 (auth): user empty or null"); - return (PAM_USER_UNKNOWN); + if (do_pkinit) { + /* + * If doing PKINIT it is okay to prompt for the user + * name. + */ + if ((err = pam_get_user(pamh, &user, NULL)) != + PAM_SUCCESS) { + if (debug) { + __pam_log(LOG_AUTH | LOG_DEBUG, + "PAM-KRB5 (auth): get user failed: " + "%s", pam_strerror(pamh, err)); + } + return (err); + } + } else { + if (debug) + __pam_log(LOG_AUTH | LOG_DEBUG, + "PAM-KRB5 (auth): user empty or null"); + return (PAM_USER_UNKNOWN); + } } /* make sure a password entry exists for this user */ if (!get_pw_uid(user, &pw_uid)) return (PAM_USER_UNKNOWN); - /* - * pam_get_data could fail if we are being called for the first time - * or if the module is not found, PAM_NO_MODULE_DATA is not an error - */ - err = pam_get_data(pamh, KRB5_DATA, (const void**)&kmd); - if (!(err == PAM_SUCCESS || err == PAM_NO_MODULE_DATA)) - return (PAM_SYSTEM_ERR); - if (kmd == NULL) { kmd = calloc(1, sizeof (krb5_module_data_t)); if (kmd == NULL) { @@ -201,6 +263,8 @@ pam_sm_authenticate( kmd->password = NULL; kmd->age_status = PAM_SUCCESS; (void) memset((char *)&kmd->initcreds, 0, sizeof (krb5_creds)); + kmd->auth_calls = 1; + kmd->preauth_type = do_pkinit ? KRB_PKINIT : KRB_PASSWD; /* * For apps that already did krb5 auth exchange... @@ -257,7 +321,7 @@ pam_sm_authenticate( (void) pam_get_item(pamh, PAM_AUTHTOK, (void **)&password); - result = attempt_krb5_auth(kmd, user, &password, 1); + result = attempt_krb5_auth(pamh, kmd, user, &password, 1); out: if (kmd) { @@ -307,8 +371,114 @@ out: return (result); } +static krb5_error_code +pam_krb5_prompter( + krb5_context ctx, + void *data, + /* ARGSUSED1 */ + const char *name, + const char *banner, + int num_prompts, + krb5_prompt prompts[]) +{ + krb5_error_code rc; + pam_handle_t *pamh = (pam_handle_t *)data; + struct pam_conv *pam_convp; + struct pam_message *msgs; + struct pam_response *ret_respp; + int i; + krb5_prompt_type *prompt_type = krb5_get_prompt_types(ctx); + char tmpbuf[PAM_MAX_MSG_SIZE]; + + /* + * Because this function should never be used for password prompts, + * disallow password prompts. + */ + for (i = 0; i < num_prompts; i++) { + if (prompt_type[i] == KRB5_PROMPT_TYPE_PASSWORD) + return (KRB5_LIBOS_CANTREADPWD); + } + + if (num_prompts == 0) { + if (prompts) { + /* This shouldn't happen */ + return (PAM_SYSTEM_ERR); + } else { + /* no prompts so return */ + return (0); + } + } + if ((rc = pam_get_item(pamh, PAM_CONV, (void **)&pam_convp)) + != PAM_SUCCESS) { + return (rc); + } + if (pam_convp == NULL) { + return (PAM_SYSTEM_ERR); + } + + msgs = (struct pam_message *)calloc(num_prompts, + sizeof (struct pam_message)); + if (msgs == NULL) { + return (PAM_BUF_ERR); + } + (void) memset(msgs, 0, sizeof (struct pam_message) * num_prompts); + + for (i = 0; i < num_prompts; i++) { + /* convert krb prompt style to PAM style */ + if (prompts[i].hidden) { + msgs[i].msg_style = PAM_PROMPT_ECHO_OFF; + } else { + msgs[i].msg_style = PAM_PROMPT_ECHO_ON; + } + /* + * krb expects the prompting function to append ": " to the + * prompt string. + */ + if (snprintf(tmpbuf, sizeof (tmpbuf), "%s: ", + prompts[i].prompt) < 0) { + rc = PAM_BUF_ERR; + goto cleanup; + } + msgs[i].msg = strdup(tmpbuf); + if (msgs[i].msg == NULL) { + rc = PAM_BUF_ERR; + goto cleanup; + } + } + + /* + * Call PAM conv function to display the prompt. + */ + rc = (pam_convp->conv)(num_prompts, &msgs, &ret_respp, + pam_convp->appdata_ptr); + + if (rc == PAM_SUCCESS) { + for (i = 0; i < num_prompts; i++) { + /* convert PAM response to krb prompt reply format */ + prompts[i].reply->length = strlen(ret_respp[i].resp) + + 1; /* adding 1 for NULL terminator */ + prompts[i].reply->data = ret_respp[i].resp; + } + /* + * Note, just free ret_respp, not the resp data since that is + * being referenced by the krb prompt reply data pointer. + */ + free(ret_respp); + } + +cleanup: + for (i = 0; i < num_prompts; i++) { + if (msgs[i].msg) { + free(msgs[i].msg); + } + } + free(msgs); + return (rc); +} + int attempt_krb5_auth( + pam_handle_t *pamh, krb5_module_data_t *kmd, char *user, char **krb5_pass, @@ -494,30 +664,88 @@ attempt_krb5_auth( } /* - * mech_krb5 interprets empty passwords as NULL passwords - * and tries to read a password from stdin. Since we are in - * pam this is bad and should not be allowed. + * mech_krb5 interprets empty passwords as NULL passwords and tries to + * read a password from stdin. Since we are in pam this is bad and + * should not be allowed. + * + * Note, the logic now is that if the preauth_type is PKINIT then + * provide a proper PAMcentric prompt function that the underlying + * PKINIT preauth plugin will use to prompt for the PIN. */ - if (*krb5_pass == NULL || strlen(*krb5_pass) == 0) { - code = KRB5KRB_AP_ERR_BAD_INTEGRITY; + if (kmd->preauth_type == KRB_PKINIT) { + /* + * Do PKINIT preauth + * + * Note: we want to limit preauth types to just those for PKINIT + * but krb5_get_init_creds() doesn't support that at this point. + * Instead we rely on pam_krb5_prompter() to limit prompts to + * non-password types. So all we can do here is set the preauth + * list so krb5_get_init_creds() will try that first. + */ + krb5_preauthtype pk_pa_list[] = { + KRB5_PADATA_PK_AS_REQ, + KRB5_PADATA_PK_AS_REQ_OLD + }; + krb5_get_init_creds_opt_set_preauth_list(&opts, pk_pa_list, 2); + + if (*krb5_pass == NULL) { + /* let preauth plugin prompt for PIN */ + code = __krb5_get_init_creds_password(kmd->kcontext, + my_creds, + me, + NULL, /* clear text passwd */ + pam_krb5_prompter, /* prompter */ + pamh, /* prompter data */ + 0, /* start time */ + NULL, /* defaults to krbtgt@REALM */ + &opts, + &as_reply); + } else { + /* + * krb pkinit does not support setting the PIN so we + * punt on trying to use krb5_pass as the PIN for now. + * Note that once this is supported by pkinit the code + * should make sure krb5_pass isn't empty and if it is + * then that's an error. + */ + code = KRB5KRB_AP_ERR_BAD_INTEGRITY; + } } else { - /* - * We call our own private version of gic_pwd, because we need - * more information, such as password/account expiration, that - * is found in the as_reply. The "prompter" interface is not - * granular enough for PAM to make use of. + * Do password based preauths + * + * See earlier PKINIT comment. We are doing something similar + * here but we do not pass in a prompter (we assume + * pam_authtok_get has already prompted for that). */ - code = __krb5_get_init_creds_password(kmd->kcontext, - my_creds, - me, - *krb5_pass, /* clear text passwd */ - NULL, /* prompter */ - NULL, /* data */ - 0, /* start time */ - NULL, /* defaults to krbtgt@REALM */ - &opts, - &as_reply); + if (*krb5_pass == NULL || strlen(*krb5_pass) == 0) { + code = KRB5KRB_AP_ERR_BAD_INTEGRITY; + } else { + krb5_preauthtype pk_pa_list[] = { + KRB5_PADATA_ENC_TIMESTAMP + }; + + krb5_get_init_creds_opt_set_preauth_list(&opts, + pk_pa_list, 1); + + /* + * We call our own private version of gic_pwd, because + * we need more information, such as password/account + * expiration, that is found in the as_reply. The + * "prompter" interface is not granular enough for PAM + * to make use of. + */ + code = __krb5_get_init_creds_password(kmd->kcontext, + my_creds, + me, + *krb5_pass, /* clear text passwd */ + NULL, /* prompter */ + NULL, /* data */ + 0, /* start time */ + NULL, /* defaults to krbtgt@REALM */ + &opts, + &as_reply); + } } if (kmd->debug) @@ -628,20 +856,30 @@ attempt_krb5_auth( case KRB5KDC_ERR_KEY_EXP: if (!kmd->err_on_exp) { /* - * Request a tik for changepw service - * and it will tell us if pw is good or not. + * Request a tik for changepw service and it will tell + * us if pw is good or not. If PKINIT is being done it + * is possible that *krb5_pass may be NULL so check for + * that. If that is the case this function will return + * an error. */ - code = krb5_verifypw(kuser, *krb5_pass, kmd->debug); - - if (kmd->debug) - __pam_log(LOG_AUTH | LOG_DEBUG, - "PAM-KRB5 (auth): attempt_krb5_auth: " - "verifypw %d", code); - - if (code == 0) { - /* pw is good, set age status for acct_mgmt */ - kmd->age_status = PAM_NEW_AUTHTOK_REQD; + if (*krb5_pass != NULL) { + code = krb5_verifypw(kuser, *krb5_pass, + kmd->debug); + if (kmd->debug) { + __pam_log(LOG_AUTH | LOG_DEBUG, + "PAM-KRB5 (auth): " + "attempt_krb5_auth: " + "verifypw %d", code); + } + if (code == 0) { + /* + * pw is good, set age status for + * acct_mgmt. + */ + kmd->age_status = PAM_NEW_AUTHTOK_REQD; + } } + } break; @@ -656,18 +894,22 @@ attempt_krb5_auth( if (code == 0) { /* - * success for the entered pw + * success for the entered pw or PKINIT succeeded. * * we can't rely on the pw in PAM_AUTHTOK * to be the (correct) krb5 one so * store krb5 pw in module data for - * use in acct_mgmt + * use in acct_mgmt. Note that *krb5_pass may be NULL if we're + * doing PKINIT. */ - if (!(kmd->password = strdup(*krb5_pass))) { - __pam_log(LOG_AUTH | LOG_ERR, "Cannot strdup password"); + if (*krb5_pass != NULL && + !(kmd->password = strdup(*krb5_pass))) { + __pam_log(LOG_AUTH | LOG_ERR, + "Cannot strdup password"); result = PAM_BUF_ERR; goto out_err; } + result = PAM_SUCCESS; goto out; } |