From d19c6a85dad0e7fdbabceef1f6df324be2c6102c Mon Sep 17 00:00:00 2001 From: Nalin Dahyabhai Date: Oct 19 2000 03:44:37 +0000 Subject: - fix prompting when the module's first in the stack and the user does not have a corresponding principal in the local realm - properly implement TGT verification - change a few non-error status messages into debugging messages - sync the README and the various man pages up --- diff --git a/README b/README index c0d7612..343f92e 100644 --- a/README +++ b/README @@ -7,16 +7,14 @@ It implements authentication, session management, and password-changing functions. Sample configuration files for many services are included. The pam_sm_authenticate() function checks the user name and password in -the user's realm. It takes the standard parameters "debug", "try_first_pass", -and "use_first_pass", as required by the PAM documentation, and -"skip_first_pass" for completeness. +the user's realm. It takes the standard parameters required by the PAM +documentation, as well as a few others listed below. With no arguments, the pam_sm_authenticate() function defaults to "try_first_pass" mode. The TGT obtained and is saved for later use by -the pam_sm_setcred() function, but the TGT is NOT stored on disk. If -the name of a critical TGS is give in the krb5.conf configuration file -(described below), the new TGT is used to obtain a service key for it to -verify that the TGT wasn't coming from a spoofed KDC. +the pam_sm_setcred() function, but the TGT is NOT stored on disk. The +new TGT is verified using a copy of the key for the local workstation's +host service if it is found in the local keytab file. The pam_sm_setcred() function creates a Kerberos 5 ticket file and, if libkrb524 was found at compile-time, can obtain and create a Kerberos 4 @@ -33,11 +31,11 @@ Because session-specific ticket files require that the KRBTKFILE and KRB5CCNAME environment variables are set correctly, certain programs that create their own environments but don't incorporate the results of pam_getenvlist() will work, but a user running 'klist' will think that -she has no tickets. Currently, this includes sshd and unpatched versions -of every display manager known except gdm2. +she has no tickets. Currently, this includes ssh (openssh works) and +unpatched versions of every display manager known except gdm2 and newer. Certain settings for the module are now stored in the krb5.conf file, which -is usually stored under /etc. The section name is "pam": +is usually stored in /etc. The section name is "pam": [pam] debug = true @@ -61,6 +59,15 @@ Descriptions of the configuration file directives read by both modules: that this is only supported by the pam_krb5afs module. hosts Hosts this ticket will also be good for, in addition to this one. Primarily for use behind firewalls. + ccache_dir The directory to store ccache files in. The default is + to use /tmp, but some people prefer /var/tmp. + banner What the module should announce itself as when changing + passwords. Defaults to "Kerberos 5". + keytab The name of a keytab file to use for TGT verification. + The default is "/etc/krb5.keytab". + required_tgs The name of a service principal (with its key in the + given keytab file) which is to be used to verify TGTs. + The default is "host/". Descriptions of configuration directives for use in /etc/pam.d: use_first_pass Use password obtained by a previous module. @@ -75,8 +82,16 @@ Descriptions of configuration directives for use in /etc/pam.d: Samba, and some other programs that don't use sessions and don't call pam_setcred, but need tokens. Note that this is only supported by the pam_krb5afs module. + use_authtok Rely on tokens input by a previous module in the stack + when changing passwords. Primarily used if you're using + pam_cracklib to screen out weak passwords. + no_user_check Don't bother checking if the login account corresponding + to the principal exists or not, and use UID the service + is executing under as the owner for any ccache files + which get created. + no_warn Ignored. -This module was built and tested against MIT Kerberos 5 v1.2, but it should +This module was built and tested against MIT Kerberos 5 v1.2.1, but it should only require v1.1.x. Caveat: pam_pwdb will cause things to fail if your user information isn't stored @@ -86,4 +101,4 @@ that happens. Let me know if you have problems, Nalin Dahyabhai -27 June 2000 +18 October 2000 diff --git a/TODO b/TODO index e69de29..3479036 100644 --- a/TODO +++ b/TODO @@ -0,0 +1 @@ +* Maybe use krb5_aname_to_localname to do principal/login mapping? diff --git a/pam_krb5.5 b/pam_krb5.5 index 3446813..e165c73 100644 --- a/pam_krb5.5 +++ b/pam_krb5.5 @@ -1,6 +1,6 @@ -.TH pam_krb5 5 2000/05/20 "Red Hat Linux" "System Administrator's Manual" +.TH pam_krb5 5 2000/10/18 "Red Hat Linux" "System Administrator's Manual" .SH NAME -pam_krb5 \- Kerberos 5 authentication +pam_krb5 \- Kerberos 5 authentication with AFS support .SH DESCRIPTION pam_krb5.so uses a portion of \fBkrb5.conf\fR to get its configuration information. You should read the \fBkrb5.conf(5)\fR man page before continuing @@ -31,13 +31,19 @@ specifies which other hosts credentials obtained by pam_krb5 will be good on. If your host is behind a firewall, you should add the IP address or name that the \fIKDC\fR sees it as to this list. .IP required_tgs -specifies a principal for which a user must be able to get a TGS for in order -to be allowed access. This is the only certain way to be absolutely sure the -TGT hasn't been forged, and should always be used. This test is disabled by -default because its value is site-specific. +specifies a principal for which a user must be able to get a session key for for +the purpose of verifying that the TGT has not been forged. The key is +decrypted using a copy of the service's key stored in a local keytab file. +This is the only certain way to be absolutely sure the TGT hasn't been forged. +.IP keytab +specifies the name of a keytab file to find a key for the \fBrequired_tgs\fP in, +for use in verifying TGTs. The default is \fB/etc/krb5.keytab\fP. .IP ccache_dir specifies the directory to place credential cache files in. The default is \fB/tmp\fR. +.IP banner +specifies what kind of password the module claims to be changing when called +to change passwords. The default is \fBKerberos 5\fP. .SH EXAMPLE @@ -48,7 +54,7 @@ specifies the directory to place credential cache files in. The default is forwardable = true krb4_convert = true hosts = thermo.example.edu alf.example.edu - required_tgs = zephyr/zephyr + required_tgs = host/thermo.example.edu ccache_dir = /var/tmp .SH FILES diff --git a/pam_krb5.8 b/pam_krb5.8 index 5ccd603..b68d761 100644 --- a/pam_krb5.8 +++ b/pam_krb5.8 @@ -18,7 +18,7 @@ password check and, if possible, obtains Kerberos 5 and Kerberos IV credentials, caching them for later use. When the application requests initialization of credentials (or opens a session), the usual ticket files are created. When the application subsequently requests deletion of -credentials or closing of the session, the module destroys the ticket files. +credentials or closing of the session, the module deletes the ticket files. .SH ARGUMENTS .IP debug @@ -35,6 +35,10 @@ is the default mode of operation. tells pam_krb5.so to not bother checking a password that has been set by a module listed earlier in the stack. This option is included mainly for completeness. +.IP use_authtok +tells pam_krb5.so to never prompt for passwords when changing passwords. +This is useful if you are using pam_cracklib.so to try to enforce use of +less-easy-to-guess passwords. .IP no_user_check tells pam_krb5.so to not check if a user exists on the local system, and to create ccache files owned by the current process's UID. This is useful diff --git a/pam_krb5.spec b/pam_krb5.spec index e87894e..819bd4b 100644 --- a/pam_krb5.spec +++ b/pam_krb5.spec @@ -39,8 +39,11 @@ make install DESTDIR=$RPM_BUILD_ROOT mandir=%{_mandir} %changelog * Wed Oct 18 2000 Nalin Dahyabhai -- fix prompting when the module's first in the stack -- fix a typo in a comment +- fix prompting when the module's first in the stack and the user does not have + a corresponding principal in the local realm +- properly implement TGT verification +- change a few non-error status messages into debugging messages +- sync the README and the various man pages up * Mon Oct 2 2000 Nalin Dahyabhai - fix "use_authtok" logic when password was not set by previous module diff --git a/pam_krb5afs.5 b/pam_krb5afs.5 index 9fc763d..948b839 100644 --- a/pam_krb5afs.5 +++ b/pam_krb5afs.5 @@ -1,4 +1,4 @@ -.TH pam_krb5afs 5 2000/05/20 "Red Hat Linux" "System Administrator's Manual" +.TH pam_krb5afs 5 2000/10/18 "Red Hat Linux" "System Administrator's Manual" .SH NAME pam_krb5afs \- Kerberos 5 authentication with AFS support .SH DESCRIPTION @@ -34,13 +34,19 @@ specifies which other hosts credentials obtained by pam_krb5afs will be good on. If your host is behind a firewall, you should add the IP address or name that the \fIKDC\fR sees it as to this list. .IP required_tgs -specifies a principal for which a user must be able to get a TGS for in order -to be allowed access. This is the only certain way to be absolutely sure the -TGT hasn't been forged, and should always be used. This test is disabled by -default because its value is site-specific. +specifies a principal for which a user must be able to get a key for for +the purpose of verifying that the TGT has not been forged. The key is +decrypted using a copy of the service's key stored in a local keytab file. +This is the only certain way to be absolutely sure the TGT hasn't been forged. +.IP keytab +specifies the name of a keytab file to find a key for the \fBrequired_tgs\fP in, +for use in verifying TGTs. The default is \fB/etc/krb5.keytab\fP. .IP ccache_dir specifies the directory to place credential cache files in. The default is \fB/tmp\fR. +.IP banner +specifies what kind of password the module claims to be changing when called +to change passwords. The default is \fBKerberos 5\fP. .SH EXAMPLE @@ -52,7 +58,7 @@ specifies the directory to place credential cache files in. The default is krb4_convert = true afs_cells = transarc.com foo.example.edu hosts = thermo.example.edu alf.example.edu - required_tgs = zephyr/zephyr + required_tgs = host/thermo.example.edu ccache_dir = /var/tmp .SH FILES diff --git a/pam_krb5afs.8 b/pam_krb5afs.8 index 30fc618..d70df2f 100644 --- a/pam_krb5afs.8 +++ b/pam_krb5afs.8 @@ -41,6 +41,10 @@ is the default mode of operation. tells pam_krb5afs.so to not bother checking a password that has been set by a module listed earlier in the stack. This option is included mainly for completeness. +.IP use_authtok +tells pam_krb5afs.so to never prompt for passwords when changing passwords. +This is useful if you are using pam_cracklib.so to try to enforce use of +less-easy-to-guess passwords. .IP no_user_check tells pam_krb5afs.so to not check if a user exists on the local system, and to create ccache files owned by the current process's UID. This is useful diff --git a/pam_krb5afs.c b/pam_krb5afs.c index b4c56ae..20934b6 100644 --- a/pam_krb5afs.c +++ b/pam_krb5afs.c @@ -138,6 +138,8 @@ #define PROFILE_NAME "pam" #define DEFAULT_CELLS "eos.ncsu.edu unity.ncsu.edu bp.ncsu.edu" +#define DEFAULT_SERVICE "host" +#define DEFAULT_KEYTAB "/etc/krb5.keytab" #define DEFAULT_LIFE 36000 #define DEFAULT_TKT_DIR "/tmp" @@ -205,6 +207,7 @@ struct config { char *realm; char *required_tgs; char *ccache_dir; + char *keytab; }; static void dEBUG(const char *x,...) { @@ -294,17 +297,23 @@ static struct config *get_config(krb5_context context, int argc, const char **argv) { int i, j; - struct config *ret = NULL; - char *foo, *cells; + struct config *ret = NULL, *config; + char *foo, *hosts; +#ifdef AFS + char *cells; +#endif profile_t profile; krb5_address **addresses = NULL; krb5_address **hostlist; + char tgsname[LINE_MAX] = DEFAULT_SERVICE "/"; /* Defaults: try everything (try_first_pass, use a PAG, no debug). */ ret = malloc(sizeof(struct config)); if(ret == NULL) { return NULL; } + config = ret; + memset(ret, 0, sizeof(struct config)); krb5_get_init_creds_opt_init(&ret->creds_opt); ret->try_first_pass = 1; @@ -322,8 +331,7 @@ static struct config *get_config(krb5_context context, ret->debug = 1; } } - if(ret->debug) - dEBUG("get_config() called"); + DEBUG("get_config() called"); /* The local realm. */ krb5_get_default_realm(context, &ret->realm); @@ -331,37 +339,35 @@ static struct config *get_config(krb5_context context, /* Ticket lifetime and other flags. */ profile_get_integer(profile, PROFILE_NAME, "renew_lifetime", NULL, DEFAULT_LIFE, &ret->lifetime); + DEBUG("setting renewable lifetime to %d", ret->lifetime); krb5_get_init_creds_opt_set_renew_life(&ret->creds_opt, ret->lifetime); - if(ret->debug) - dEBUG("setting renewable lifetime to %d", ret->lifetime); + profile_get_integer(profile, PROFILE_NAME, "ticket_lifetime", NULL, DEFAULT_LIFE, &ret->lifetime); + DEBUG("setting ticket lifetime to %d", ret->lifetime); krb5_get_init_creds_opt_set_tkt_life(&ret->creds_opt, ret->lifetime); - if(ret->debug) - dEBUG("setting ticket lifetime to %d", ret->lifetime); + profile_get_string(profile, PROFILE_NAME, "forwardable", NULL, DEFAULT_CELLS, &foo); if(!strcmp(foo, "true")) { - if(ret->debug) - dEBUG("making tickets forwardable"); + DEBUG("making tickets forwardable"); krb5_get_init_creds_opt_set_forwardable(&ret->creds_opt, TRUE); } /* Hosts to get tickets for. */ profile_get_string(profile, PROFILE_NAME, "hosts", NULL, - "", &cells); + "", &hosts); krb5_os_localaddr(context, &hostlist); for(j = 0; hostlist[j] != NULL; j++) ; - addresses = malloc(sizeof(krb5_address) * (num_words(cells) + 1 + j)); - memset(addresses, 0, sizeof(krb5_address) * (num_words(cells) + 1 + j)); + addresses = malloc(sizeof(krb5_address) * (num_words(hosts) + 1 + j)); + memset(addresses, 0, sizeof(krb5_address) * (num_words(hosts) + 1 + j)); for(j = 0; hostlist[j] != NULL; j++) { addresses[j] = hostlist[j]; } - for(i = 0; i < num_words(cells); i++) { - foo = word_copy(nth_word(cells, i)); + for(i = 0; i < num_words(hosts); i++) { + foo = word_copy(nth_word(hosts, i)); krb5_os_hostaddr(context, foo, &hostlist); - if(ret->debug) - dEBUG("also getting ticket for host %s", foo); + DEBUG("also getting ticket for host %s", foo); addresses[i + j] = hostlist[0]; } krb5_get_init_creds_opt_set_address_list(&ret->creds_opt, addresses); @@ -369,21 +375,18 @@ static struct config *get_config(krb5_context context, /* Which directory to put ticket files in. */ profile_get_string(profile, PROFILE_NAME, "ccache_dir", NULL, DEFAULT_TKT_DIR, &ret->ccache_dir); - if(ret->debug) - dEBUG("ticket directory is \"%s\"", ret->ccache_dir); + DEBUG("ticket directory set to \"%s\"", ret->ccache_dir); /* What to say we are when changing passwords. */ profile_get_string(profile, PROFILE_NAME, "banner", NULL, "Kerberos 5", &ret->banner); - if(ret->debug) - dEBUG("password-changing banner set to \"%s\"", ret->banner); + DEBUG("password-changing banner set to \"%s\"", ret->banner); /* Whether to get krb4 tickets using krb524convertcreds(). */ profile_get_string(profile, PROFILE_NAME, "krb4_convert", NULL, "true", &foo); if(!strcmp(foo, "true")) ret->krb4_convert = TRUE; - if(ret->debug) - dEBUG("krb4_convert %s", ret->krb4_convert ? "true" : "false"); + DEBUG("krb4_convert %s", ret->krb4_convert ? "true" : "false"); #ifdef AFS /* Cells to get tokens for. */ @@ -393,23 +396,30 @@ static struct config *get_config(krb5_context context, memset(ret->cell_list, 0, sizeof(char*) * (num_words(cells) + 1)); for(i = 0; i < num_words(cells); i++) { ret->cell_list[i] = word_copy(nth_word(cells, i)); - if(ret->debug) { - dEBUG("will afslog to cell %s", ret->cell_list[i]); - } + DEBUG("will afslog to cell %s", ret->cell_list[i]); if(ret->krb4_convert != TRUE) { ret->krb4_convert = TRUE; - if(ret->debug) { - dEBUG("krb4_convert forced on"); - } + DEBUG("krb4_convert forced on"); } } ret->get_tokens = TRUE; #endif - /* Get the name of a service ticket the user must be able to obtain, - as a double-check. */ + /* Get the name of a service ticket the user must be able to obtain and + * a keytab with the key for the service in it which we can use to + * decrypt the credential to make sure the KDC's response wasn't + * spoofed. This is an undocumented way to do it, but it's what people + * do if they need to verify the TGT. */ + if(gethostname(tgsname + strlen(tgsname), + sizeof(tgsname) - strlen(tgsname) - 1) == -1) { + memset(&tgsname, 0, sizeof(tgsname)); + } profile_get_string(profile, PROFILE_NAME, "required_tgs", - NULL, "", &ret->required_tgs); + NULL, tgsname, &ret->required_tgs); + DEBUG("required_tgs set to \"%s\"", ret->required_tgs); + profile_get_string(profile, PROFILE_NAME, "keytab", + NULL, DEFAULT_KEYTAB, &ret->keytab); + DEBUG("keytab file name set to \"%s\"", ret->keytab); for(i = 0; i < argc; i++) { /* Required argument that we don't use but need to recognize.*/ @@ -507,7 +517,6 @@ static int pam_prompter(krb5_context context, void *data, const char *name, { int i = 0, ret = PAM_SUCCESS; const char *p = NULL; - dEBUG("pam_prompter() called for %d items", num_prompts); for(i = 0; i < num_prompts; i++) { char *q = NULL; int l = strlen(prompts[i].prompt) + strlen(": ") + 1; @@ -531,13 +540,114 @@ static int pam_prompter(krb5_context context, void *data, const char *name, return ret; } +/* Validate the TGT in stash->v5_creds using the keytab and required_tgs + * set in config. Return zero only if validation fails, required_tgs is + * set, and we can read the keytab file. */ +static int verify_tgt(const char *user, krb5_context context, + struct config *config, struct stash *stash) +{ + krb5_keytab keytab; + krb5_keytab_entry entry; + krb5_principal server; + krb5_creds st, *tgs; + krb5_ticket *ticket; + krb5_error_code ret; + struct stat buf; + + /* The only non-fatal errors. */ + if((config->required_tgs == NULL) || + (strlen(config->required_tgs) == 0)) { + DEBUG("TGT not verified because required_tgs was not set"); + return 1; + } + if((stat(config->keytab, &buf) == -1) && (errno == ENOENT)) { + DEBUG("TGT not verified because keytab file %s doesn't exist", + config->keytab); + return 1; + } + + /* Parse out the service name into a principal. */ + DEBUG("verifying TGT"); + ret = krb5_parse_name(context, config->required_tgs, &server); + if(ret) { + CRIT("error building service principal for %s: %s", + config->required_tgs, error_message(ret)); + return 0; + } + + /* Try to get a service ticket. */ + memset(&st, 0, sizeof(st)); + st.client = stash->v5_creds.client; + st.server = server; + ret = krb5_get_cred_via_tkt(context, &stash->v5_creds, 0, NULL, + &st, &tgs); + if(ret) { + CRIT("error getting credential for %s: %s", + config->required_tgs, error_message(ret)); + krb5_free_principal(context, server); + return 0; + } + + /* Decode the service information from the ticket. */ + ret = krb5_decode_ticket(&tgs->ticket, &ticket); + if(ret) { + CRIT("error decoding key information for %s: %s", + config->required_tgs, error_message(ret)); + krb5_free_principal(context, server); + krb5_free_creds(context, tgs); + return 0; + } + + /* Open the keytab. */ + ret = krb5_kt_resolve(context, config->keytab, &keytab); + if(ret) { + DEBUG("error trying to open %s: %s", + config->keytab, error_message(ret)); + krb5_free_principal(context, server); + krb5_free_creds(context, tgs); + krb5_free_ticket(context, ticket); + return 0; + } + + /* Read the key for the service. */ + ret = krb5_kt_get_entry(context, keytab, server, + ticket->enc_part.kvno, + ticket->enc_part.enctype, + &entry); + if(ret) { + CRIT("error reading keys for %s from %s: %s", + config->required_tgs, config->keytab, error_message(ret)); + krb5_free_principal(context, server); + krb5_free_creds(context, tgs); + krb5_free_ticket(context, ticket); + krb5_kt_close(context, keytab); + return 0; + } + + /* Try to decrypt the encrypted part with the key. */ + ret = krb5_decrypt_tkt_part(context, &entry.key, ticket); + if(ret) { + CRIT("verification error: %s", + error_message(ret)); + } else { + INFO("TGT for %s successfully verified", user); + } + + krb5_free_principal(context, server); + krb5_free_creds(context, tgs); + krb5_free_ticket(context, ticket); + krb5_kt_close(context, keytab); + krb5_kt_free_entry(context, &entry); + + return !ret; +} + /* Big authentication module. */ int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) { krb5_context context; krb5_principal principal; - krb5_principal tgs; struct config *config; const char *user = NULL; const char *password = NULL; @@ -638,7 +748,7 @@ int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, } /* Now try to get a TGT using the password, prompting the user if it - fails and we're allowed to prompt. */ + fails and we're allowed to prompt for one. */ if(ret == KRB5_SUCCESS) { int done = 0; @@ -673,10 +783,17 @@ int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, /* Try to converse if the password failed. */ if(config->try_second_pass && !done) { + password = NULL; + ret = pam_prompt_for(pamh, PAM_PROMPT_ECHO_OFF, + "Password: ", &password); + if(password) { + pam_set_item(pamh, PAM_AUTHTOK, + strdup(password)); + } ret = krb5_get_init_creds_password(context, &stash->v5_creds, principal, - NULL, + (char*)password, pam_prompter, pamh, 0, @@ -696,48 +813,18 @@ int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, } } + /* Verify that the TGT is good (i.e., that the reply wasn't spoofed). */ + if(verify_tgt(user, context, config, stash) == 0) { + ret = PAM_AUTH_ERR; + } + + /* Log something. */ if(ret == KRB5_SUCCESS) { INFO("authentication succeeds for %s", user); } else { INFO("authentication fails for %s", user); } - /* Build a principal for the service credential we'll use for double- - checking the validity of the TGT. */ - if((ret == KRB5_SUCCESS) && (config->required_tgs != NULL) && - (strlen(config->required_tgs) > 0)) { - ret = krb5_parse_name(context, config->required_tgs, &tgs); - if(ret != KRB5_SUCCESS) { - CRIT("%s building principal for %s", - error_message(ret), config->required_tgs); - ret = PAM_SYSTEM_ERR; - } - - /* Attempt to use our new TGT to obtain a service ticket. */ - if(ret == KRB5_SUCCESS) { - krb5_creds creds, *out_creds; - memset(&creds, 0, sizeof(creds)); - creds.client = stash->v5_creds.client; - creds.server = tgs; - ret = krb5_get_cred_via_tkt(context, - &stash->v5_creds, - 0, - NULL, - &creds, - &out_creds); - if(ret == KRB5_SUCCESS) { - INFO("TGT for %s verifies", user); - } else { - CRIT("TGT for %s was useless (%s)", - user, error_message(ret)); - ret = PAM_SYSTEM_ERR; - } - } - } else { - INFO("TGT for %s not verified (no required_tgs " - "defined)", user); - } - if(ret == PAM_SUCCESS) { ret = pam_set_data(pamh, MODULE_STASH_NAME, stash, cleanup); DEBUG("credentials saved for %s", user); @@ -775,8 +862,8 @@ int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, config->lifetime / 60 / 5, NULL, 0, ciphertext); if(rc != KSUCCESS) { - INFO("Couldn't get v4 TGT for %s%s@%s (%s), " - "continuing.", v4name, + INFO("couldn't get v4 TGT for %s%s@%s (%s), " + "continuing", v4name, strlen(v4inst) ? ".": "", v4inst, v4realm, krb_get_err_text(rc)); } @@ -930,8 +1017,8 @@ int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, /* Done with Kerberos. */ krb5_free_context(context); + /* Save the return code for later use by setcred(). */ *pret = ret; - ret = pam_set_data(pamh, MODULE_RET_NAME, pret, cleanup); if(ret == PAM_SUCCESS) { DEBUG("saved return code (%d) for later use", *pret); @@ -1175,7 +1262,10 @@ int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) #ifdef AFS /* Use the new tickets to create tokens. */ - if((ret == PAM_SUCCESS) && config->get_tokens && config->cell_list) { + if((flags & PAM_ESTABLISH_CRED) && + (ret == KRB5_SUCCESS) && + config->get_tokens && + config->cell_list) { if(!k_hasafs()) { CRIT("cells specified but AFS not running"); ret = PAM_SYSTEM_ERR; @@ -1197,7 +1287,7 @@ int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) if((ret == PAM_SUCCESS) && (strlen(stash->v5_path) > 0)) { DEBUG("credentials retrieved"); /* Delete the v5 ticket cache. */ - INFO("removing %s", stash->v5_path); + DEBUG("removing %s", stash->v5_path); if(remove(stash->v5_path) == -1) { CRIT("error removing file %s: %s", stash->v5_path, strerror(errno)); @@ -1206,7 +1296,7 @@ int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) #ifdef HAVE_LIBKRB4 if((ret == PAM_SUCCESS) && (strlen(stash->v4_path) > 0)) { /* Delete the v4 ticket cache. */ - INFO("removing %s", stash->v4_path); + DEBUG("removing %s", stash->v4_path); if(remove(stash->v4_path) == -1) { CRIT("error removing file %s: %s", stash->v4_path, strerror(errno)); @@ -1216,7 +1306,7 @@ int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) #ifdef AFS /* Clear tokens unless we need them. */ if(!config->setcred && k_hasafs()) { - INFO("destroying tokens"); + DEBUG("destroying tokens"); k_unlog(); } #endif diff --git a/validate.c b/validate.c new file mode 100644 index 0000000..36bb47f --- /dev/null +++ b/validate.c @@ -0,0 +1,112 @@ +/* Sample validation code for debugging. */ + +#include +#include +#include + +int main(int argc, char **argv) +{ + char person[] = "jrandomuser@EXAMPLE.COM"; + char tgtname[] = "krbtgt/EXAMPLE.COM@EXAMPLE.COM"; + char service[] = "host/randomhost.example.com@EXAMPLE.COM"; + char password[LINE_MAX] = "bubbubba"; + char *unparsed; + krb5_principal client, tgtprinc, server; + krb5_creds tgt, st, *tgts; + krb5_ticket *ticket; + krb5_keytab keytab; + krb5_keytab_entry entry; + krb5_context context; + int ret; + + initialize_krb5_error_table(); + + ret = krb5_init_context(&context); + if(ret) { + fprintf(stderr, "Error in krb5_init_context().\n"); + return 1; + } + + ret = krb5_parse_name(context, person, &client); + if(ret) { + fprintf(stderr, "Error in krb5_parse_name().\n"); + return 1; + } + + ret = krb5_parse_name(context, tgtname, &tgtprinc); + if(ret) { + fprintf(stderr, "Error in krb5_parse_name().\n"); + return 1; + } + + ret = krb5_parse_name(context, service, &server); + if(ret) { + fprintf(stderr, "Error in krb5_parse_name().\n"); + return 1; + } + + memset(&tgt, 0, sizeof(tgt)); + tgt.client = client; + tgt.server = tgtprinc; + + ret = krb5_get_init_creds_password(context, &tgt, client, password, + NULL, NULL, 0, tgtname, NULL); + if(ret) { + fprintf(stderr, "Error in get_init_creds().\n"); + return 1; + } + + memset(&st, 0, sizeof(st)); + st.client = client; + st.server = server; + + ret = krb5_get_cred_via_tkt(context, &tgt, 0, NULL, &st, &tgts); + if(ret) { + fprintf(stderr, "Error in get_credentials(): %s.\n", + error_message(ret)); + return 1; + } + + ret = krb5_unparse_name(context, tgts[0].server, &unparsed); + if(ret) { + fprintf(stderr, "Error in unparse_name(): %s.\n", + error_message(ret)); + return 1; + } else { + fprintf(stderr, "Got cred for \"%s\".\n", unparsed); + } + + ret = krb5_kt_resolve(context, "/etc/krb5.keytab", &keytab); + if(ret) { + fprintf(stderr, "Error in kt_resolve(): %s.\n", + error_message(ret)); + return 1; + } + + ret = krb5_decode_ticket(&tgts[0].ticket, &ticket); + if(ret) { + fprintf(stderr, "Error in decode_ticket(): %s.\n", + error_message(ret)); + } + + fprintf(stderr, "kvno = %d.\n", ticket->enc_part.kvno); + + ret = krb5_kt_get_entry(context, keytab, server, ticket->enc_part.kvno, + ticket->enc_part.enctype, &entry); + if(ret) { + fprintf(stderr, "Error in get_entry(): %s.\n", + error_message(ret)); + return 1; + } + +#ifdef BREAK_VALIDATION + entry.key.contents[0] = 5; +#endif + ret = krb5_decrypt_tkt_part(context, &entry.key, ticket); + if(ret) { + fprintf(stderr, "Error in decrypt(): %s.\n", + error_message(ret)); + } + + return 0; +}