10Sstevel@tonic-gate /*
20Sstevel@tonic-gate * CDDL HEADER START
30Sstevel@tonic-gate *
40Sstevel@tonic-gate * The contents of this file are subject to the terms of the
51474Smp153739 * Common Development and Distribution License (the "License").
61474Smp153739 * You may not use this file except in compliance with the License.
70Sstevel@tonic-gate *
80Sstevel@tonic-gate * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
90Sstevel@tonic-gate * or http://www.opensolaris.org/os/licensing.
100Sstevel@tonic-gate * See the License for the specific language governing permissions
110Sstevel@tonic-gate * and limitations under the License.
120Sstevel@tonic-gate *
130Sstevel@tonic-gate * When distributing Covered Code, include this CDDL HEADER in each
140Sstevel@tonic-gate * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
150Sstevel@tonic-gate * If applicable, add the following below this CDDL HEADER, with the
160Sstevel@tonic-gate * fields enclosed by brackets "[]" replaced with your own identifying
170Sstevel@tonic-gate * information: Portions Copyright [yyyy] [name of copyright owner]
180Sstevel@tonic-gate *
190Sstevel@tonic-gate * CDDL HEADER END
200Sstevel@tonic-gate */
2111411SSurya.Prakki@Sun.COM
220Sstevel@tonic-gate /*
2312941Swill.fiveash@oracle.com * Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved.
240Sstevel@tonic-gate */
250Sstevel@tonic-gate
260Sstevel@tonic-gate #include <security/pam_appl.h>
270Sstevel@tonic-gate #include <security/pam_modules.h>
280Sstevel@tonic-gate #include <security/pam_impl.h>
290Sstevel@tonic-gate #include <string.h>
300Sstevel@tonic-gate #include <stdio.h>
310Sstevel@tonic-gate #include <stdlib.h>
320Sstevel@tonic-gate #include <sys/types.h>
330Sstevel@tonic-gate #include <sys/stat.h>
340Sstevel@tonic-gate #include <pwd.h>
350Sstevel@tonic-gate #include <syslog.h>
360Sstevel@tonic-gate #include <libintl.h>
370Sstevel@tonic-gate #include <k5-int.h>
380Sstevel@tonic-gate #include "profile/prof_int.h"
390Sstevel@tonic-gate #include <netdb.h>
400Sstevel@tonic-gate #include <ctype.h>
410Sstevel@tonic-gate #include "utils.h"
420Sstevel@tonic-gate #include "krb5_repository.h"
430Sstevel@tonic-gate
440Sstevel@tonic-gate #define KRB5_DEFAULT_OPTIONS 0
450Sstevel@tonic-gate
460Sstevel@tonic-gate int forwardable_flag = 0;
470Sstevel@tonic-gate int renewable_flag = 0;
480Sstevel@tonic-gate int proxiable_flag = 0;
490Sstevel@tonic-gate int no_address_flag = 0;
500Sstevel@tonic-gate profile_options_boolean config_option[] = {
510Sstevel@tonic-gate { "forwardable", &forwardable_flag, 0 },
520Sstevel@tonic-gate { "renewable", &renewable_flag, 0 },
530Sstevel@tonic-gate { "proxiable", &proxiable_flag, 0 },
540Sstevel@tonic-gate { "no_addresses", &no_address_flag, 0 },
550Sstevel@tonic-gate { NULL, NULL, 0 }
560Sstevel@tonic-gate };
570Sstevel@tonic-gate char *renew_timeval;
580Sstevel@tonic-gate char *life_timeval;
590Sstevel@tonic-gate profile_option_strings config_times[] = {
600Sstevel@tonic-gate { "max_life", &life_timeval, 0 },
610Sstevel@tonic-gate { "max_renewable_life", &renew_timeval, 0 },
620Sstevel@tonic-gate { NULL, NULL, 0 }
630Sstevel@tonic-gate };
640Sstevel@tonic-gate char *realmdef[] = { "realms", NULL, NULL, NULL };
650Sstevel@tonic-gate char *appdef[] = { "appdefaults", "kinit", NULL };
660Sstevel@tonic-gate
670Sstevel@tonic-gate #define krb_realm (*(realmdef + 1))
680Sstevel@tonic-gate
6911778SWill.Fiveash@Sun.COM int attempt_krb5_auth(pam_handle_t *, krb5_module_data_t *, char *,
7011778SWill.Fiveash@Sun.COM char **, boolean_t);
710Sstevel@tonic-gate void krb5_cleanup(pam_handle_t *, void *, int);
720Sstevel@tonic-gate
730Sstevel@tonic-gate extern errcode_t profile_get_options_boolean();
740Sstevel@tonic-gate extern errcode_t profile_get_options_string();
752941Ssemery extern int krb5_verifypw(char *, char *, int);
760Sstevel@tonic-gate extern krb5_error_code krb5_verify_init_creds(krb5_context,
770Sstevel@tonic-gate krb5_creds *, krb5_principal, krb5_keytab, krb5_ccache *,
780Sstevel@tonic-gate krb5_verify_init_creds_opt *);
793641Ssemery extern krb5_error_code __krb5_get_init_creds_password(krb5_context,
803641Ssemery krb5_creds *, krb5_principal, char *, krb5_prompter_fct, void *,
813641Ssemery krb5_deltat, char *, krb5_get_init_creds_opt *,
823641Ssemery krb5_kdc_rep **);
830Sstevel@tonic-gate
840Sstevel@tonic-gate /*
850Sstevel@tonic-gate * pam_sm_authenticate - Authenticate user
860Sstevel@tonic-gate */
870Sstevel@tonic-gate int
pam_sm_authenticate(pam_handle_t * pamh,int flags,int argc,const char ** argv)880Sstevel@tonic-gate pam_sm_authenticate(
890Sstevel@tonic-gate pam_handle_t *pamh,
900Sstevel@tonic-gate int flags,
910Sstevel@tonic-gate int argc,
920Sstevel@tonic-gate const char **argv)
930Sstevel@tonic-gate {
942554Ssemery char *user = NULL;
950Sstevel@tonic-gate int err;
960Sstevel@tonic-gate int result = PAM_AUTH_ERR;
970Sstevel@tonic-gate /* pam.conf options */
980Sstevel@tonic-gate int debug = 0;
990Sstevel@tonic-gate int warn = 1;
1000Sstevel@tonic-gate /* return an error on password expire */
1010Sstevel@tonic-gate int err_on_exp = 0;
1020Sstevel@tonic-gate int i;
1030Sstevel@tonic-gate char *password = NULL;
1040Sstevel@tonic-gate uid_t pw_uid;
1050Sstevel@tonic-gate krb5_module_data_t *kmd = NULL;
1060Sstevel@tonic-gate krb5_repository_data_t *krb5_data = NULL;
1070Sstevel@tonic-gate pam_repository_t *rep_data = NULL;
10811778SWill.Fiveash@Sun.COM boolean_t do_pkinit = FALSE;
1090Sstevel@tonic-gate
1100Sstevel@tonic-gate for (i = 0; i < argc; i++) {
1110Sstevel@tonic-gate if (strcmp(argv[i], "debug") == 0) {
1120Sstevel@tonic-gate debug = 1;
1130Sstevel@tonic-gate } else if (strcmp(argv[i], "nowarn") == 0) {
1140Sstevel@tonic-gate warn = 0;
1150Sstevel@tonic-gate } else if (strcmp(argv[i], "err_on_exp") == 0) {
1160Sstevel@tonic-gate err_on_exp = 1;
11711778SWill.Fiveash@Sun.COM } else if (strcmp(argv[i], "pkinit") == 0) {
11811778SWill.Fiveash@Sun.COM do_pkinit = TRUE;
1190Sstevel@tonic-gate } else {
1203391Ssemery __pam_log(LOG_AUTH | LOG_ERR,
12111778SWill.Fiveash@Sun.COM "PAM-KRB5 (auth) unrecognized option %s", argv[i]);
1220Sstevel@tonic-gate }
1230Sstevel@tonic-gate }
1240Sstevel@tonic-gate if (flags & PAM_SILENT) warn = 0;
1250Sstevel@tonic-gate
1260Sstevel@tonic-gate if (debug)
1273391Ssemery __pam_log(LOG_AUTH | LOG_DEBUG,
1280Sstevel@tonic-gate "PAM-KRB5 (auth): pam_sm_authenticate flags=%d",
1290Sstevel@tonic-gate flags);
1300Sstevel@tonic-gate
1310Sstevel@tonic-gate /*
1320Sstevel@tonic-gate * pam_get_data could fail if we are being called for the first time
1330Sstevel@tonic-gate * or if the module is not found, PAM_NO_MODULE_DATA is not an error
1340Sstevel@tonic-gate */
1350Sstevel@tonic-gate err = pam_get_data(pamh, KRB5_DATA, (const void**)&kmd);
1360Sstevel@tonic-gate if (!(err == PAM_SUCCESS || err == PAM_NO_MODULE_DATA))
1373391Ssemery return (PAM_SYSTEM_ERR);
1380Sstevel@tonic-gate
13911778SWill.Fiveash@Sun.COM /*
14011778SWill.Fiveash@Sun.COM * If pam_krb5 was stacked higher in the auth stack and did PKINIT
14111778SWill.Fiveash@Sun.COM * preauth sucessfully then this instance is a fallback to password
14211778SWill.Fiveash@Sun.COM * based preauth and should just return PAM_IGNORE.
14311778SWill.Fiveash@Sun.COM *
14411778SWill.Fiveash@Sun.COM * The else clause is handled further down.
14511778SWill.Fiveash@Sun.COM */
14611778SWill.Fiveash@Sun.COM if (kmd != NULL) {
14711778SWill.Fiveash@Sun.COM if (++(kmd->auth_calls) > 2) {
14811778SWill.Fiveash@Sun.COM /*
14911778SWill.Fiveash@Sun.COM * pam_krb5 has been stacked > 2 times in the auth
15011778SWill.Fiveash@Sun.COM * stack. Clear out the current kmd and proceed as if
15111778SWill.Fiveash@Sun.COM * this is the first time pam_krb5 auth has been called.
15211778SWill.Fiveash@Sun.COM */
15311778SWill.Fiveash@Sun.COM if (debug) {
15411778SWill.Fiveash@Sun.COM __pam_log(LOG_AUTH | LOG_DEBUG,
15511778SWill.Fiveash@Sun.COM "PAM-KRB5 (auth): stacked more than"
15611778SWill.Fiveash@Sun.COM " two times, clearing kmd");
15711778SWill.Fiveash@Sun.COM }
15811778SWill.Fiveash@Sun.COM /* clear out/free current kmd */
15911778SWill.Fiveash@Sun.COM err = pam_set_data(pamh, KRB5_DATA, NULL, NULL);
16011778SWill.Fiveash@Sun.COM if (err != PAM_SUCCESS) {
16111778SWill.Fiveash@Sun.COM krb5_cleanup(pamh, kmd, err);
16211778SWill.Fiveash@Sun.COM result = err;
16311778SWill.Fiveash@Sun.COM goto out;
16411778SWill.Fiveash@Sun.COM }
16511778SWill.Fiveash@Sun.COM kmd = NULL;
16611778SWill.Fiveash@Sun.COM } else if (kmd->auth_calls == 2 &&
16711778SWill.Fiveash@Sun.COM kmd->auth_status == PAM_SUCCESS) {
16811778SWill.Fiveash@Sun.COM /*
16911778SWill.Fiveash@Sun.COM * The previous instance of pam_krb5 succeeded and this
17011778SWill.Fiveash@Sun.COM * instance was a fall back in case it didn't succeed so
17111778SWill.Fiveash@Sun.COM * return ignore.
17211778SWill.Fiveash@Sun.COM */
17311778SWill.Fiveash@Sun.COM if (debug) {
17411778SWill.Fiveash@Sun.COM __pam_log(LOG_AUTH | LOG_DEBUG,
17511778SWill.Fiveash@Sun.COM "PAM-KRB5 (auth): PKINIT succeeded "
17611778SWill.Fiveash@Sun.COM "earlier so returning PAM_IGNORE");
17711778SWill.Fiveash@Sun.COM }
17811778SWill.Fiveash@Sun.COM return (PAM_IGNORE);
17911778SWill.Fiveash@Sun.COM }
18011778SWill.Fiveash@Sun.COM }
18111778SWill.Fiveash@Sun.COM
18211778SWill.Fiveash@Sun.COM (void) pam_get_item(pamh, PAM_USER, (void**) &user);
18311778SWill.Fiveash@Sun.COM
18411778SWill.Fiveash@Sun.COM if (user == NULL || *user == '\0') {
18511778SWill.Fiveash@Sun.COM if (do_pkinit) {
18611778SWill.Fiveash@Sun.COM /*
18711778SWill.Fiveash@Sun.COM * If doing PKINIT it is okay to prompt for the user
18811778SWill.Fiveash@Sun.COM * name.
18911778SWill.Fiveash@Sun.COM */
19011778SWill.Fiveash@Sun.COM if ((err = pam_get_user(pamh, &user, NULL)) !=
19111778SWill.Fiveash@Sun.COM PAM_SUCCESS) {
19211778SWill.Fiveash@Sun.COM if (debug) {
19311778SWill.Fiveash@Sun.COM __pam_log(LOG_AUTH | LOG_DEBUG,
19411778SWill.Fiveash@Sun.COM "PAM-KRB5 (auth): get user failed: "
19511778SWill.Fiveash@Sun.COM "%s", pam_strerror(pamh, err));
19611778SWill.Fiveash@Sun.COM }
19711778SWill.Fiveash@Sun.COM return (err);
19811778SWill.Fiveash@Sun.COM }
19911778SWill.Fiveash@Sun.COM } else {
20011778SWill.Fiveash@Sun.COM if (debug)
20111778SWill.Fiveash@Sun.COM __pam_log(LOG_AUTH | LOG_DEBUG,
20211778SWill.Fiveash@Sun.COM "PAM-KRB5 (auth): user empty or null");
20311778SWill.Fiveash@Sun.COM return (PAM_USER_UNKNOWN);
20411778SWill.Fiveash@Sun.COM }
20511778SWill.Fiveash@Sun.COM }
20611778SWill.Fiveash@Sun.COM
20711778SWill.Fiveash@Sun.COM /* make sure a password entry exists for this user */
20811778SWill.Fiveash@Sun.COM if (!get_pw_uid(user, &pw_uid))
20911778SWill.Fiveash@Sun.COM return (PAM_USER_UNKNOWN);
21011778SWill.Fiveash@Sun.COM
2110Sstevel@tonic-gate if (kmd == NULL) {
2120Sstevel@tonic-gate kmd = calloc(1, sizeof (krb5_module_data_t));
2130Sstevel@tonic-gate if (kmd == NULL) {
2140Sstevel@tonic-gate result = PAM_BUF_ERR;
2150Sstevel@tonic-gate goto out;
2160Sstevel@tonic-gate }
2170Sstevel@tonic-gate
2180Sstevel@tonic-gate err = pam_set_data(pamh, KRB5_DATA, kmd, &krb5_cleanup);
2190Sstevel@tonic-gate if (err != PAM_SUCCESS) {
2200Sstevel@tonic-gate free(kmd);
2210Sstevel@tonic-gate result = err;
2220Sstevel@tonic-gate goto out;
2230Sstevel@tonic-gate }
2240Sstevel@tonic-gate }
2250Sstevel@tonic-gate
2260Sstevel@tonic-gate if (!kmd->env) {
2270Sstevel@tonic-gate char buffer[512];
2280Sstevel@tonic-gate
2290Sstevel@tonic-gate if (snprintf(buffer, sizeof (buffer),
23011780SWill.Fiveash@Sun.COM "%s=FILE:/tmp/krb5cc_%d",
23111780SWill.Fiveash@Sun.COM KRB5_ENV_CCNAME, (int)pw_uid) >= sizeof (buffer)) {
2320Sstevel@tonic-gate result = PAM_SYSTEM_ERR;
2330Sstevel@tonic-gate goto out;
2340Sstevel@tonic-gate }
2350Sstevel@tonic-gate
2360Sstevel@tonic-gate /* we MUST copy this to the heap for the putenv to work! */
2370Sstevel@tonic-gate kmd->env = strdup(buffer);
2380Sstevel@tonic-gate if (!kmd->env) {
2390Sstevel@tonic-gate result = PAM_BUF_ERR;
2400Sstevel@tonic-gate goto out;
2410Sstevel@tonic-gate } else {
2420Sstevel@tonic-gate if (putenv(kmd->env)) {
2430Sstevel@tonic-gate result = PAM_SYSTEM_ERR;
2440Sstevel@tonic-gate goto out;
2450Sstevel@tonic-gate }
2460Sstevel@tonic-gate }
2470Sstevel@tonic-gate }
2480Sstevel@tonic-gate
2495201Ssemery if (kmd->user != NULL)
2505201Ssemery free(kmd->user);
2513391Ssemery if ((kmd->user = strdup(user)) == NULL) {
2523391Ssemery result = PAM_BUF_ERR;
2533391Ssemery goto out;
2543391Ssemery }
2553391Ssemery
2560Sstevel@tonic-gate kmd->auth_status = PAM_AUTH_ERR;
2570Sstevel@tonic-gate kmd->debug = debug;
2580Sstevel@tonic-gate kmd->warn = warn;
2590Sstevel@tonic-gate kmd->err_on_exp = err_on_exp;
2600Sstevel@tonic-gate kmd->ccache = NULL;
2610Sstevel@tonic-gate kmd->kcontext = NULL;
2620Sstevel@tonic-gate kmd->password = NULL;
2630Sstevel@tonic-gate kmd->age_status = PAM_SUCCESS;
2640Sstevel@tonic-gate (void) memset((char *)&kmd->initcreds, 0, sizeof (krb5_creds));
26511778SWill.Fiveash@Sun.COM kmd->auth_calls = 1;
26611778SWill.Fiveash@Sun.COM kmd->preauth_type = do_pkinit ? KRB_PKINIT : KRB_PASSWD;
2670Sstevel@tonic-gate
2680Sstevel@tonic-gate /*
2690Sstevel@tonic-gate * For apps that already did krb5 auth exchange...
2700Sstevel@tonic-gate * Now that we've created the kmd structure, we can
2710Sstevel@tonic-gate * return SUCCESS. 'kmd' may be needed later by other
2720Sstevel@tonic-gate * PAM functions, thats why we wait until this point to
2730Sstevel@tonic-gate * return.
2740Sstevel@tonic-gate */
2752554Ssemery (void) pam_get_item(pamh, PAM_REPOSITORY, (void **)&rep_data);
2760Sstevel@tonic-gate
2770Sstevel@tonic-gate if (rep_data != NULL) {
2780Sstevel@tonic-gate if (strcmp(rep_data->type, KRB5_REPOSITORY_NAME) != 0) {
2790Sstevel@tonic-gate if (debug)
2803391Ssemery __pam_log(LOG_AUTH | LOG_DEBUG,
28111780SWill.Fiveash@Sun.COM "PAM-KRB5 (auth): wrong"
28211780SWill.Fiveash@Sun.COM "repository found (%s), returning "
28311780SWill.Fiveash@Sun.COM "PAM_IGNORE", rep_data->type);
2840Sstevel@tonic-gate return (PAM_IGNORE);
2850Sstevel@tonic-gate }
2860Sstevel@tonic-gate if (rep_data->scope_len == sizeof (krb5_repository_data_t)) {
2870Sstevel@tonic-gate krb5_data = (krb5_repository_data_t *)rep_data->scope;
2880Sstevel@tonic-gate
2890Sstevel@tonic-gate if (krb5_data->flags ==
29011780SWill.Fiveash@Sun.COM SUNW_PAM_KRB5_ALREADY_AUTHENTICATED &&
29111780SWill.Fiveash@Sun.COM krb5_data->principal != NULL &&
29211780SWill.Fiveash@Sun.COM strlen(krb5_data->principal)) {
2930Sstevel@tonic-gate if (debug)
2943391Ssemery __pam_log(LOG_AUTH | LOG_DEBUG,
29511780SWill.Fiveash@Sun.COM "PAM-KRB5 (auth): Principal "
29611780SWill.Fiveash@Sun.COM "%s already authenticated",
29711780SWill.Fiveash@Sun.COM krb5_data->principal);
2980Sstevel@tonic-gate kmd->auth_status = PAM_SUCCESS;
2990Sstevel@tonic-gate return (PAM_SUCCESS);
3000Sstevel@tonic-gate }
3010Sstevel@tonic-gate }
3020Sstevel@tonic-gate }
3030Sstevel@tonic-gate
3040Sstevel@tonic-gate /*
3050Sstevel@tonic-gate * if root key exists in the keytab, it's a random key so no
3060Sstevel@tonic-gate * need to prompt for pw and we just return IGNORE.
3070Sstevel@tonic-gate *
3080Sstevel@tonic-gate * note we don't need to force a prompt for pw as authtok_get
3090Sstevel@tonic-gate * is required to be stacked above this module.
3100Sstevel@tonic-gate */
3110Sstevel@tonic-gate if ((strcmp(user, ROOT_UNAME) == 0) &&
3120Sstevel@tonic-gate key_in_keytab(user, debug)) {
3130Sstevel@tonic-gate if (debug)
3143391Ssemery __pam_log(LOG_AUTH | LOG_DEBUG,
3150Sstevel@tonic-gate "PAM-KRB5 (auth): "
3160Sstevel@tonic-gate "key for '%s' in keytab, returning IGNORE", user);
3170Sstevel@tonic-gate result = PAM_IGNORE;
3180Sstevel@tonic-gate goto out;
3190Sstevel@tonic-gate }
3200Sstevel@tonic-gate
3212554Ssemery (void) pam_get_item(pamh, PAM_AUTHTOK, (void **)&password);
3220Sstevel@tonic-gate
32311778SWill.Fiveash@Sun.COM result = attempt_krb5_auth(pamh, kmd, user, &password, 1);
3240Sstevel@tonic-gate
3250Sstevel@tonic-gate out:
3260Sstevel@tonic-gate if (kmd) {
3270Sstevel@tonic-gate if (debug)
3283391Ssemery __pam_log(LOG_AUTH | LOG_DEBUG,
3290Sstevel@tonic-gate "PAM-KRB5 (auth): pam_sm_auth finalize"
3300Sstevel@tonic-gate " ccname env, result =%d, env ='%s',"
3310Sstevel@tonic-gate " age = %d, status = %d",
3320Sstevel@tonic-gate result, kmd->env ? kmd->env : "<null>",
3330Sstevel@tonic-gate kmd->age_status, kmd->auth_status);
3340Sstevel@tonic-gate
3350Sstevel@tonic-gate if (kmd->env &&
3360Sstevel@tonic-gate !(kmd->age_status == PAM_NEW_AUTHTOK_REQD &&
33711780SWill.Fiveash@Sun.COM kmd->auth_status == PAM_SUCCESS)) {
3380Sstevel@tonic-gate
3390Sstevel@tonic-gate
3400Sstevel@tonic-gate if (result == PAM_SUCCESS) {
3410Sstevel@tonic-gate /*
3420Sstevel@tonic-gate * Put ccname into the pamh so that login
3430Sstevel@tonic-gate * apps can pick this up when they run
3440Sstevel@tonic-gate * pam_getenvlist().
3450Sstevel@tonic-gate */
3460Sstevel@tonic-gate if ((result = pam_putenv(pamh, kmd->env))
3470Sstevel@tonic-gate != PAM_SUCCESS) {
3480Sstevel@tonic-gate /* should not happen but... */
3493391Ssemery __pam_log(LOG_AUTH | LOG_ERR,
3500Sstevel@tonic-gate "PAM-KRB5 (auth):"
3513391Ssemery " pam_putenv failed: result: %d",
3520Sstevel@tonic-gate result);
3530Sstevel@tonic-gate goto cleanupccname;
3540Sstevel@tonic-gate }
3550Sstevel@tonic-gate } else {
3560Sstevel@tonic-gate cleanupccname:
3570Sstevel@tonic-gate /* for lack of a Solaris unputenv() */
3580Sstevel@tonic-gate krb5_unsetenv(KRB5_ENV_CCNAME);
3590Sstevel@tonic-gate free(kmd->env);
3600Sstevel@tonic-gate kmd->env = NULL;
3610Sstevel@tonic-gate }
3620Sstevel@tonic-gate }
3630Sstevel@tonic-gate kmd->auth_status = result;
3640Sstevel@tonic-gate }
3650Sstevel@tonic-gate
3660Sstevel@tonic-gate if (debug)
3673391Ssemery __pam_log(LOG_AUTH | LOG_DEBUG,
3680Sstevel@tonic-gate "PAM-KRB5 (auth): end: %s", pam_strerror(pamh, result));
3690Sstevel@tonic-gate
3700Sstevel@tonic-gate return (result);
3710Sstevel@tonic-gate }
3720Sstevel@tonic-gate
37311778SWill.Fiveash@Sun.COM static krb5_error_code
pam_krb5_prompter(krb5_context ctx,void * data,const char * name,const char * banner,int num_prompts,krb5_prompt prompts[])37411778SWill.Fiveash@Sun.COM pam_krb5_prompter(
37511778SWill.Fiveash@Sun.COM krb5_context ctx,
37611778SWill.Fiveash@Sun.COM void *data,
37711778SWill.Fiveash@Sun.COM /* ARGSUSED1 */
37811778SWill.Fiveash@Sun.COM const char *name,
37911778SWill.Fiveash@Sun.COM const char *banner,
38011778SWill.Fiveash@Sun.COM int num_prompts,
38111778SWill.Fiveash@Sun.COM krb5_prompt prompts[])
38211778SWill.Fiveash@Sun.COM {
383*12946Swill.fiveash@oracle.com krb5_error_code rc = KRB5_LIBOS_CANTREADPWD;
38411778SWill.Fiveash@Sun.COM pam_handle_t *pamh = (pam_handle_t *)data;
38511778SWill.Fiveash@Sun.COM struct pam_conv *pam_convp;
386*12946Swill.fiveash@oracle.com struct pam_message *msgs = NULL;
387*12946Swill.fiveash@oracle.com struct pam_response *ret_respp = NULL;
38811778SWill.Fiveash@Sun.COM int i;
38911778SWill.Fiveash@Sun.COM krb5_prompt_type *prompt_type = krb5_get_prompt_types(ctx);
39011778SWill.Fiveash@Sun.COM char tmpbuf[PAM_MAX_MSG_SIZE];
39111778SWill.Fiveash@Sun.COM
392*12946Swill.fiveash@oracle.com if (prompts) {
393*12946Swill.fiveash@oracle.com assert(num_prompts > 0);
394*12946Swill.fiveash@oracle.com }
39511778SWill.Fiveash@Sun.COM /*
39611778SWill.Fiveash@Sun.COM * Because this function should never be used for password prompts,
39711778SWill.Fiveash@Sun.COM * disallow password prompts.
39811778SWill.Fiveash@Sun.COM */
39911778SWill.Fiveash@Sun.COM for (i = 0; i < num_prompts; i++) {
40012942Swill.fiveash@oracle.com switch (prompt_type[i]) {
40112942Swill.fiveash@oracle.com case KRB5_PROMPT_TYPE_PASSWORD:
40212942Swill.fiveash@oracle.com case KRB5_PROMPT_TYPE_NEW_PASSWORD:
40312942Swill.fiveash@oracle.com case KRB5_PROMPT_TYPE_NEW_PASSWORD_AGAIN:
404*12946Swill.fiveash@oracle.com return (rc);
40512942Swill.fiveash@oracle.com }
40611778SWill.Fiveash@Sun.COM }
40711778SWill.Fiveash@Sun.COM
408*12946Swill.fiveash@oracle.com if (pam_get_item(pamh, PAM_CONV, (void **)&pam_convp) != PAM_SUCCESS) {
40911778SWill.Fiveash@Sun.COM return (rc);
41011778SWill.Fiveash@Sun.COM }
41111778SWill.Fiveash@Sun.COM if (pam_convp == NULL) {
412*12946Swill.fiveash@oracle.com return (rc);
41311778SWill.Fiveash@Sun.COM }
41411778SWill.Fiveash@Sun.COM
41511778SWill.Fiveash@Sun.COM msgs = (struct pam_message *)calloc(num_prompts,
41611778SWill.Fiveash@Sun.COM sizeof (struct pam_message));
41711778SWill.Fiveash@Sun.COM if (msgs == NULL) {
418*12946Swill.fiveash@oracle.com return (rc);
41911778SWill.Fiveash@Sun.COM }
42011778SWill.Fiveash@Sun.COM (void) memset(msgs, 0, sizeof (struct pam_message) * num_prompts);
42111778SWill.Fiveash@Sun.COM
42211778SWill.Fiveash@Sun.COM for (i = 0; i < num_prompts; i++) {
42311778SWill.Fiveash@Sun.COM /* convert krb prompt style to PAM style */
42411778SWill.Fiveash@Sun.COM if (prompts[i].hidden) {
42511778SWill.Fiveash@Sun.COM msgs[i].msg_style = PAM_PROMPT_ECHO_OFF;
42611778SWill.Fiveash@Sun.COM } else {
42711778SWill.Fiveash@Sun.COM msgs[i].msg_style = PAM_PROMPT_ECHO_ON;
42811778SWill.Fiveash@Sun.COM }
42911778SWill.Fiveash@Sun.COM /*
43011778SWill.Fiveash@Sun.COM * krb expects the prompting function to append ": " to the
43111778SWill.Fiveash@Sun.COM * prompt string.
43211778SWill.Fiveash@Sun.COM */
43311778SWill.Fiveash@Sun.COM if (snprintf(tmpbuf, sizeof (tmpbuf), "%s: ",
43411778SWill.Fiveash@Sun.COM prompts[i].prompt) < 0) {
43511778SWill.Fiveash@Sun.COM goto cleanup;
43611778SWill.Fiveash@Sun.COM }
43711778SWill.Fiveash@Sun.COM msgs[i].msg = strdup(tmpbuf);
43811778SWill.Fiveash@Sun.COM if (msgs[i].msg == NULL) {
43911778SWill.Fiveash@Sun.COM goto cleanup;
44011778SWill.Fiveash@Sun.COM }
44111778SWill.Fiveash@Sun.COM }
44211778SWill.Fiveash@Sun.COM
44311778SWill.Fiveash@Sun.COM /*
44411778SWill.Fiveash@Sun.COM * Call PAM conv function to display the prompt.
44511778SWill.Fiveash@Sun.COM */
44611778SWill.Fiveash@Sun.COM
447*12946Swill.fiveash@oracle.com if ((pam_convp->conv)(num_prompts, &msgs, &ret_respp,
448*12946Swill.fiveash@oracle.com pam_convp->appdata_ptr) == PAM_SUCCESS) {
44911778SWill.Fiveash@Sun.COM for (i = 0; i < num_prompts; i++) {
45011778SWill.Fiveash@Sun.COM /* convert PAM response to krb prompt reply format */
451*12946Swill.fiveash@oracle.com assert(prompts[i].reply->data != NULL);
452*12946Swill.fiveash@oracle.com assert(ret_respp[i].resp != NULL);
453*12946Swill.fiveash@oracle.com
454*12946Swill.fiveash@oracle.com if (strlcpy(prompts[i].reply->data,
455*12946Swill.fiveash@oracle.com ret_respp[i].resp, prompts[i].reply->length) >=
456*12946Swill.fiveash@oracle.com prompts[i].reply->length) {
457*12946Swill.fiveash@oracle.com char errmsg[1][PAM_MAX_MSG_SIZE];
458*12946Swill.fiveash@oracle.com
459*12946Swill.fiveash@oracle.com (void) snprintf(errmsg[0], PAM_MAX_MSG_SIZE,
460*12946Swill.fiveash@oracle.com "%s", dgettext(TEXT_DOMAIN,
461*12946Swill.fiveash@oracle.com "Reply too long: "));
462*12946Swill.fiveash@oracle.com (void) __pam_display_msg(pamh, PAM_ERROR_MSG,
463*12946Swill.fiveash@oracle.com 1, errmsg, NULL);
464*12946Swill.fiveash@oracle.com goto cleanup;
465*12946Swill.fiveash@oracle.com } else {
466*12946Swill.fiveash@oracle.com char *retp;
467*12946Swill.fiveash@oracle.com
468*12946Swill.fiveash@oracle.com /*
469*12946Swill.fiveash@oracle.com * newline must be replaced with \0 terminator
470*12946Swill.fiveash@oracle.com */
471*12946Swill.fiveash@oracle.com retp = strchr(prompts[i].reply->data, '\n');
472*12946Swill.fiveash@oracle.com if (retp != NULL)
473*12946Swill.fiveash@oracle.com *retp = '\0';
474*12946Swill.fiveash@oracle.com /* NULL terminator should not be counted */
475*12946Swill.fiveash@oracle.com prompts[i].reply->length =
476*12946Swill.fiveash@oracle.com strlen(prompts[i].reply->data);
477*12946Swill.fiveash@oracle.com }
47811778SWill.Fiveash@Sun.COM }
479*12946Swill.fiveash@oracle.com rc = 0;
48011778SWill.Fiveash@Sun.COM }
48111778SWill.Fiveash@Sun.COM
48211778SWill.Fiveash@Sun.COM cleanup:
48311778SWill.Fiveash@Sun.COM for (i = 0; i < num_prompts; i++) {
48411778SWill.Fiveash@Sun.COM if (msgs[i].msg) {
48511778SWill.Fiveash@Sun.COM free(msgs[i].msg);
48611778SWill.Fiveash@Sun.COM }
487*12946Swill.fiveash@oracle.com if (ret_respp[i].resp) {
488*12946Swill.fiveash@oracle.com /* 0 out sensitive data before free() */
489*12946Swill.fiveash@oracle.com (void) memset(ret_respp[i].resp, 0,
490*12946Swill.fiveash@oracle.com strlen(ret_respp[i].resp));
491*12946Swill.fiveash@oracle.com free(ret_respp[i].resp);
492*12946Swill.fiveash@oracle.com }
49311778SWill.Fiveash@Sun.COM }
49411778SWill.Fiveash@Sun.COM free(msgs);
495*12946Swill.fiveash@oracle.com free(ret_respp);
49611778SWill.Fiveash@Sun.COM return (rc);
49711778SWill.Fiveash@Sun.COM }
49811778SWill.Fiveash@Sun.COM
4990Sstevel@tonic-gate int
attempt_krb5_auth(pam_handle_t * pamh,krb5_module_data_t * kmd,char * user,char ** krb5_pass,boolean_t verify_tik)5000Sstevel@tonic-gate attempt_krb5_auth(
50111778SWill.Fiveash@Sun.COM pam_handle_t *pamh,
5020Sstevel@tonic-gate krb5_module_data_t *kmd,
5030Sstevel@tonic-gate char *user,
5040Sstevel@tonic-gate char **krb5_pass,
5052554Ssemery boolean_t verify_tik)
5060Sstevel@tonic-gate {
5075201Ssemery krb5_principal me = NULL, clientp = NULL;
5085201Ssemery krb5_principal server = NULL, serverp = NULL;
5090Sstevel@tonic-gate krb5_creds *my_creds;
5100Sstevel@tonic-gate krb5_timestamp now;
5110Sstevel@tonic-gate krb5_error_code code = 0;
5120Sstevel@tonic-gate char kuser[2*MAXHOSTNAMELEN];
5130Sstevel@tonic-gate krb5_deltat lifetime;
5140Sstevel@tonic-gate krb5_deltat rlife;
5150Sstevel@tonic-gate krb5_deltat krb5_max_duration;
5160Sstevel@tonic-gate int options = KRB5_DEFAULT_OPTIONS;
5170Sstevel@tonic-gate krb5_data tgtname = {
5180Sstevel@tonic-gate 0,
5190Sstevel@tonic-gate KRB5_TGS_NAME_SIZE,
5200Sstevel@tonic-gate KRB5_TGS_NAME
5210Sstevel@tonic-gate };
52212941Swill.fiveash@oracle.com krb5_get_init_creds_opt *opts = NULL;
5233641Ssemery krb5_kdc_rep *as_reply = NULL;
5240Sstevel@tonic-gate /*
5250Sstevel@tonic-gate * "result" should not be assigned PAM_SUCCESS unless
5260Sstevel@tonic-gate * authentication has succeeded and there are no other errors.
5270Sstevel@tonic-gate *
5280Sstevel@tonic-gate * "code" is sometimes used for PAM codes, sometimes for krb5
5290Sstevel@tonic-gate * codes. Be careful.
5300Sstevel@tonic-gate */
5310Sstevel@tonic-gate int result = PAM_AUTH_ERR;
5320Sstevel@tonic-gate
5330Sstevel@tonic-gate if (kmd->debug)
5343391Ssemery __pam_log(LOG_AUTH | LOG_DEBUG,
5350Sstevel@tonic-gate "PAM-KRB5 (auth): attempt_krb5_auth: start: user='%s'",
5360Sstevel@tonic-gate user ? user : "<null>");
5370Sstevel@tonic-gate
5380Sstevel@tonic-gate /* need to free context with krb5_free_context */
5398991SPeter.Shoults@Sun.COM if (code = krb5_init_secure_context(&kmd->kcontext)) {
5403391Ssemery __pam_log(LOG_AUTH | LOG_ERR,
54111780SWill.Fiveash@Sun.COM "PAM-KRB5 (auth): Error initializing "
54211780SWill.Fiveash@Sun.COM "krb5: %s",
54311780SWill.Fiveash@Sun.COM error_message(code));
5440Sstevel@tonic-gate return (PAM_SYSTEM_ERR);
5450Sstevel@tonic-gate }
5460Sstevel@tonic-gate
5470Sstevel@tonic-gate if ((code = get_kmd_kuser(kmd->kcontext, (const char *)user, kuser,
54811780SWill.Fiveash@Sun.COM 2*MAXHOSTNAMELEN)) != 0) {
5490Sstevel@tonic-gate /* get_kmd_kuser returns proper PAM error statuses */
5500Sstevel@tonic-gate return (code);
5510Sstevel@tonic-gate }
5520Sstevel@tonic-gate
5530Sstevel@tonic-gate if ((code = krb5_parse_name(kmd->kcontext, kuser, &me)) != 0) {
5540Sstevel@tonic-gate krb5_free_context(kmd->kcontext);
5550Sstevel@tonic-gate kmd->kcontext = NULL;
5563391Ssemery return (PAM_SYSTEM_ERR);
5570Sstevel@tonic-gate }
5580Sstevel@tonic-gate
5590Sstevel@tonic-gate /* call krb5_free_cred_contents() on error */
5600Sstevel@tonic-gate my_creds = &kmd->initcreds;
5610Sstevel@tonic-gate
5623391Ssemery if ((code =
5633391Ssemery krb5_copy_principal(kmd->kcontext, me, &my_creds->client))) {
5643391Ssemery result = PAM_SYSTEM_ERR;
5653391Ssemery goto out_err;
5663391Ssemery }
5675201Ssemery clientp = my_creds->client;
5680Sstevel@tonic-gate
5690Sstevel@tonic-gate if (code = krb5_build_principal_ext(kmd->kcontext, &server,
57011780SWill.Fiveash@Sun.COM krb5_princ_realm(kmd->kcontext, me)->length,
57111780SWill.Fiveash@Sun.COM krb5_princ_realm(kmd->kcontext, me)->data,
57211780SWill.Fiveash@Sun.COM tgtname.length, tgtname.data,
57311780SWill.Fiveash@Sun.COM krb5_princ_realm(kmd->kcontext, me)->length,
57411780SWill.Fiveash@Sun.COM krb5_princ_realm(kmd->kcontext, me)->data, 0)) {
5753391Ssemery __pam_log(LOG_AUTH | LOG_ERR,
57611780SWill.Fiveash@Sun.COM "PAM-KRB5 (auth): attempt_krb5_auth: "
57711780SWill.Fiveash@Sun.COM "krb5_build_princ_ext failed: %s",
57811780SWill.Fiveash@Sun.COM error_message(code));
5793391Ssemery result = PAM_SYSTEM_ERR;
5800Sstevel@tonic-gate goto out;
5810Sstevel@tonic-gate }
5820Sstevel@tonic-gate
5830Sstevel@tonic-gate if (code = krb5_copy_principal(kmd->kcontext, server,
58411780SWill.Fiveash@Sun.COM &my_creds->server)) {
5853391Ssemery result = PAM_SYSTEM_ERR;
5860Sstevel@tonic-gate goto out_err;
5870Sstevel@tonic-gate }
5885201Ssemery serverp = my_creds->server;
5890Sstevel@tonic-gate
5900Sstevel@tonic-gate if (code = krb5_timeofday(kmd->kcontext, &now)) {
5913391Ssemery __pam_log(LOG_AUTH | LOG_ERR,
59211780SWill.Fiveash@Sun.COM "PAM-KRB5 (auth): attempt_krb5_auth: "
59311780SWill.Fiveash@Sun.COM "krb5_timeofday failed: %s",
59411780SWill.Fiveash@Sun.COM error_message(code));
5953391Ssemery result = PAM_SYSTEM_ERR;
5960Sstevel@tonic-gate goto out;
5970Sstevel@tonic-gate }
5980Sstevel@tonic-gate
5990Sstevel@tonic-gate /*
6000Sstevel@tonic-gate * set the values for lifetime and rlife to be the maximum
6010Sstevel@tonic-gate * possible
6020Sstevel@tonic-gate */
6030Sstevel@tonic-gate krb5_max_duration = KRB5_KDB_EXPIRATION - now - 60*60;
6040Sstevel@tonic-gate lifetime = krb5_max_duration;
6050Sstevel@tonic-gate rlife = krb5_max_duration;
6060Sstevel@tonic-gate
6070Sstevel@tonic-gate /*
6080Sstevel@tonic-gate * Let us get the values for various options
6090Sstevel@tonic-gate * from Kerberos configuration file
6100Sstevel@tonic-gate */
6110Sstevel@tonic-gate
6120Sstevel@tonic-gate krb_realm = krb5_princ_realm(kmd->kcontext, me)->data;
6130Sstevel@tonic-gate profile_get_options_boolean(kmd->kcontext->profile,
61411780SWill.Fiveash@Sun.COM realmdef, config_option);
6150Sstevel@tonic-gate profile_get_options_boolean(kmd->kcontext->profile,
61611780SWill.Fiveash@Sun.COM appdef, config_option);
6170Sstevel@tonic-gate profile_get_options_string(kmd->kcontext->profile,
61811780SWill.Fiveash@Sun.COM realmdef, config_times);
6190Sstevel@tonic-gate profile_get_options_string(kmd->kcontext->profile,
62011780SWill.Fiveash@Sun.COM appdef, config_times);
6210Sstevel@tonic-gate
6220Sstevel@tonic-gate if (renew_timeval) {
6230Sstevel@tonic-gate code = krb5_string_to_deltat(renew_timeval, &rlife);
6240Sstevel@tonic-gate if (code != 0 || rlife == 0 || rlife > krb5_max_duration) {
6253391Ssemery __pam_log(LOG_AUTH | LOG_ERR,
62611780SWill.Fiveash@Sun.COM "PAM-KRB5 (auth): Bad max_renewable_life "
62711780SWill.Fiveash@Sun.COM " value '%s' in Kerberos config file",
6280Sstevel@tonic-gate renew_timeval);
6290Sstevel@tonic-gate result = PAM_SYSTEM_ERR;
6300Sstevel@tonic-gate goto out;
6310Sstevel@tonic-gate }
6320Sstevel@tonic-gate }
6330Sstevel@tonic-gate if (life_timeval) {
6340Sstevel@tonic-gate code = krb5_string_to_deltat(life_timeval, &lifetime);
6350Sstevel@tonic-gate if (code != 0 || lifetime == 0 ||
6360Sstevel@tonic-gate lifetime > krb5_max_duration) {
6373391Ssemery __pam_log(LOG_AUTH | LOG_ERR,
63811780SWill.Fiveash@Sun.COM "lifetime value '%s' in Kerberos config file",
6390Sstevel@tonic-gate life_timeval);
6400Sstevel@tonic-gate result = PAM_SYSTEM_ERR;
6410Sstevel@tonic-gate goto out;
6420Sstevel@tonic-gate }
6430Sstevel@tonic-gate }
6440Sstevel@tonic-gate /* start timer when request gets to KDC */
6450Sstevel@tonic-gate my_creds->times.starttime = 0;
6460Sstevel@tonic-gate my_creds->times.endtime = now + lifetime;
6470Sstevel@tonic-gate
6480Sstevel@tonic-gate if (options & KDC_OPT_RENEWABLE) {
6490Sstevel@tonic-gate my_creds->times.renew_till = now + rlife;
6500Sstevel@tonic-gate } else
6510Sstevel@tonic-gate my_creds->times.renew_till = 0;
6520Sstevel@tonic-gate
65312941Swill.fiveash@oracle.com code = krb5_get_init_creds_opt_alloc(kmd->kcontext, &opts);
65412941Swill.fiveash@oracle.com if (code != 0) {
65512941Swill.fiveash@oracle.com __pam_log(LOG_AUTH | LOG_ERR,
65612941Swill.fiveash@oracle.com "Error allocating gic opts: %s",
65712941Swill.fiveash@oracle.com error_message(code));
65812941Swill.fiveash@oracle.com result = PAM_SYSTEM_ERR;
65912941Swill.fiveash@oracle.com goto out;
66012941Swill.fiveash@oracle.com }
66112941Swill.fiveash@oracle.com
66212941Swill.fiveash@oracle.com krb5_get_init_creds_opt_set_tkt_life(opts, lifetime);
6630Sstevel@tonic-gate
6640Sstevel@tonic-gate if (proxiable_flag) { /* Set in config file */
6650Sstevel@tonic-gate if (kmd->debug)
6663391Ssemery __pam_log(LOG_AUTH | LOG_DEBUG,
66711780SWill.Fiveash@Sun.COM "PAM-KRB5 (auth): Proxiable tickets "
66811780SWill.Fiveash@Sun.COM "requested");
66912941Swill.fiveash@oracle.com krb5_get_init_creds_opt_set_proxiable(opts, TRUE);
6700Sstevel@tonic-gate }
6710Sstevel@tonic-gate if (forwardable_flag) {
6720Sstevel@tonic-gate if (kmd->debug)
6733391Ssemery __pam_log(LOG_AUTH | LOG_DEBUG,
67411780SWill.Fiveash@Sun.COM "PAM-KRB5 (auth): Forwardable tickets "
67511780SWill.Fiveash@Sun.COM "requested");
67612941Swill.fiveash@oracle.com krb5_get_init_creds_opt_set_forwardable(opts, TRUE);
6770Sstevel@tonic-gate }
6780Sstevel@tonic-gate if (renewable_flag) {
6790Sstevel@tonic-gate if (kmd->debug)
6803391Ssemery __pam_log(LOG_AUTH | LOG_DEBUG,
68111780SWill.Fiveash@Sun.COM "PAM-KRB5 (auth): Renewable tickets "
68211780SWill.Fiveash@Sun.COM "requested");
68312941Swill.fiveash@oracle.com krb5_get_init_creds_opt_set_renew_life(opts, rlife);
6840Sstevel@tonic-gate }
6850Sstevel@tonic-gate if (no_address_flag) {
6860Sstevel@tonic-gate if (kmd->debug)
6873391Ssemery __pam_log(LOG_AUTH | LOG_DEBUG,
68811780SWill.Fiveash@Sun.COM "PAM-KRB5 (auth): Addressless tickets "
68911780SWill.Fiveash@Sun.COM "requested");
69012941Swill.fiveash@oracle.com krb5_get_init_creds_opt_set_address_list(opts, NULL);
6910Sstevel@tonic-gate }
6920Sstevel@tonic-gate
6930Sstevel@tonic-gate /*
69411778SWill.Fiveash@Sun.COM * mech_krb5 interprets empty passwords as NULL passwords and tries to
69511778SWill.Fiveash@Sun.COM * read a password from stdin. Since we are in pam this is bad and
69611778SWill.Fiveash@Sun.COM * should not be allowed.
69711778SWill.Fiveash@Sun.COM *
69811778SWill.Fiveash@Sun.COM * Note, the logic now is that if the preauth_type is PKINIT then
69911778SWill.Fiveash@Sun.COM * provide a proper PAMcentric prompt function that the underlying
70011778SWill.Fiveash@Sun.COM * PKINIT preauth plugin will use to prompt for the PIN.
7010Sstevel@tonic-gate */
70211778SWill.Fiveash@Sun.COM if (kmd->preauth_type == KRB_PKINIT) {
7033641Ssemery /*
70411778SWill.Fiveash@Sun.COM * Do PKINIT preauth
70511778SWill.Fiveash@Sun.COM *
70611778SWill.Fiveash@Sun.COM * Note: we want to limit preauth types to just those for PKINIT
70711778SWill.Fiveash@Sun.COM * but krb5_get_init_creds() doesn't support that at this point.
70811778SWill.Fiveash@Sun.COM * Instead we rely on pam_krb5_prompter() to limit prompts to
70911778SWill.Fiveash@Sun.COM * non-password types. So all we can do here is set the preauth
71011778SWill.Fiveash@Sun.COM * list so krb5_get_init_creds() will try that first.
7113641Ssemery */
71211778SWill.Fiveash@Sun.COM krb5_preauthtype pk_pa_list[] = {
71311778SWill.Fiveash@Sun.COM KRB5_PADATA_PK_AS_REQ,
71411778SWill.Fiveash@Sun.COM KRB5_PADATA_PK_AS_REQ_OLD
71511778SWill.Fiveash@Sun.COM };
71612941Swill.fiveash@oracle.com krb5_get_init_creds_opt_set_preauth_list(opts, pk_pa_list, 2);
71712941Swill.fiveash@oracle.com
71812941Swill.fiveash@oracle.com if (*krb5_pass == NULL || strlen(*krb5_pass) != 0) {
71912941Swill.fiveash@oracle.com if (*krb5_pass != NULL) {
72012941Swill.fiveash@oracle.com /* treat the krb5_pass as a PIN */
72112941Swill.fiveash@oracle.com code = krb5_get_init_creds_opt_set_pa(
72212941Swill.fiveash@oracle.com kmd->kcontext, opts, "PIN", *krb5_pass);
72312941Swill.fiveash@oracle.com }
72411778SWill.Fiveash@Sun.COM
72512941Swill.fiveash@oracle.com if (!code) {
72612941Swill.fiveash@oracle.com code = __krb5_get_init_creds_password(
72712941Swill.fiveash@oracle.com kmd->kcontext,
72812941Swill.fiveash@oracle.com my_creds,
72912941Swill.fiveash@oracle.com me,
73012941Swill.fiveash@oracle.com NULL, /* clear text passwd */
73112941Swill.fiveash@oracle.com pam_krb5_prompter, /* prompter */
73212941Swill.fiveash@oracle.com pamh, /* prompter data */
73312941Swill.fiveash@oracle.com 0, /* start time */
73412941Swill.fiveash@oracle.com NULL, /* defaults to krbtgt@REALM */
73512941Swill.fiveash@oracle.com opts,
73612941Swill.fiveash@oracle.com &as_reply);
73712941Swill.fiveash@oracle.com }
73811778SWill.Fiveash@Sun.COM } else {
73912941Swill.fiveash@oracle.com /* invalid PIN */
74011778SWill.Fiveash@Sun.COM code = KRB5KRB_AP_ERR_BAD_INTEGRITY;
74111778SWill.Fiveash@Sun.COM }
74211778SWill.Fiveash@Sun.COM } else {
74311778SWill.Fiveash@Sun.COM /*
74411778SWill.Fiveash@Sun.COM * Do password based preauths
74511778SWill.Fiveash@Sun.COM *
74611778SWill.Fiveash@Sun.COM * See earlier PKINIT comment. We are doing something similar
74711778SWill.Fiveash@Sun.COM * here but we do not pass in a prompter (we assume
74811778SWill.Fiveash@Sun.COM * pam_authtok_get has already prompted for that).
74911778SWill.Fiveash@Sun.COM */
75011778SWill.Fiveash@Sun.COM if (*krb5_pass == NULL || strlen(*krb5_pass) == 0) {
75111778SWill.Fiveash@Sun.COM code = KRB5KRB_AP_ERR_BAD_INTEGRITY;
75211778SWill.Fiveash@Sun.COM } else {
75311778SWill.Fiveash@Sun.COM krb5_preauthtype pk_pa_list[] = {
75411778SWill.Fiveash@Sun.COM KRB5_PADATA_ENC_TIMESTAMP
75511778SWill.Fiveash@Sun.COM };
75611778SWill.Fiveash@Sun.COM
75712941Swill.fiveash@oracle.com krb5_get_init_creds_opt_set_preauth_list(opts,
75811778SWill.Fiveash@Sun.COM pk_pa_list, 1);
75911778SWill.Fiveash@Sun.COM
76011778SWill.Fiveash@Sun.COM /*
76111778SWill.Fiveash@Sun.COM * We call our own private version of gic_pwd, because
76211778SWill.Fiveash@Sun.COM * we need more information, such as password/account
76311778SWill.Fiveash@Sun.COM * expiration, that is found in the as_reply. The
76411778SWill.Fiveash@Sun.COM * "prompter" interface is not granular enough for PAM
76511778SWill.Fiveash@Sun.COM * to make use of.
76611778SWill.Fiveash@Sun.COM */
76711778SWill.Fiveash@Sun.COM code = __krb5_get_init_creds_password(kmd->kcontext,
76811778SWill.Fiveash@Sun.COM my_creds,
76911778SWill.Fiveash@Sun.COM me,
77011778SWill.Fiveash@Sun.COM *krb5_pass, /* clear text passwd */
77111778SWill.Fiveash@Sun.COM NULL, /* prompter */
77211778SWill.Fiveash@Sun.COM NULL, /* data */
77311778SWill.Fiveash@Sun.COM 0, /* start time */
77411778SWill.Fiveash@Sun.COM NULL, /* defaults to krbtgt@REALM */
77512941Swill.fiveash@oracle.com opts,
77611778SWill.Fiveash@Sun.COM &as_reply);
77711778SWill.Fiveash@Sun.COM }
7780Sstevel@tonic-gate }
7790Sstevel@tonic-gate
7800Sstevel@tonic-gate if (kmd->debug)
7813391Ssemery __pam_log(LOG_AUTH | LOG_DEBUG,
7820Sstevel@tonic-gate "PAM-KRB5 (auth): attempt_krb5_auth: "
7830Sstevel@tonic-gate "krb5_get_init_creds_password returns: %s",
7840Sstevel@tonic-gate code == 0 ? "SUCCESS" : error_message(code));
7850Sstevel@tonic-gate
7860Sstevel@tonic-gate switch (code) {
7870Sstevel@tonic-gate case 0:
7880Sstevel@tonic-gate /* got a tgt, let's verify it */
7890Sstevel@tonic-gate if (verify_tik) {
7900Sstevel@tonic-gate krb5_verify_init_creds_opt vopts;
7910Sstevel@tonic-gate
7921474Smp153739 krb5_principal sp = NULL;
7931474Smp153739 char kt_name[MAX_KEYTAB_NAME_LEN];
7941474Smp153739 char *fqdn;
7951474Smp153739
7960Sstevel@tonic-gate krb5_verify_init_creds_opt_init(&vopts);
7970Sstevel@tonic-gate
7980Sstevel@tonic-gate code = krb5_verify_init_creds(kmd->kcontext,
79911780SWill.Fiveash@Sun.COM my_creds,
80011780SWill.Fiveash@Sun.COM NULL, /* defaults to host/localhost@REALM */
80111780SWill.Fiveash@Sun.COM NULL,
80211780SWill.Fiveash@Sun.COM NULL,
80311780SWill.Fiveash@Sun.COM &vopts);
8040Sstevel@tonic-gate
8050Sstevel@tonic-gate if (code) {
8060Sstevel@tonic-gate result = PAM_SYSTEM_ERR;
8070Sstevel@tonic-gate
8082554Ssemery /*
8092554Ssemery * Give a better error message when the
8102554Ssemery * keytable entry isn't found or the keytab
8112554Ssemery * file cannot be found.
8122554Ssemery */
8131474Smp153739 if (krb5_sname_to_principal(kmd->kcontext, NULL,
81411780SWill.Fiveash@Sun.COM NULL, KRB5_NT_SRV_HST, &sp))
8151474Smp153739 fqdn = "<fqdn>";
8161474Smp153739 else
8171474Smp153739 fqdn = sp->data[1].data;
8181474Smp153739
8191474Smp153739 if (krb5_kt_default_name(kmd->kcontext, kt_name,
82011780SWill.Fiveash@Sun.COM sizeof (kt_name)))
82111780SWill.Fiveash@Sun.COM (void) strlcpy(kt_name,
82211780SWill.Fiveash@Sun.COM "default keytab",
82311780SWill.Fiveash@Sun.COM sizeof (kt_name));
8241474Smp153739
8251474Smp153739 switch (code) {
8261474Smp153739 case KRB5_KT_NOTFOUND:
8273391Ssemery __pam_log(LOG_AUTH | LOG_ERR,
82811780SWill.Fiveash@Sun.COM "PAM-KRB5 (auth): "
82911780SWill.Fiveash@Sun.COM "krb5_verify_init_creds failed:"
83011780SWill.Fiveash@Sun.COM " Key table entry \"host/%s\""
83111780SWill.Fiveash@Sun.COM " not found in %s",
83211780SWill.Fiveash@Sun.COM fqdn, kt_name);
8331474Smp153739 break;
8341474Smp153739 case ENOENT:
8353391Ssemery __pam_log(LOG_AUTH | LOG_ERR,
83611780SWill.Fiveash@Sun.COM "PAM-KRB5 (auth): "
83711780SWill.Fiveash@Sun.COM "krb5_verify_init_creds failed:"
83811780SWill.Fiveash@Sun.COM " Keytab file \"%s\""
83911780SWill.Fiveash@Sun.COM " does not exist.\n",
84011780SWill.Fiveash@Sun.COM kt_name);
8411474Smp153739 break;
8421474Smp153739 default:
8433391Ssemery __pam_log(LOG_AUTH | LOG_ERR,
84411780SWill.Fiveash@Sun.COM "PAM-KRB5 (auth): "
84511780SWill.Fiveash@Sun.COM "krb5_verify_init_creds failed:"
84611780SWill.Fiveash@Sun.COM " %s",
84711780SWill.Fiveash@Sun.COM error_message(code));
8481474Smp153739 break;
8491474Smp153739 }
8501474Smp153739
8511474Smp153739 if (sp)
8521474Smp153739 krb5_free_principal(kmd->kcontext, sp);
8530Sstevel@tonic-gate }
8540Sstevel@tonic-gate }
8553641Ssemery
8563641Ssemery if (code == 0)
8573641Ssemery kmd->expiration = as_reply->enc_part2->key_exp;
8583641Ssemery
8590Sstevel@tonic-gate break;
8600Sstevel@tonic-gate
8610Sstevel@tonic-gate case KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN:
8620Sstevel@tonic-gate /*
8630Sstevel@tonic-gate * Since this principal is not part of the local
8640Sstevel@tonic-gate * Kerberos realm, we just return PAM_USER_UNKNOWN.
8650Sstevel@tonic-gate */
8660Sstevel@tonic-gate result = PAM_USER_UNKNOWN;
8670Sstevel@tonic-gate
8680Sstevel@tonic-gate if (kmd->debug)
8693391Ssemery __pam_log(LOG_AUTH | LOG_DEBUG,
87011780SWill.Fiveash@Sun.COM "PAM-KRB5 (auth): attempt_krb5_auth:"
87111780SWill.Fiveash@Sun.COM " User is not part of the local Kerberos"
87211780SWill.Fiveash@Sun.COM " realm: %s", error_message(code));
8730Sstevel@tonic-gate break;
8740Sstevel@tonic-gate
8750Sstevel@tonic-gate case KRB5KDC_ERR_PREAUTH_FAILED:
8760Sstevel@tonic-gate case KRB5KRB_AP_ERR_BAD_INTEGRITY:
8770Sstevel@tonic-gate /*
8780Sstevel@tonic-gate * We could be trying the password from a previous
8790Sstevel@tonic-gate * pam authentication module, but we don't want to
8800Sstevel@tonic-gate * generate an error if the unix password is different
8810Sstevel@tonic-gate * than the Kerberos password...
8820Sstevel@tonic-gate */
8830Sstevel@tonic-gate break;
8840Sstevel@tonic-gate
8850Sstevel@tonic-gate case KRB5KDC_ERR_KEY_EXP:
8860Sstevel@tonic-gate if (!kmd->err_on_exp) {
8870Sstevel@tonic-gate /*
88811778SWill.Fiveash@Sun.COM * Request a tik for changepw service and it will tell
88911778SWill.Fiveash@Sun.COM * us if pw is good or not. If PKINIT is being done it
89011778SWill.Fiveash@Sun.COM * is possible that *krb5_pass may be NULL so check for
89111778SWill.Fiveash@Sun.COM * that. If that is the case this function will return
89211778SWill.Fiveash@Sun.COM * an error.
8930Sstevel@tonic-gate */
89411778SWill.Fiveash@Sun.COM if (*krb5_pass != NULL) {
89511778SWill.Fiveash@Sun.COM code = krb5_verifypw(kuser, *krb5_pass,
89611778SWill.Fiveash@Sun.COM kmd->debug);
89711778SWill.Fiveash@Sun.COM if (kmd->debug) {
89811778SWill.Fiveash@Sun.COM __pam_log(LOG_AUTH | LOG_DEBUG,
89911778SWill.Fiveash@Sun.COM "PAM-KRB5 (auth): "
90011778SWill.Fiveash@Sun.COM "attempt_krb5_auth: "
90111778SWill.Fiveash@Sun.COM "verifypw %d", code);
90211778SWill.Fiveash@Sun.COM }
90311778SWill.Fiveash@Sun.COM if (code == 0) {
90411778SWill.Fiveash@Sun.COM /*
90511778SWill.Fiveash@Sun.COM * pw is good, set age status for
90611778SWill.Fiveash@Sun.COM * acct_mgmt.
90711778SWill.Fiveash@Sun.COM */
90811778SWill.Fiveash@Sun.COM kmd->age_status = PAM_NEW_AUTHTOK_REQD;
90911778SWill.Fiveash@Sun.COM }
91011778SWill.Fiveash@Sun.COM }
9110Sstevel@tonic-gate
9120Sstevel@tonic-gate }
9130Sstevel@tonic-gate break;
9140Sstevel@tonic-gate
9150Sstevel@tonic-gate default:
9160Sstevel@tonic-gate result = PAM_SYSTEM_ERR;
9170Sstevel@tonic-gate if (kmd->debug)
9183391Ssemery __pam_log(LOG_AUTH | LOG_DEBUG,
91911780SWill.Fiveash@Sun.COM "PAM-KRB5 (auth): error %d - %s",
92011780SWill.Fiveash@Sun.COM code, error_message(code));
9210Sstevel@tonic-gate break;
9220Sstevel@tonic-gate }
9230Sstevel@tonic-gate
9240Sstevel@tonic-gate if (code == 0) {
9250Sstevel@tonic-gate /*
92611778SWill.Fiveash@Sun.COM * success for the entered pw or PKINIT succeeded.
9270Sstevel@tonic-gate *
9280Sstevel@tonic-gate * we can't rely on the pw in PAM_AUTHTOK
9290Sstevel@tonic-gate * to be the (correct) krb5 one so
9300Sstevel@tonic-gate * store krb5 pw in module data for
93111778SWill.Fiveash@Sun.COM * use in acct_mgmt. Note that *krb5_pass may be NULL if we're
93211778SWill.Fiveash@Sun.COM * doing PKINIT.
9330Sstevel@tonic-gate */
93411778SWill.Fiveash@Sun.COM if (*krb5_pass != NULL &&
93511778SWill.Fiveash@Sun.COM !(kmd->password = strdup(*krb5_pass))) {
93611778SWill.Fiveash@Sun.COM __pam_log(LOG_AUTH | LOG_ERR,
93711780SWill.Fiveash@Sun.COM "Cannot strdup password");
9380Sstevel@tonic-gate result = PAM_BUF_ERR;
9390Sstevel@tonic-gate goto out_err;
9400Sstevel@tonic-gate }
94111778SWill.Fiveash@Sun.COM
9420Sstevel@tonic-gate result = PAM_SUCCESS;
9430Sstevel@tonic-gate goto out;
9440Sstevel@tonic-gate }
9450Sstevel@tonic-gate
9460Sstevel@tonic-gate out_err:
9470Sstevel@tonic-gate /* jump (or reach) here if error and cred cache has been init */
9480Sstevel@tonic-gate
9490Sstevel@tonic-gate if (kmd->debug)
9503391Ssemery __pam_log(LOG_AUTH | LOG_DEBUG,
9510Sstevel@tonic-gate "PAM-KRB5 (auth): clearing initcreds in "
9520Sstevel@tonic-gate "pam_authenticate()");
9530Sstevel@tonic-gate
9540Sstevel@tonic-gate krb5_free_cred_contents(kmd->kcontext, &kmd->initcreds);
9550Sstevel@tonic-gate (void) memset((char *)&kmd->initcreds, 0, sizeof (krb5_creds));
9560Sstevel@tonic-gate
9570Sstevel@tonic-gate out:
9580Sstevel@tonic-gate if (server)
9590Sstevel@tonic-gate krb5_free_principal(kmd->kcontext, server);
9600Sstevel@tonic-gate if (me)
9610Sstevel@tonic-gate krb5_free_principal(kmd->kcontext, me);
9623641Ssemery if (as_reply)
9633641Ssemery krb5_free_kdc_rep(kmd->kcontext, as_reply);
9645201Ssemery
9655201Ssemery /*
9665201Ssemery * clientp or serverp could be NULL in certain error cases in this
9675201Ssemery * function. mycreds->[client|server] could also be NULL in case
9685201Ssemery * of error in this function, see out_err above. The pointers clientp
9695201Ssemery * and serverp reference the input argument in my_creds for
9705201Ssemery * get_init_creds and must be freed if the input argument does not
9715201Ssemery * match the output argument, which occurs during a successful call
9725201Ssemery * to get_init_creds.
9735201Ssemery */
9745201Ssemery if (clientp && my_creds->client && clientp != my_creds->client)
9755201Ssemery krb5_free_principal(kmd->kcontext, clientp);
9765201Ssemery if (serverp && my_creds->server && serverp != my_creds->server)
9775201Ssemery krb5_free_principal(kmd->kcontext, serverp);
9785201Ssemery
9790Sstevel@tonic-gate if (kmd->kcontext) {
9800Sstevel@tonic-gate krb5_free_context(kmd->kcontext);
9810Sstevel@tonic-gate kmd->kcontext = NULL;
9820Sstevel@tonic-gate }
98312941Swill.fiveash@oracle.com if (opts)
98412941Swill.fiveash@oracle.com krb5_get_init_creds_opt_free(kmd->kcontext, opts);
9850Sstevel@tonic-gate
9860Sstevel@tonic-gate if (kmd->debug)
9873391Ssemery __pam_log(LOG_AUTH | LOG_DEBUG,
9880Sstevel@tonic-gate "PAM-KRB5 (auth): attempt_krb5_auth returning %d",
9890Sstevel@tonic-gate result);
9900Sstevel@tonic-gate
9910Sstevel@tonic-gate return (kmd->auth_status = result);
9920Sstevel@tonic-gate }
9930Sstevel@tonic-gate
9940Sstevel@tonic-gate /*ARGSUSED*/
9950Sstevel@tonic-gate void
krb5_cleanup(pam_handle_t * pamh,void * data,int pam_status)9960Sstevel@tonic-gate krb5_cleanup(pam_handle_t *pamh, void *data, int pam_status)
9970Sstevel@tonic-gate {
9980Sstevel@tonic-gate krb5_module_data_t *kmd = (krb5_module_data_t *)data;
9990Sstevel@tonic-gate
10000Sstevel@tonic-gate if (kmd == NULL)
10010Sstevel@tonic-gate return;
10020Sstevel@tonic-gate
10030Sstevel@tonic-gate if (kmd->debug) {
10043391Ssemery __pam_log(LOG_AUTH | LOG_DEBUG,
100511780SWill.Fiveash@Sun.COM "PAM-KRB5 (auth): krb5_cleanup auth_status = %d",
10060Sstevel@tonic-gate kmd->auth_status);
10070Sstevel@tonic-gate }
10080Sstevel@tonic-gate
10090Sstevel@tonic-gate /*
10105201Ssemery * Apps could be calling pam_end here, so we should always clean
10115201Ssemery * up regardless of success or failure here.
10120Sstevel@tonic-gate */
10135201Ssemery if (kmd->ccache)
101411411SSurya.Prakki@Sun.COM (void) krb5_cc_close(kmd->kcontext, kmd->ccache);
10150Sstevel@tonic-gate
10160Sstevel@tonic-gate if (kmd->password) {
10170Sstevel@tonic-gate (void) memset(kmd->password, 0, strlen(kmd->password));
10180Sstevel@tonic-gate free(kmd->password);
10190Sstevel@tonic-gate }
10200Sstevel@tonic-gate
10215201Ssemery if (kmd->user)
10223391Ssemery free(kmd->user);
10233391Ssemery
10245201Ssemery if (kmd->env)
10255201Ssemery free(kmd->env);
10265201Ssemery
10275201Ssemery krb5_free_cred_contents(kmd->kcontext, &kmd->initcreds);
10285201Ssemery (void) memset((char *)&kmd->initcreds, 0, sizeof (krb5_creds));
10290Sstevel@tonic-gate
10300Sstevel@tonic-gate free(kmd);
10310Sstevel@tonic-gate }
1032