From: Tom Yu Date: Sat, 6 Dec 1997 08:00:17 +0000 (+0000) Subject: * Makefile.in: Add files chpw.c, gic_*, preauth2.c, vfy_increds.c, X-Git-Tag: krb5-1.1-beta1~925 X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=32ab452e321b0d79d788c41c1b81bc039f9fe7fb;p=krb5.git * Makefile.in: Add files chpw.c, gic_*, preauth2.c, vfy_increds.c, vic_opt.c. * chpw.c: New file; implement Cygnus chpw. * get_in_tkt.c: Implement support for Cygnus initial credentials API. * gic_keytab.c: New file; Cygnus initial creds. * gic_opt.c: New file; Cygnus initial creds. * gic_pwd.c: New file; Cygnus initial creds. * preauth.c: Add more SAM support (from Cygnus). * preauth2.c: New file; additional SAM support from Cygnus. * send_tgs.c: Account for additional parameter to sendto_kdc. * vfy_increds.c: New file; Cygnus initial creds. * vic_opt.c: New file; Cygnus initial creds. git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@10321 dc483132-0cff-0310-8789-dd5450dbe970 --- diff --git a/src/lib/krb5/krb/ChangeLog b/src/lib/krb5/krb/ChangeLog index 043f0f748..8c2d5e2da 100644 --- a/src/lib/krb5/krb/ChangeLog +++ b/src/lib/krb5/krb/ChangeLog @@ -1,3 +1,29 @@ +Sat Dec 6 02:28:17 1997 Tom Yu + + * Makefile.in: Add files chpw.c, gic_*, preauth2.c, vfy_increds.c, + vic_opt.c. + + * chpw.c: New file; implement Cygnus chpw. + + * get_in_tkt.c: Implement support for Cygnus initial credentials + API. + + * gic_keytab.c: New file; Cygnus initial creds. + + * gic_opt.c: New file; Cygnus initial creds. + + * gic_pwd.c: New file; Cygnus initial creds. + + * preauth.c: Add more SAM support (from Cygnus). + + * preauth2.c: New file; additional SAM support from Cygnus. + + * send_tgs.c: Account for additional parameter to sendto_kdc. + + * vfy_increds.c: New file; Cygnus initial creds. + + * vic_opt.c: New file; Cygnus initial creds. + Wed Oct 22 00:29:33 1997 Theodore Y. Ts'o * send_tgs.c (krb5_send_tgs): Don't send a zero endtime; if the diff --git a/src/lib/krb5/krb/Makefile.in b/src/lib/krb5/krb/Makefile.in index 8d08fcea3..87f93ea19 100644 --- a/src/lib/krb5/krb/Makefile.in +++ b/src/lib/krb5/krb/Makefile.in @@ -16,6 +16,7 @@ STLIBOBJS= \ bld_pr_ext.o \ bld_princ.o \ chk_trans.o \ + chpw.o \ conv_princ.o \ copy_addrs.o \ copy_auth.o \ @@ -39,6 +40,9 @@ STLIBOBJS= \ gen_subkey.o \ get_creds.o \ get_in_tkt.o \ + gic_keytab.o \ + gic_opt.o \ + gic_pwd.o \ in_tkt_ktb.o \ in_tkt_pwd.o \ in_tkt_sky.o \ @@ -54,6 +58,7 @@ STLIBOBJS= \ parse.o \ pr_to_salt.o \ preauth.o \ + preauth2.o \ princ_comp.o \ rd_cred.o \ rd_error.o \ @@ -81,6 +86,8 @@ STLIBOBJS= \ tgtname.o \ unparse.o \ valid_times.o \ + vfy_increds.o \ + vic_opt.o \ walk_rtree.o OBJS= addr_comp.$(OBJEXT) \ @@ -90,6 +97,7 @@ OBJS= addr_comp.$(OBJEXT) \ bld_pr_ext.$(OBJEXT) \ bld_princ.$(OBJEXT) \ chk_trans.$(OBJEXT) \ + chpw.$(OBJEXT) \ conv_princ.$(OBJEXT) \ copy_addrs.$(OBJEXT) \ copy_auth.$(OBJEXT) \ @@ -113,6 +121,9 @@ OBJS= addr_comp.$(OBJEXT) \ gen_subkey.$(OBJEXT) \ get_creds.$(OBJEXT) \ get_in_tkt.$(OBJEXT) \ + gic_keytab.$(OBJEXT) \ + gic_opt.$(OBJEXT) \ + gic_pwd.$(OBJEXT) \ in_tkt_ktb.$(OBJEXT) \ in_tkt_pwd.$(OBJEXT) \ in_tkt_sky.$(OBJEXT) \ @@ -128,6 +139,7 @@ OBJS= addr_comp.$(OBJEXT) \ parse.$(OBJEXT) \ pr_to_salt.$(OBJEXT) \ preauth.$(OBJEXT) \ + preauth2.$(OBJEXT) \ princ_comp.$(OBJEXT) \ rd_cred.$(OBJEXT) \ rd_error.$(OBJEXT) \ @@ -155,6 +167,8 @@ OBJS= addr_comp.$(OBJEXT) \ tgtname.$(OBJEXT) \ unparse.$(OBJEXT) \ valid_times.$(OBJEXT) \ + vfy_increds.$(OBJEXT) \ + vic_opt.$(OBJEXT) \ walk_rtree.$(OBJEXT) SRCS= $(srcdir)/addr_comp.c \ @@ -165,6 +179,7 @@ SRCS= $(srcdir)/addr_comp.c \ $(srcdir)/bld_princ.c \ $(srcdir)/brand.c \ $(srcdir)/chk_trans.c \ + $(srcdir)/chpw.c \ $(srcdir)/conv_princ.c \ $(srcdir)/copy_addrs.c \ $(srcdir)/copy_auth.c \ @@ -188,6 +203,9 @@ SRCS= $(srcdir)/addr_comp.c \ $(srcdir)/gen_subkey.c \ $(srcdir)/get_creds.c \ $(srcdir)/get_in_tkt.c \ + $(srcdir)/gic_keytab.c \ + $(srcdir)/gic_opt.c \ + $(srcdir)/gic_pwd.c \ $(srcdir)/in_tkt_ktb.c \ $(srcdir)/in_tkt_pwd.c \ $(srcdir)/in_tkt_sky.c \ @@ -203,6 +221,7 @@ SRCS= $(srcdir)/addr_comp.c \ $(srcdir)/parse.c \ $(srcdir)/pr_to_salt.c \ $(srcdir)/preauth.c \ + $(srcdir)/preauth2.c \ $(srcdir)/princ_comp.c \ $(srcdir)/rd_cred.c \ $(srcdir)/rd_error.c \ @@ -230,6 +249,8 @@ SRCS= $(srcdir)/addr_comp.c \ $(srcdir)/tgtname.c \ $(srcdir)/unparse.c \ $(srcdir)/valid_times.c \ + $(srcdir)/vfy_increds.c \ + $(srcdir)/vic_opt.c \ $(srcdir)/walk_rtree.c ##DOS##LIBOBJS = $(OBJS) diff --git a/src/lib/krb5/krb/chpw.c b/src/lib/krb5/krb/chpw.c new file mode 100644 index 000000000..3266f4f87 --- /dev/null +++ b/src/lib/krb5/krb/chpw.c @@ -0,0 +1,221 @@ +#include + +#include "k5-int.h" +#include "krb5_err.h" +#include "auth_con.h" + +KRB5_DLLIMP krb5_error_code KRB5_CALLCONV +krb5_mk_chpw_req(context, auth_context, ap_req, passwd, packet) + krb5_context context; + krb5_auth_context auth_context; + krb5_data *ap_req; + char *passwd; + krb5_data *packet; +{ + krb5_error_code ret; + krb5_data clearpw; + krb5_data cipherpw; + krb5_replay_data replay; + char *ptr; + + if (ret = krb5_auth_con_setflags(context, auth_context, + KRB5_AUTH_CONTEXT_DO_SEQUENCE)) + return(ret); + + clearpw.length = strlen(passwd); + clearpw.data = passwd; + + if (ret = krb5_mk_priv(context, auth_context, + &clearpw, &cipherpw, &replay)) + return(ret); + + packet->length = 6 + ap_req->length + cipherpw.length; + packet->data = (char *) malloc(packet->length); + ptr = packet->data; + + /* length */ + + *ptr++ = (packet->length>>8) & 0xff; + *ptr++ = packet->length & 0xff; + + /* version == 0x0001 big-endian */ + + *ptr++ = 0; + *ptr++ = 1; + + /* ap_req length, big-endian */ + + *ptr++ = (ap_req->length>>8) & 0xff; + *ptr++ = ap_req->length & 0xff; + + /* ap-req data */ + + memcpy(ptr, ap_req->data, ap_req->length); + ptr += ap_req->length; + + /* krb-priv of password */ + + memcpy(ptr, cipherpw.data, cipherpw.length); + + return(0); +} + +KRB5_DLLIMP krb5_error_code KRB5_CALLCONV +krb5_rd_chpw_rep(context, auth_context, packet, result_code, result_data) + krb5_context context; + krb5_auth_context auth_context; + krb5_data *packet; + int *result_code; + krb5_data *result_data; +{ + char *ptr; + int plen, vno; + krb5_data ap_rep; + krb5_ap_rep_enc_part *ap_rep_enc; + krb5_error_code ret; + krb5_data cipherresult; + krb5_data clearresult; + krb5_error *krberror; + krb5_replay_data replay; + krb5_keyblock *tmp; + + if (packet->length < 4) + /* either this, or the server is printing bad messages, + or the caller passed in garbage */ + return(KRB5KRB_AP_ERR_MODIFIED); + + ptr = packet->data; + + /* verify length */ + + plen = (*ptr++ & 0xff); + plen = (plen<<8) | (*ptr++ & 0xff); + + if (plen != packet->length) + return(KRB5KRB_AP_ERR_MODIFIED); + + /* verify version number */ + + vno = (*ptr++ & 0xff); + vno = (vno<<8) | (*ptr++ & 0xff); + + if (vno != 1) + return(KRB5KDC_ERR_BAD_PVNO); + + /* read, check ap-rep length */ + + ap_rep.length = (*ptr++ & 0xff); + ap_rep.length = (ap_rep.length<<8) | (*ptr++ & 0xff); + + if (ptr + ap_rep.length >= packet->data + packet->length) + return(KRB5KRB_AP_ERR_MODIFIED); + + if (ap_rep.length) { + /* verify ap_rep */ + ap_rep.data = ptr; + ptr += ap_rep.length; + + if (ret = krb5_rd_rep(context, auth_context, &ap_rep, &ap_rep_enc)) + return(ret); + + krb5_free_ap_rep_enc_part(context, ap_rep_enc); + + /* extract and decrypt the result */ + + cipherresult.data = ptr; + cipherresult.length = (packet->data + packet->length) - ptr; + + /* XXX there's no api to do this right. The problem is that + if there's a remote subkey, it will be used. This is + not what the spec requires */ + + tmp = auth_context->remote_subkey; + auth_context->remote_subkey = NULL; + + ret = krb5_rd_priv(context, auth_context, &cipherresult, &clearresult, + &replay); + + auth_context->remote_subkey = tmp; + + if (ret) + return(ret); + } else { + cipherresult.data = ptr; + cipherresult.length = (packet->data + packet->length) - ptr; + + if (ret = krb5_rd_error(context, &cipherresult, &krberror)) + return(ret); + + clearresult = krberror->e_data; + } + + if (clearresult.length < 2) { + ret = KRB5KRB_AP_ERR_MODIFIED; + goto cleanup; + } + + ptr = clearresult.data; + + *result_code = (*ptr++ & 0xff); + *result_code = (*result_code<<8) | (*ptr++ & 0xff); + + if ((*result_code < KRB5_KPASSWD_SUCCESS) || + (*result_code > KRB5_KPASSWD_SOFTERROR)) { + ret = KRB5KRB_AP_ERR_MODIFIED; + goto cleanup; + } + + /* all success replies should be authenticated/encrypted */ + + if ((ap_rep.length == 0) && (*result_code == KRB5_KPASSWD_SUCCESS)) { + ret = KRB5KRB_AP_ERR_MODIFIED; + goto cleanup; + } + + result_data->length = (clearresult.data + clearresult.length) - ptr; + + if (result_data->length) { + result_data->data = (char *) malloc(result_data->length); + memcpy(result_data->data, ptr, result_data->length); + } else { + result_data->data = NULL; + } + + ret = 0; + +cleanup: + if (ap_rep.length) { + krb5_xfree(clearresult.data); + } else { + krb5_free_error(context, krberror); + } + + return(ret); +} + +KRB5_DLLIMP krb5_error_code KRB5_CALLCONV +krb5_chpw_result_code_string(context, result_code, code_string) + krb5_context context; + int result_code; + char **code_string; +{ + switch (result_code) { + case KRB5_KPASSWD_MALFORMED: + *code_string = "Malformed request error"; + break; + case KRB5_KPASSWD_HARDERROR: + *code_string = "Server error"; + break; + case KRB5_KPASSWD_AUTHERROR: + *code_string = "Authentication error"; + break; + case KRB5_KPASSWD_SOFTERROR: + *code_string = "Password change rejected"; + break; + default: + *code_string = "Password change failed"; + break; + } + + return(0); +} diff --git a/src/lib/krb5/krb/get_in_tkt.c b/src/lib/krb5/krb/get_in_tkt.c index 131469016..cbcbc7246 100644 --- a/src/lib/krb5/krb/get_in_tkt.c +++ b/src/lib/krb5/krb/get_in_tkt.c @@ -24,6 +24,8 @@ * krb5_get_in_tkt() */ +#include + #include "k5-int.h" /* @@ -74,12 +76,14 @@ typedef krb5_error_code (*git_decrypt_proc) PROTOTYPE((krb5_context, * unexpected response, an error is returned. */ static krb5_error_code -send_as_request(context, request, time_now, ret_err_reply, ret_as_reply) +send_as_request(context, request, time_now, ret_err_reply, ret_as_reply, + master) krb5_context context; krb5_kdc_req *request; krb5_timestamp *time_now; krb5_error ** ret_err_reply; krb5_kdc_rep ** ret_as_reply; + int * master; { krb5_kdc_rep *as_reply = 0; krb5_error_code retval; @@ -104,7 +108,8 @@ send_as_request(context, request, time_now, ret_err_reply, ret_as_reply) k4_version = packet->data[0]; retval = krb5_sendto_kdc(context, packet, - krb5_princ_realm(context, request->client), &reply); + krb5_princ_realm(context, request->client), + &reply, master); krb5_free_data(context, packet); if (retval) goto cleanup; @@ -233,7 +238,8 @@ verify_as_reply(context, time_now, request, as_reply) || (request->nonce != as_reply->enc_part2->nonce) /* XXX check for extraneous flags */ /* XXX || (!krb5_addresses_compare(context, addrs, as_reply->enc_part2->caddrs)) */ - || ((request->from != 0) && + || ((request->kdc_options & KDC_OPT_POSTDATED) && + (request->from != 0) && (request->from != as_reply->enc_part2->times.starttime)) || ((request->till != 0) && (as_reply->enc_part2->times.endtime > request->till)) @@ -272,14 +278,26 @@ stash_as_reply(context, time_now, request, as_reply, creds, ccache) { krb5_error_code retval; krb5_data * packet; + krb5_principal client; + krb5_principal server; + + client = NULL; + server = NULL; + + if (!creds->client) + if (retval = krb5_copy_principal(context, as_reply->client, &client)) + goto cleanup; + + if (!creds->server) + if (retval = krb5_copy_principal(context, as_reply->enc_part2->server, + &server)) + goto cleanup; - /* XXX issue warning if as_reply->enc_part2->key_exp is nearby */ - /* fill in the credentials */ if ((retval = krb5_copy_keyblock_contents(context, as_reply->enc_part2->session, &creds->keyblock))) - return (retval); + goto cleanup; creds->times = as_reply->enc_part2->times; creds->is_skey = FALSE; /* this is an AS_REQ, so cannot @@ -299,13 +317,21 @@ stash_as_reply(context, time_now, request, as_reply, creds, ccache) krb5_xfree(packet); /* store it in the ccache! */ - if (ccache) { - if ((retval = krb5_cc_store_cred(context, ccache, creds))) - goto cleanup; - } + if (ccache) + if ((retval = krb5_cc_store_cred(context, ccache, creds))) + goto cleanup; + + if (!creds->client) + creds->client = client; + if (!creds->server) + creds->server = server; cleanup: if (retval) { + if (client) + krb5_free_principal(context, client); + if (server) + krb5_free_principal(context, server); if (creds->keyblock.contents) { memset((char *)creds->keyblock.contents, 0, creds->keyblock.length); @@ -326,9 +352,10 @@ cleanup: } static krb5_error_code -make_preauth_list(context, ptypes, ret_list) +make_preauth_list(context, ptypes, nptypes, ret_list) krb5_context context; krb5_preauthtype * ptypes; + int nptypes; krb5_pa_data *** ret_list; { krb5_preauthtype * ptypep; @@ -336,26 +363,36 @@ make_preauth_list(context, ptypes, ret_list) krb5_pa_data ** preauth_to_use; int i; - for (i=1, ptypep = ptypes; *ptypep; ptypep++, i++) - ; - preauth_to_use = malloc(i * sizeof(krb5_pa_data *)); - if (preauth_to_use == NULL) - return (ENOMEM); - for (preauthp = preauth_to_use, ptypep = ptypes; - *ptypep; - preauthp++, ptypep++) { - *preauthp = malloc(sizeof(krb5_pa_data)); - if (*preauthp == NULL) { - krb5_free_pa_data(context, preauth_to_use); + if (nptypes < 0) { + for (nptypes=0, ptypep = ptypes; *ptypep; ptypep++, nptypes++) + ; + } + + /* allocate space for a NULL to terminate the list */ + + if ((preauthp = + (krb5_pa_data **) malloc((nptypes+1)*sizeof(krb5_pa_data *))) == NULL) + return(ENOMEM); + + for (i=0; i=0; i++) + free(preauthp[i]); + free(preauthp); return (ENOMEM); } - (*preauthp)->magic = KV5M_PA_DATA; - (*preauthp)->pa_type = *ptypep; - (*preauthp)->length = 0; - (*preauthp)->contents = 0; + preauthp[i]->magic = KV5M_PA_DATA; + preauthp[i]->pa_type = ptypes[i]; + preauthp[i]->length = 0; + preauthp[i]->contents = 0; } - *preauthp = NULL; - *ret_list = preauth_to_use; + + /* fill in the terminating NULL */ + + preauthp[nptypes] = NULL; + + *ret_list = preauthp; return 0; } @@ -450,7 +487,7 @@ krb5_get_in_tkt(context, options, addrs, ktypes, ptypes, key_proc, keyseed, err_reply = 0; as_reply = 0; if ((retval = send_as_request(context, &request, &time_now, &err_reply, - &as_reply))) + &as_reply, NULL))) goto cleanup; if (err_reply) { @@ -505,7 +542,7 @@ cleanup: if (preauth_to_use) krb5_free_pa_data(context, preauth_to_use); if (decrypt_key) - krb5_free_keyblock(context, decrypt_key); + krb5_free_keyblock(context, decrypt_key); if (as_reply) { if (ret_as_reply) *ret_as_reply = as_reply; @@ -514,3 +551,466 @@ cleanup: } return (retval); } + +/* begin libdefaults parsing code. This should almost certainly move + somewhere else, but I don't know where the correct somewhere else + is yet. */ + +/* XXX Duplicating this is annoying; try to work on a better way.*/ +static char *conf_yes[] = { + "y", "yes", "true", "t", "1", "on", + 0, +}; + +static char *conf_no[] = { + "n", "no", "false", "nil", "0", "off", + 0, +}; + +static int conf_boolean(s) + char *s; +{ + char **p; + + for(p=conf_yes; *p; p++) { + if (!strcasecmp(*p,s)) + return 1; + } + + for(p=conf_no; *p; p++) { + if (!strcasecmp(*p,s)) + return 0; + } + + /* Default to "no" */ + return 0; +} + +static krb5_error_code +krb5_appdefault_string(context, realm, option, ret_value) + krb5_context context; + const krb5_data *realm; + const char *option; + char **ret_value; +{ + profile_t profile; + const char *names[5]; + char **nameval = NULL; + krb5_error_code retval; + char realmstr[1024]; + char **cpp; + + if (realm->length > sizeof(realmstr)-1) + return(EINVAL); + + strncpy(realmstr, realm->data, realm->length); + realmstr[realm->length] = '\0'; + + if (!context || (context->magic != KV5M_CONTEXT)) + return KV5M_CONTEXT; + + profile = context->profile; + + names[0] = "libdefaults"; + + /* + * Try number one: + * + * [appdefaults] + * REALM = { + * option = + * } + */ + + names[1] = realmstr; + names[2] = option; + names[3] = 0; + retval = profile_get_values(profile, names, &nameval); + if (retval == 0 && nameval && nameval[0]) + goto goodbye; + + /* + * Try number two: + * + * [appdefaults] + * option = + */ + + names[1] = option; + names[2] = 0; + retval = profile_get_values(profile, names, &nameval); + if (retval == 0 && nameval && nameval[0]) + goto goodbye; + +goodbye: + if (!nameval) + return(ENOENT); + + if (!nameval[0]) { + free(nameval); + return(ENOENT); + } + + *ret_value = nameval[0]; + + for (cpp = &nameval[1]; *cpp; cpp++) + free(*cpp); + + free(nameval); + + return 0; +} + +/* not static so verify_init_creds() can call it */ + +krb5_error_code +krb5_appdefault_boolean(context, realm, option, ret_value) + krb5_context context; + const char *option; + const krb5_data *realm; + int *ret_value; +{ + char *string = NULL; + krb5_error_code retval; + + retval = krb5_appdefault_string(context, realm, option, &string); + + if (retval) + return(retval); + + *ret_value = conf_boolean(string); + free(string); + + return(0); +} + +KRB5_DLLIMP krb5_error_code KRB5_CALLCONV +krb5_get_init_creds(context, creds, client, prompter, prompter_data, + start_time, in_tkt_service, options, gak_fct, gak_data, + master, as_reply) + krb5_context context; + krb5_creds *creds; + krb5_principal client; + krb5_prompter_fct prompter; + void *prompter_data; + krb5_deltat start_time; + char *in_tkt_service; + krb5_get_init_creds_opt *options; + krb5_gic_get_as_key_fct gak_fct; + void *gak_data; + int *master; + krb5_kdc_rep **as_reply; +{ + krb5_error_code ret; + krb5_kdc_req request; + krb5_pa_data **padata; + int tempint; + char *tempstr; + krb5_deltat renew_life; + int loopcount; + krb5_data salt; + krb5_keyblock as_key; + krb5_error *err_reply; + krb5_kdc_rep *local_as_reply; + krb5_timestamp time_now; + + /* initialize everything which will be freed at cleanup */ + + request.server = NULL; + request.ktype = NULL; + request.addresses = NULL; + request.padata = NULL; + padata = NULL; + as_key.length = 0; + salt.length = 0; + salt.data = NULL; + + /* + * Set up the basic request structure + */ + request.magic = KV5M_KDC_REQ; + request.msg_type = KRB5_AS_REQ; + + /* request.padata is filled in later */ + + request.kdc_options = 0; + + /* forwardable */ + + if (options && (options->flags & KRB5_GET_INIT_CREDS_OPT_FORWARDABLE)) + tempint = options->forwardable; + else if ((ret = krb5_appdefault_boolean(context, &client->realm, + "forwardable", &tempint)) == 0) + ; + else + tempint = 0; + if (tempint) + request.kdc_options |= KDC_OPT_FORWARDABLE; + + /* proxiable */ + + if (options && (options->flags & KRB5_GET_INIT_CREDS_OPT_PROXIABLE)) + tempint = options->proxiable; + else if ((ret = krb5_appdefault_boolean(context, &client->realm, + "proxiable", &tempint)) == 0) + ; + else + tempint = 0; + if (tempint) + request.kdc_options |= KDC_OPT_PROXIABLE; + + /* renewable */ + + if (options && (options->flags & KRB5_GET_INIT_CREDS_OPT_RENEW_LIFE)) { + renew_life = options->renew_life; + } else if ((ret = krb5_appdefault_string(context, &client->realm, + "renew_lifetime", &tempstr)) + == 0) { + if (ret = krb5_string_to_deltat(tempstr, &renew_life)) { + free(tempstr); + goto cleanup; + } + } else { + renew_life = 0; + } + if (renew_life > 0) + request.kdc_options |= KDC_OPT_RENEWABLE; + + /* allow_postdate */ + + if (start_time > 0) + request.kdc_options |= (KDC_OPT_ALLOW_POSTDATE|KDC_OPT_POSTDATED); + + /* client */ + + request.client = client; + + if (in_tkt_service) { + /* this is ugly, because so are the data structures involved. I'm + in the library, so I'm going to manipulate the data structures + directly, otherwise, it will be worse. */ + + if (ret = krb5_parse_name(context, in_tkt_service, &request.server)) + goto cleanup; + + /* stuff the client realm into the server principal. + realloc if necessary */ + if (request.server->realm.length < request.client->realm.length) + if ((request.server->realm.data = + (char *) realloc(request.server->realm.data, + request.client->realm.length)) == NULL) { + ret = ENOMEM; + goto cleanup; + } + + request.server->realm.length = request.client->realm.length; + memcpy(request.server->realm.data, request.client->realm.data, + request.client->realm.length); + } else { + if (ret = krb5_build_principal_ext(context, &request.server, + request.client->realm.length, + request.client->realm.data, + KRB5_TGS_NAME_SIZE, + KRB5_TGS_NAME, + request.client->realm.length, + request.client->realm.data, + 0)) + goto cleanup; + } + + if (ret = krb5_timeofday(context, &request.from)) + goto cleanup; + request.from += start_time; + + request.till = request.from; + if (options && (options->flags & KRB5_GET_INIT_CREDS_OPT_TKT_LIFE)) + request.till += options->tkt_life; + else + request.till += 10*60*60; /* this used to be hardcoded in kinit.c */ + + if (renew_life > 0) { + request.rtime = request.from; + request.rtime += renew_life; + } else { + request.rtime = 0; + } + + /* nonce is filled in by send_as_request */ + + if (options && (options->flags & KRB5_GET_INIT_CREDS_OPT_ETYPE_LIST)) { + request.ktype = options->etype_list; + request.nktypes = options->etype_list_length; + } else if ((ret = krb5_get_default_in_tkt_ktypes(context, + &request.ktype)) == 0) { + for (request.nktypes = 0; + request.ktype[request.nktypes]; + request.nktypes++) + ; + } else { + /* there isn't any useful default here. ret is set from above */ + goto cleanup; + } + + if (options && (options->flags & KRB5_GET_INIT_CREDS_OPT_ADDRESS_LIST)) + request.addresses = options->address_list; + else + if ((ret = krb5_os_localaddr(context, &request.addresses))) + goto cleanup; + + request.authorization_data.ciphertext.length = 0; + request.authorization_data.ciphertext.data = 0; + request.unenc_authdata = 0; + request.second_ticket = 0; + + /* set up the other state. */ + + if (options && (options->flags & KRB5_GET_INIT_CREDS_OPT_PREAUTH_LIST)) { + if (ret = make_preauth_list(context, options->preauth_list, + options->preauth_list_length, + &padata)) + goto cleanup; + } + + /* the salt is allocated from somewhere, unless it is from the caller, + then it is a reference */ + + if (options && (options->flags & KRB5_GET_INIT_CREDS_OPT_SALT)) { + salt = *options->salt; + } else { + salt.length = -1; + salt.data = NULL; + } + + /* now, loop processing preauth data and talking to the kdc */ + + for (loopcount = 0; loopcount < MAX_IN_TKT_LOOPS; loopcount++) { + if (request.padata) { + krb5_free_pa_data(context, request.padata); + request.padata = NULL; + } + + if (ret = krb5_do_preauth(context, &request, + padata, &request.padata, + &salt, &as_key, prompter, + prompter_data, gak_fct, gak_data)) + goto cleanup; + + if (padata) { + krb5_free_pa_data(context, padata); + padata = 0; + } + + err_reply = 0; + local_as_reply = 0; + if ((ret = send_as_request(context, &request, &time_now, &err_reply, + &local_as_reply, master))) + goto cleanup; + + if (err_reply) { + if (err_reply->error == KDC_ERR_PREAUTH_REQUIRED && + err_reply->e_data.length > 0) { + ret = decode_krb5_padata_sequence(&err_reply->e_data, + &padata); + krb5_free_error(context, err_reply); + if (ret) + goto cleanup; + } else { + ret = err_reply->error + ERROR_TABLE_BASE_krb5; + krb5_free_error(context, err_reply); + goto cleanup; + } + } else if (local_as_reply) { + break; + } else { + ret = KRB5KRB_AP_ERR_MSG_TYPE; + goto cleanup; + } + } + + if (loopcount == MAX_IN_TKT_LOOPS) { + ret = KRB5_GET_IN_TKT_LOOP; + goto cleanup; + } + + /* process any preauth data in the as_reply */ + + if (ret = krb5_do_preauth(context, &request, + local_as_reply->padata, &padata, + &salt, &as_key, prompter, + prompter_data, gak_fct, gak_data)) + goto cleanup; + + /* XXX if there's padata on output, something is wrong, but it's + not obviously an error */ + + /* XXX because etypes are handled poorly (particularly wrt SAM, + where the etype is fixed by the kdc), we may want to try + decrypt_as_reply twice. If there's an as_key available, try + it. If decrypting the as_rep fails, or if there isn't an + as_key at all yet, then use the gak_fct to get one, and try + again. */ + + if (as_key.length) + ret = decrypt_as_reply(context, NULL, local_as_reply, NULL, + NULL, &as_key, krb5_kdc_rep_decrypt_proc, + NULL); + else + ret = -1; + + if (ret) { + /* if we haven't get gotten a key, get it now */ + + if (ret = ((*gak_fct)(context, request.client, + local_as_reply->enc_part.enctype, + prompter, prompter_data, &salt, + &as_key, gak_data))) + goto cleanup; + + if (ret = decrypt_as_reply(context, NULL, local_as_reply, NULL, + NULL, &as_key, krb5_kdc_rep_decrypt_proc, + NULL)) + goto cleanup; + } + + if (ret = verify_as_reply(context, time_now, &request, local_as_reply)) + goto cleanup; + + /* XXX this should be inside stash_as_reply, but as long as + get_in_tkt is still around using that arg as an in/out, I can't + do that */ + memset(creds, 0, sizeof(*creds)); + + if (ret = stash_as_reply(context, time_now, &request, local_as_reply, + creds, NULL)) + goto cleanup; + + /* success */ + + ret = 0; + +cleanup: + if (request.server) + krb5_free_principal(context, request.server); + if (request.ktype && + (!(options && (options->flags & KRB5_GET_INIT_CREDS_OPT_ETYPE_LIST)))) + free(request.ktype); + if (request.addresses && + (!(options && + (options->flags & KRB5_GET_INIT_CREDS_OPT_ADDRESS_LIST)))) + krb5_free_addresses(context, request.addresses); + if (padata) + krb5_free_pa_data(context, padata); + if (request.padata) + krb5_free_pa_data(context, request.padata); + if (as_key.length) + krb5_free_keyblock_contents(context, &as_key); + if (salt.data && + (!(options && (options->flags & KRB5_GET_INIT_CREDS_OPT_SALT)))) + krb5_xfree(salt.data); + if (as_reply) + *as_reply = local_as_reply; + else if (local_as_reply) + krb5_free_kdc_rep(context, local_as_reply); + + return(ret); +} diff --git a/src/lib/krb5/krb/gic_keytab.c b/src/lib/krb5/krb/gic_keytab.c new file mode 100644 index 000000000..8b6f23199 --- /dev/null +++ b/src/lib/krb5/krb/gic_keytab.c @@ -0,0 +1,126 @@ +#include "k5-int.h" + +static krb5_error_code +krb5_get_as_key_keytab(context, client, etype, prompter, prompter_data, + salt, as_key, gak_data) + krb5_context context; + krb5_principal client; + krb5_enctype etype; + krb5_prompter_fct prompter; + void *prompter_data; + krb5_data *salt; + krb5_keyblock *as_key; + void *gak_data; +{ + krb5_keytab keytab = (krb5_keytab) gak_data; + krb5_error_code ret; + krb5_keytab_entry kt_ent; + krb5_keyblock *kt_key; + + /* if there's already a key of the correct etype, we're done. + if the etype is wrong, free the existing key, and make + a new one. */ + + if (as_key->length) { + if (as_key->enctype == etype) + return(0); + + krb5_free_keyblock(context, as_key); + as_key->length = 0; + } + + if (!valid_enctype(etype)) + return(KRB5_PROG_ETYPE_NOSUPP); + + if ((ret = krb5_kt_get_entry(context, keytab, client, + 0, /* don't have vno available */ + etype, &kt_ent))) + return(ret); + + ret = krb5_copy_keyblock(context, &kt_ent.key, &kt_key); + + /* again, krb5's memory management is lame... */ + + *as_key = *kt_key; + krb5_xfree(kt_key); + + (void) krb5_kt_free_entry(context, &kt_ent); + + return(ret); +} + +KRB5_DLLIMP krb5_error_code KRB5_CALLCONV +krb5_get_init_creds_keytab(context, creds, client, arg_keytab, + start_time, in_tkt_service, options) + krb5_context context; + krb5_creds *creds; + krb5_principal client; + krb5_keytab arg_keytab; + krb5_deltat start_time; + char *in_tkt_service; + krb5_get_init_creds_opt *options; +{ + krb5_error_code ret, ret2; + int master; + krb5_keytab keytab; + + if (arg_keytab == NULL) { + if (ret = krb5_kt_default(context, &keytab)) + return ret; + } else { + keytab = arg_keytab; + } + + master = 0; + + /* first try: get the requested tkt from any kdc */ + + ret = krb5_get_init_creds(context, creds, client, NULL, NULL, + start_time, in_tkt_service, options, + krb5_get_as_key_keytab, (void *) keytab, + &master, NULL); + + /* check for success */ + + if (ret == 0) + goto cleanup; + + /* If all the kdc's are unavailable fail */ + + if (ret == KRB5_KDC_UNREACH) + goto cleanup; + + /* if the reply did not come from the master kdc, try again with + the master kdc */ + + if (!master) { + master = 1; + + ret2 = krb5_get_init_creds(context, creds, client, NULL, NULL, + start_time, in_tkt_service, options, + krb5_get_as_key_keytab, (void *) keytab, + &master, NULL); + + if (ret2 == 0) { + ret = 0; + goto cleanup; + } + + /* if the master is unreachable, return the error from the + slave we were able to contact */ + + if (ret2 == KRB5_KDC_UNREACH) + goto cleanup; + + ret = ret2; + } + + /* at this point, we have a response from the master. Since we don't + do any prompting or changing for keytabs, that's it. */ + +cleanup: + if (arg_keytab == NULL) + krb5_kt_close(context, keytab); + + return(ret); +} diff --git a/src/lib/krb5/krb/gic_opt.c b/src/lib/krb5/krb/gic_opt.c new file mode 100644 index 000000000..eb04f51ee --- /dev/null +++ b/src/lib/krb5/krb/gic_opt.c @@ -0,0 +1,85 @@ +#include "k5-int.h" + +KRB5_DLLIMP void KRB5_CALLCONV +krb5_get_init_creds_opt_init(opt) + krb5_get_init_creds_opt *opt; +{ + opt->flags = 0; +} + +KRB5_DLLIMP void KRB5_CALLCONV +krb5_get_init_creds_opt_set_tkt_life(opt, tkt_life) + krb5_get_init_creds_opt *opt; + krb5_deltat tkt_life; +{ + opt->flags |= KRB5_GET_INIT_CREDS_OPT_TKT_LIFE; + opt->tkt_life = tkt_life; +} + +KRB5_DLLIMP void KRB5_CALLCONV +krb5_get_init_creds_opt_set_renew_life(opt, renew_life) + krb5_get_init_creds_opt *opt; + krb5_deltat renew_life; +{ + opt->flags |= KRB5_GET_INIT_CREDS_OPT_RENEW_LIFE; + opt->renew_life = renew_life; +} + +KRB5_DLLIMP void KRB5_CALLCONV +krb5_get_init_creds_opt_set_forwardable(opt, forwardable) + krb5_get_init_creds_opt *opt; + int forwardable; +{ + opt->flags |= KRB5_GET_INIT_CREDS_OPT_FORWARDABLE; + opt->forwardable = forwardable; +} + +KRB5_DLLIMP void KRB5_CALLCONV +krb5_get_init_creds_opt_set_proxiable(opt, proxiable) + krb5_get_init_creds_opt *opt; + int proxiable; +{ + opt->flags |= KRB5_GET_INIT_CREDS_OPT_PROXIABLE; + opt->proxiable = proxiable; +} + +KRB5_DLLIMP void KRB5_CALLCONV +krb5_get_init_creds_opt_set_etype_list(opt, etype_list, etype_list_length) + krb5_get_init_creds_opt *opt; + krb5_enctype *etype_list; + int etype_list_length; +{ + opt->flags |= KRB5_GET_INIT_CREDS_OPT_ETYPE_LIST; + opt->etype_list = etype_list; + opt->etype_list_length = etype_list_length; +} + +KRB5_DLLIMP void KRB5_CALLCONV +krb5_get_init_creds_opt_set_address_list(opt, addresses) + krb5_get_init_creds_opt *opt; + krb5_address **addresses; +{ + opt->flags |= KRB5_GET_INIT_CREDS_OPT_ADDRESS_LIST; + opt->address_list = addresses; +} + +KRB5_DLLIMP void KRB5_CALLCONV +krb5_get_init_creds_opt_set_preauth_list(opt, preauth_list, + preauth_list_length) + krb5_get_init_creds_opt *opt; + krb5_preauthtype *preauth_list; + int preauth_list_length; +{ + opt->flags |= KRB5_GET_INIT_CREDS_OPT_PREAUTH_LIST; + opt->preauth_list = preauth_list; + opt->preauth_list_length = preauth_list_length; +} + +KRB5_DLLIMP void KRB5_CALLCONV +krb5_get_init_creds_opt_set_salt(opt, salt) + krb5_get_init_creds_opt *opt; + krb5_data *salt; +{ + opt->flags |= KRB5_GET_INIT_CREDS_OPT_SALT; + opt->salt = salt; +} diff --git a/src/lib/krb5/krb/gic_pwd.c b/src/lib/krb5/krb/gic_pwd.c new file mode 100644 index 000000000..5ab3280c6 --- /dev/null +++ b/src/lib/krb5/krb/gic_pwd.c @@ -0,0 +1,317 @@ +#include "k5-int.h" +#include "com_err.h" + +static krb5_error_code +krb5_get_as_key_password(context, client, etype, prompter, prompter_data, + salt, as_key, gak_data) + krb5_context context; + krb5_principal client; + krb5_enctype etype; + krb5_prompter_fct prompter; + void *prompter_data; + krb5_data *salt; + krb5_keyblock *as_key; + void *gak_data; +{ + krb5_data *password; + krb5_error_code ret; + krb5_data defsalt; + krb5_encrypt_block eblock; + char *clientstr; + char promptstr[1024]; + krb5_prompt prompt; + + password = (krb5_data *) gak_data; + + /* if there's already a key of the correct etype, we're done. + if the etype is wrong, free the existing key, and make + a new one. */ + + if (as_key->length) { + if (as_key->enctype == etype) + return(0); + + krb5_free_keyblock_contents(context, as_key); + as_key->length = 0; + } + + if (!valid_enctype(etype)) + return(KRB5_PROG_ETYPE_NOSUPP); + + krb5_use_enctype(context, &eblock, etype); + + if (password->data[0] == '\0') { + if (prompter == NULL) + return(EIO); + + if (ret = krb5_unparse_name(context, client, &clientstr)) + return(ret); + + strcpy(promptstr, "Password for "); + strncat(promptstr, clientstr, sizeof(promptstr)-strlen(promptstr)-1); + promptstr[sizeof(promptstr)-1] = '\0'; + + free(clientstr); + + prompt.prompt = promptstr; + prompt.hidden = 1; + prompt.reply = password; + + if (ret = ((*prompter)(context, prompter_data, NULL, 1, &prompt))) + return(ret); + } + + if ((salt->length == -1) && (salt->data == NULL)) { + if (ret = krb5_principal2salt(context, client, &defsalt)) + return(ret); + + salt = &defsalt; + } else { + defsalt.length = 0; + } + + ret = krb5_string_to_key(context, &eblock, as_key, password, salt); + + if (defsalt.length) + krb5_xfree(defsalt.data); + + return(ret); +} + +KRB5_DLLIMP krb5_error_code KRB5_CALLCONV +krb5_get_init_creds_password(context, creds, client, password, prompter, data, + start_time, in_tkt_service, options) + krb5_context context; + krb5_creds *creds; + krb5_principal client; + char *password; + krb5_prompter_fct prompter; + void *data; + krb5_deltat start_time; + char *in_tkt_service; + krb5_get_init_creds_opt *options; +{ + krb5_error_code ret, ret2; + int master; + krb5_kdc_rep *as_reply; + int tries; + krb5_creds chpw_creds; + krb5_get_init_creds_opt chpw_opts; + krb5_data pw0, pw1; + char banner[1024], pw0array[1024], pw1array[1024]; + krb5_prompt prompt[2]; + + master = 0; + as_reply = NULL; + memset(&chpw_creds, 0, sizeof(chpw_creds)); + + pw0.data = pw0array; + + if (password) { + if ((pw0.length = strlen(password)) > sizeof(pw0array)) { + ret = EINVAL; + goto cleanup; + } + strcpy(pw0.data, password); + } else { + pw0.data[0] = '\0'; + pw0.length = sizeof(pw0array); + } + + pw1.data = pw1array; + pw1.data[0] = '\0'; + pw1.length = sizeof(pw1array); + + /* first try: get the requested tkt from any kdc */ + + ret = krb5_get_init_creds(context, creds, client, prompter, data, + start_time, in_tkt_service, options, + krb5_get_as_key_password, (void *) &pw0, + &master, &as_reply); + + /* check for success */ + + if (ret == 0) + goto cleanup; + + /* If all the kdc's are unavailable, or if the error was due to a + user interrupt, fail */ + + if ((ret == KRB5_KDC_UNREACH) || + (ret == KRB5_LIBOS_PWDINTR)) + goto cleanup; + + /* if the reply did not come from the master kdc, try again with + the master kdc */ + + if (!master) { + master = 1; + + ret2 = krb5_get_init_creds(context, creds, client, prompter, data, + start_time, in_tkt_service, options, + krb5_get_as_key_password, (void *) &pw0, + &master, &as_reply); + + if (ret2 == 0) { + ret = 0; + goto cleanup; + } + + /* if the master is unreachable, return the error from the + slave we were able to contact */ + + if (ret2 == KRB5_KDC_UNREACH) + goto cleanup; + + ret = ret2; + } + + /* at this point, we have an error from the master. if the error + is not password expired, or if it is but there's no prompter, + return this error */ + + if ((ret != KRB5KDC_ERR_KEY_EXP) || + (prompter == NULL)) + goto cleanup; + + /* ok, we have an expired password. Give the user a few chances + to change it */ + + /* use a minimal set of options */ + + krb5_get_init_creds_opt_init(&chpw_opts); + krb5_get_init_creds_opt_set_tkt_life(&chpw_opts, 5*60); + krb5_get_init_creds_opt_set_renew_life(&chpw_opts, 0); + krb5_get_init_creds_opt_set_forwardable(&chpw_opts, 0); + krb5_get_init_creds_opt_set_proxiable(&chpw_opts, 0); + + if (ret = krb5_get_init_creds(context, &chpw_creds, client, + prompter, data, + start_time, "kadmin/changepw", &chpw_opts, + krb5_get_as_key_password, (void *) &pw0, + &master, NULL)) + goto cleanup; + + prompt[0].prompt = "Enter new password"; + prompt[0].hidden = 1; + prompt[0].reply = &pw0; + + prompt[1].prompt = "Enter it again"; + prompt[1].hidden = 1; + prompt[1].reply = &pw1; + + strcpy(banner, "Password expired. You must change it now."); + + for (tries = 3; tries; tries--) { + pw0.length = sizeof(pw0array); + pw1.length = sizeof(pw1array); + + if (ret = ((*prompter)(context, data, banner, + sizeof(prompt)/sizeof(prompt[0]), prompt))) + goto cleanup; + + if (strcmp(pw0.data, pw1.data) != 0) { + ret = KRB5_LIBOS_BADPWDMATCH; + sprintf(banner, "%s. Please try again.", error_message(ret)); + } else if (pw0.length == 0) { + ret = KRB5_CHPW_PWDNULL; + sprintf(banner, "%s. Please try again.", error_message(ret)); + } else { + int result_code; + krb5_data code_string; + krb5_data result_string; + + if (ret = krb5_change_password(context, &chpw_creds, pw0array, + &result_code, &code_string, + &result_string)) + goto cleanup; + + /* the change succeeded. go on */ + + if (result_code == 0) { + krb5_xfree(result_string.data); + break; + } + + /* set this in case the retry loop falls through */ + + ret = KRB5_CHPW_FAIL; + + if (result_code != KRB5_KPASSWD_SOFTERROR) { + krb5_xfree(result_string.data); + goto cleanup; + } + + /* the error was soft, so try again */ + + /* 100 is I happen to know that no code_string will be longer + than 100 chars */ + + if (result_string.length > (sizeof(banner)-100)) + result_string.length = sizeof(banner)-100; + + sprintf(banner, "%.*s%s%.*s. Please try again.\n", + code_string.length, code_string.data, + result_string.length?": ":"", + result_string.length, result_string.data); + + krb5_xfree(code_string.data); + krb5_xfree(result_string.data); + } + } + + if (ret) + goto cleanup; + + /* the password change was successful. Get an initial ticket + from the master. this is the last try. the return from this + is final. */ + + ret = krb5_get_init_creds(context, creds, client, prompter, data, + start_time, in_tkt_service, options, + krb5_get_as_key_password, (void *) &pw0, + &master, &as_reply); + +cleanup: + /* if getting the password was successful, then check to see if the + password is about to expire, and warn if so */ + + if (ret == 0) { + krb5_timestamp now; + int hours; + + /* XXX 7 days should be configurable. This is all pretty ad hoc, + and could probably be improved if I was willing to screw around + with timezones, etc. */ + + if (prompter && + (in_tkt_service && + (strcmp(in_tkt_service, "kadmin/changepw") != 0)) && + ((ret = krb5_timeofday(context, &now)) == 0) && + as_reply->enc_part2->key_exp && + ((hours = ((as_reply->enc_part2->key_exp-now)/(60*60))) <= 7*24) && + (hours >= 0)) { + if (hours < 1) + sprintf(banner, + "Warning: Your password will expire in less than one hour.", + hours); + else if (hours <= 48) + sprintf(banner, "Warning: Your password will expire in %d hour%s.", + hours, (hours == 1)?"":"s"); + else + sprintf(banner, "Warning: Your password will expire in %d days.", + hours/24); + + /* ignore an error here */ + (*prompter)(context, data, banner, NULL, 0); + } + } + + memset(pw0array, 0, sizeof(pw0array)); + memset(pw1array, 0, sizeof(pw1array)); + krb5_free_cred_contents(context, &chpw_creds); + if (as_reply) + krb5_free_kdc_rep(context, as_reply); + + return(ret); +} diff --git a/src/lib/krb5/krb/preauth.c b/src/lib/krb5/krb/preauth.c index 586e1b94d..910069d1c 100644 --- a/src/lib/krb5/krb/preauth.c +++ b/src/lib/krb5/krb/preauth.c @@ -446,8 +446,13 @@ char *handle_sam_labels(sc) label = "Challenge for Enigma Logic mechanism"; break; case PA_SAM_TYPE_DIGI_PATH: /* Digital Pathways */ + case PA_SAM_TYPE_DIGI_PATH_HEX: /* Digital Pathways */ label = "Challenge for Digital Pathways mechanism"; break; + case PA_SAM_TYPE_ACTIVCARD_DEC: /* Digital Pathways */ + case PA_SAM_TYPE_ACTIVCARD_HEX: /* Digital Pathways */ + label = "Challenge for Activcard mechanism"; + break; case PA_SAM_TYPE_SKEY_K0: /* S/key where KDC has key 0 */ label = "Challenge for Enhanced S/Key mechanism"; break; @@ -457,6 +462,9 @@ char *handle_sam_labels(sc) case PA_SAM_TYPE_SECURID: /* Security Dynamics */ label = "Challenge for Security Dynamics mechanism"; break; + case PA_SAM_TYPE_SECURID_PREDICT: /* predictive Security Dynamics */ + label = "Challenge for Security Dynamics mechanism"; + break; } prompt = "Passcode"; label_len = strlen(label); @@ -470,10 +478,12 @@ char *handle_sam_labels(sc) p = prompt1 = malloc(label_len + strlen(sep1) + challenge_len + strlen(sep2) + prompt_len+ strlen(sep3) + 1); - strncpy(p, label, label_len); p += label_len; - strcpy(p, sep1); p += strlen(sep1); - strncpy(p, challenge, challenge_len); p += challenge_len; - strcpy(p, sep2); p += strlen(sep2); + if (challenge_len) { + strncpy(p, label, label_len); p += label_len; + strcpy(p, sep1); p += strlen(sep1); + strncpy(p, challenge, challenge_len); p += challenge_len; + strcpy(p, sep2); p += strlen(sep2); + } strncpy(p, prompt, prompt_len); p += prompt_len; strcpy(p, sep3); /* p += strlen(sep3); */ return prompt1; diff --git a/src/lib/krb5/krb/preauth2.c b/src/lib/krb5/krb/preauth2.c new file mode 100644 index 000000000..0a154f6e1 --- /dev/null +++ b/src/lib/krb5/krb/preauth2.c @@ -0,0 +1,471 @@ +/* + * Copyright 1995 by the Massachusetts Institute of Technology. All + * Rights Reserved. + * + * Export of this software from the United States of America may + * require a specific license from the United States Government. + * It is the responsibility of any person or organization contemplating + * export to obtain such a license before exporting. + * + * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + * distribute this software and its documentation for any purpose and + * without fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright notice and + * this permission notice appear in supporting documentation, and that + * the name of M.I.T. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. M.I.T. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + * + */ + +/* + * This file contains routines for establishing, verifying, and any other + * necessary functions, for utilizing the pre-authentication field of the + * kerberos kdc request, with various hardware/software verification devices. + */ + +#include "k5-int.h" + +typedef krb5_error_code (*pa_function)(krb5_context, + krb5_kdc_req *request, + krb5_pa_data *in_padata, + krb5_pa_data **out_padata, + krb5_data *salt, + krb5_keyblock *as_key, + krb5_prompter_fct prompter_fct, + void *prompter_data, + krb5_gic_get_as_key_fct gak_fct, + void *gak_data); + +typedef struct _pa_types_t { + krb5_preauthtype type; + pa_function fct; + int flags; +} pa_types_t; + +#define PA_REAL 0x0001 +#define PA_INFO 0x0002 + +static +krb5_error_code pa_salt(krb5_context context, + krb5_kdc_req *request, + krb5_pa_data *in_padata, + krb5_pa_data **out_padata, + krb5_data *salt, + krb5_keyblock *as_key, + krb5_prompter_fct prompter, void *prompter_data, + krb5_gic_get_as_key_fct gak_fct, void *gak_data) +{ + krb5_error_code ret; + krb5_data tmp; + + /* screw the abstraction. If there was a *reasonable* copy_data, + I'd use it. But I'm inside the library, which is the twilight + zone of source code, so I can do anything. */ + + tmp.length = in_padata->length; + if (tmp.length) { + if ((tmp.data = malloc(tmp.length)) == NULL) + return ENOMEM; + memcpy(tmp.data, in_padata->contents, tmp.length); + } else { + tmp.data = NULL; + } + + *salt = tmp; + + /* assume that no other salt was allocated */ + + if (in_padata->pa_type == KRB5_PADATA_AFS3_SALT) + salt->length = -1; + + return(0); +} + +static +krb5_error_code pa_enc_timestamp(krb5_context context, + krb5_kdc_req *request, + krb5_pa_data *in_padata, + krb5_pa_data **out_padata, + krb5_data *salt, + krb5_keyblock *as_key, + krb5_prompter_fct prompter, + void *prompter_data, + krb5_gic_get_as_key_fct gak_fct, + void *gak_data) +{ + krb5_error_code ret; + krb5_pa_enc_ts pa_enc; + krb5_data *tmp; + krb5_enc_data enc_data; + krb5_pa_data *pa; + + /* if we haven't yet gotten a key, get it now. */ + + if (ret = ((*gak_fct)(context, request->client, + request->ktype[0], prompter, prompter_data, + salt, as_key, gak_data))) + return(ret); + + /* now get the time of day, and encrypt it accordingly */ + + if (ret = krb5_us_timeofday(context, &pa_enc.patimestamp, &pa_enc.pausec)) + return(ret); + + if (ret = encode_krb5_pa_enc_ts(&pa_enc, &tmp)) + return(ret); + + ret = krb5_encrypt_data(context, as_key, 0, tmp, &enc_data); + + krb5_free_data(context, tmp); + + if (ret) + return(ret); + + ret = encode_krb5_enc_data(&enc_data, &tmp); + + krb5_xfree(enc_data.ciphertext.data); + + if (ret) + return(ret); + + if ((pa = (krb5_pa_data *) malloc(sizeof(krb5_pa_data))) == NULL) { + krb5_free_data(context, tmp); + return(ENOMEM); + } + + pa->magic = KV5M_PA_DATA; + pa->pa_type = KRB5_PADATA_ENC_TIMESTAMP; + pa->length = tmp->length; + pa->contents = (krb5_octet *) tmp->data; + + *out_padata = pa; + + krb5_xfree(tmp); + + return(0); +} + +static +char *sam_challenge_banner(sam_type) + krb5_int32 sam_type; +{ + char *label; + + switch (sam_type) { + case PA_SAM_TYPE_ENIGMA: /* Enigma Logic */ + label = "Challenge for Enigma Logic mechanism"; + break; + case PA_SAM_TYPE_DIGI_PATH: /* Digital Pathways */ + case PA_SAM_TYPE_DIGI_PATH_HEX: /* Digital Pathways */ + label = "Challenge for Digital Pathways mechanism"; + break; + case PA_SAM_TYPE_ACTIVCARD_DEC: /* Digital Pathways */ + case PA_SAM_TYPE_ACTIVCARD_HEX: /* Digital Pathways */ + label = "Challenge for Activcard mechanism"; + break; + case PA_SAM_TYPE_SKEY_K0: /* S/key where KDC has key 0 */ + label = "Challenge for Enhanced S/Key mechanism"; + break; + case PA_SAM_TYPE_SKEY: /* Traditional S/Key */ + label = "Challenge for Traditional S/Key mechanism"; + break; + case PA_SAM_TYPE_SECURID: /* Security Dynamics */ + label = "Challenge for Security Dynamics mechanism"; + break; + case PA_SAM_TYPE_SECURID_PREDICT: /* predictive Security Dynamics */ + label = "Challenge for Security Dynamics mechanism"; + break; + default: + label = "Challenge from authentication server"; + break; + } + + return(label); +} + +/* this macro expands to the int,ptr necessary for "%.*s" in an sprintf */ + +#define SAMDATA(kdata, str, maxsize) \ + (kdata.length)? \ + ((((kdata.length)<=(maxsize))?(kdata.length):(maxsize))): \ + strlen(str), \ + (kdata.length)?(kdata.data):(str) + +/* XXX Danger! This code is not in sync with the kerberos-password-02 + draft. This draft cannot be implemented as written. This code is + compatible with earlier versions of mit krb5 and cygnus kerbnet. */ + +static +krb5_error_code pa_sam(krb5_context context, + krb5_kdc_req *request, + krb5_pa_data *in_padata, + krb5_pa_data **out_padata, + krb5_data *salt, + krb5_keyblock *as_key, + krb5_prompter_fct prompter, + void *prompter_data, + krb5_gic_get_as_key_fct gak_fct, + void *gak_data) +{ + krb5_error_code ret; + krb5_data tmpsam; + char banner[100], prompt[100], response[100]; + krb5_data response_data; + krb5_prompt kprompt; + krb5_encrypt_block eblock; + krb5_data defsalt; + krb5_sam_challenge *sam_challenge = 0; + krb5_sam_response sam_response; + /* these two get encrypted and stuffed in to sam_response */ + krb5_enc_sam_response_enc enc_sam_response_enc; + krb5_keyblock * sam_use_key = 0; + krb5_data * scratch; + krb5_pa_data * pa; + + tmpsam.length = in_padata->length; + tmpsam.data = (char *) in_padata->contents; + if (ret = decode_krb5_sam_challenge(&tmpsam, &sam_challenge)) + return(ret); + + if (sam_challenge->sam_flags & KRB5_SAM_MUST_PK_ENCRYPT_SAD) { + krb5_xfree(sam_challenge); + return(KRB5_SAM_UNSUPPORTED); + } + + sprintf(banner, "%.*s", + SAMDATA(sam_challenge->sam_challenge_label, + sam_challenge_banner(sam_challenge->sam_type), + sizeof(banner)-1)); + + /* sprintf(prompt, "Challenge is [%s], %s: ", challenge, prompt); */ + sprintf(prompt, "%s%.*s%s%.*s", + sam_challenge->sam_challenge.length?"Challenge is [":"", + SAMDATA(sam_challenge->sam_challenge, "", 20), + sam_challenge->sam_challenge.length?"], ":"", + SAMDATA(sam_challenge->sam_response_prompt, "passcode", 55)); + + response_data.data = response; + response_data.length = sizeof(response); + + kprompt.prompt = prompt; + kprompt.hidden = sam_challenge->sam_challenge.length?0:1; + kprompt.reply = &response_data; + + if (ret = ((*prompter)(context, prompter_data, banner, 1, &kprompt))) { + krb5_xfree(sam_challenge); + return(ret); + } + + enc_sam_response_enc.sam_nonce = sam_challenge->sam_nonce; + if (sam_challenge->sam_flags & KRB5_SAM_SEND_ENCRYPTED_SAD) { + enc_sam_response_enc.sam_passcode = response_data; + } else if (sam_challenge->sam_flags & KRB5_SAM_USE_SAD_AS_KEY) { + if (sam_challenge->sam_nonce == 0) { + if (ret = krb5_us_timeofday(context, + &enc_sam_response_enc.sam_timestamp, + &enc_sam_response_enc.sam_usec)) { + krb5_xfree(sam_challenge); + return(ret); + } + + sam_response.sam_patimestamp = enc_sam_response_enc.sam_timestamp; + } + + /* process the key as password */ + + if (as_key->length) { + krb5_free_keyblock_contents(context, as_key); + as_key->length = 0; + } + + /* XXX the server uses this fixed enctype, so we will, too. */ + + if (!valid_enctype(ENCTYPE_DES_CBC_MD5)) + return(KRB5_PROG_ETYPE_NOSUPP); + + krb5_use_enctype(context, &eblock, ENCTYPE_DES_CBC_MD5); + +#if 0 + if ((salt->length == -1) && (salt->data == NULL)) { + if (ret = krb5_principal2salt(context, request->client, + &defsalt)) { + krb5_xfree(sam_challenge); + return(ret); + } + + salt = &defsalt; + } else { + defsalt.length = 0; + } +#else + defsalt.length = 0; + salt = NULL; +#endif + + ret = krb5_string_to_key(context, &eblock, as_key, + &response_data, salt); + if (defsalt.length) + krb5_xfree(defsalt.data); + + if (ret) { + krb5_xfree(sam_challenge); + return(ret); + } + + enc_sam_response_enc.sam_passcode.length = 0; + } + + /* copy things from the challenge */ + sam_response.sam_nonce = sam_challenge->sam_nonce; + sam_response.sam_flags = sam_challenge->sam_flags; + sam_response.sam_track_id = sam_challenge->sam_track_id; + sam_response.sam_type = sam_challenge->sam_type; + sam_response.magic = KV5M_SAM_RESPONSE; + + krb5_xfree(sam_challenge); + + /* encode the encoded part of the response */ + if (ret = encode_krb5_enc_sam_response_enc(&enc_sam_response_enc, + &scratch)) + return(ret); + + ret = krb5_encrypt_data(context, as_key, 0, scratch, + &sam_response.sam_enc_nonce_or_ts); + + krb5_free_data(context, scratch); + + if (ret) + return(ret); + + /* sam_enc_key is reserved for future use */ + sam_response.sam_enc_key.ciphertext.length = 0; + + if ((pa = malloc(sizeof(krb5_pa_data))) == NULL) + return(ENOMEM); + + if (ret = encode_krb5_sam_response(&sam_response, &scratch)) { + free(pa); + return(ret); + } + + pa->magic = KV5M_PA_DATA; + pa->pa_type = KRB5_PADATA_SAM_RESPONSE; + pa->length = scratch->length; + pa->contents = (krb5_octet *) scratch->data; + + *out_padata = pa; + + return(0); +} + +pa_types_t pa_types[] = { + { + KRB5_PADATA_PW_SALT, + pa_salt, + PA_INFO, + }, + { + KRB5_PADATA_AFS3_SALT, + pa_salt, + PA_INFO, + }, + { + KRB5_PADATA_ENC_TIMESTAMP, + pa_enc_timestamp, + PA_REAL, + }, + { + KRB5_PADATA_SAM_CHALLENGE, + pa_sam, + PA_REAL, + }, + { + -1, + NULL, + 0, + }, +}; + +krb5_error_code +krb5_do_preauth(krb5_context context, + krb5_kdc_req *request, + krb5_pa_data **in_padata, krb5_pa_data ***out_padata, + krb5_data *salt, + krb5_keyblock *as_key, + krb5_prompter_fct prompter, void *prompter_data, + krb5_gic_get_as_key_fct gak_fct, void *gak_data) +{ + int h, i, j, out_pa_list_size; + krb5_pa_data *out_pa, **out_pa_list; + krb5_error_code ret; + static int paorder[] = { PA_INFO, PA_REAL }; + int realdone; + + if (in_padata == NULL) { + *out_padata = NULL; + return(0); + } + + out_pa_list = NULL; + out_pa_list_size = 0; + + /* first do all the informational preauths, then the first real one */ + + for (h=0; h<(sizeof(paorder)/sizeof(paorder[0])); h++) { + realdone = 0; + for (i=0; in_padata[i] && !realdone; i++) { + for (j=0; pa_types[j].type >= 0; j++) { + if ((in_padata[i]->pa_type == pa_types[j].type) && + (pa_types[j].flags & paorder[h])) { + out_pa = NULL; + + if (ret = ((*pa_types[j].fct)(context, request, + in_padata[i], &out_pa, + salt, as_key, + prompter, prompter_data, + gak_fct, gak_data))) { + if (out_pa_list) { + out_pa_list[out_pa_list_size++] = NULL; + krb5_free_pa_data(context, out_pa_list); + } + return(ret); + } + + if (out_pa) { + if (out_pa_list == NULL) { + if ((out_pa_list = + (krb5_pa_data **) + malloc(2*sizeof(krb5_pa_data *))) + == NULL) + return(ENOMEM); + } else { + if ((out_pa_list = + (krb5_pa_data **) + realloc(out_pa_list, + (out_pa_list_size+2)* + sizeof(krb5_pa_data *))) + == NULL) + /* XXX this will leak the pointers which + have already been allocated. oh well. */ + return(ENOMEM); + } + + out_pa_list[out_pa_list_size++] = out_pa; + } + if (h == PA_REAL) + realdone = 1; + } + } + } + } + + if (out_pa_list) + out_pa_list[out_pa_list_size++] = NULL; + + *out_padata = out_pa_list; + + return(0); +} diff --git a/src/lib/krb5/krb/send_tgs.c b/src/lib/krb5/krb/send_tgs.c index 3247c8eb5..b06ef2bfc 100644 --- a/src/lib/krb5/krb/send_tgs.c +++ b/src/lib/krb5/krb/send_tgs.c @@ -337,7 +337,7 @@ krb5_send_tgs(context, kdcoptions, timestruct, ktypes, sname, addrs, /* now send request & get response from KDC */ retval = krb5_sendto_kdc(context, scratch, krb5_princ_realm(context, sname), - &rep->response); + &rep->response, NULL); krb5_free_data(context, scratch); if (retval == 0) { diff --git a/src/lib/krb5/krb/vfy_increds.c b/src/lib/krb5/krb/vfy_increds.c new file mode 100644 index 000000000..bb8ea349b --- /dev/null +++ b/src/lib/krb5/krb/vfy_increds.c @@ -0,0 +1,238 @@ +#include "k5-int.h" + +static krb5_error_code +krb5_cc_copy_creds_except(context, incc, outcc, princ) + krb5_context context; + krb5_ccache incc; + krb5_ccache outcc; + krb5_principal princ; +{ + krb5_error_code code; + krb5_flags flags; + krb5_cc_cursor cur; + krb5_creds creds; + + flags = 0; /* turns off OPENCLOSE mode */ + if ((code = krb5_cc_set_flags(context, incc, flags))) + return(code); + if ((code = krb5_cc_set_flags(context, outcc, flags))) + return(code); + + if ((code = krb5_cc_start_seq_get(context, incc, &cur))) + goto cleanup; + + while (!(code = krb5_cc_next_cred(context, incc, &cur, &creds))) { + if (krb5_principal_compare(context, princ, creds.server)) + continue; + + code = krb5_cc_store_cred(context, outcc, &creds); + krb5_free_cred_contents(context, &creds); + if (code) + goto cleanup; + } + + if (code != KRB5_CC_END) + goto cleanup; + + code = 0; + +cleanup: + flags = KRB5_TC_OPENCLOSE; + + if (code) + krb5_cc_set_flags(context, incc, flags); + else + code = krb5_cc_set_flags(context, incc, flags); + + if (code) + krb5_cc_set_flags(context, outcc, flags); + else + code = krb5_cc_set_flags(context, outcc, flags); + + return(code); +} + +KRB5_DLLIMP krb5_error_code KRB5_CALLCONV +krb5_verify_init_creds(krb5_context context, + krb5_creds *creds, + krb5_principal server_arg, + krb5_keytab keytab_arg, + krb5_ccache *ccache_arg, + krb5_verify_init_creds_opt *options) +{ + krb5_error_code ret; + krb5_principal server; + krb5_keytab keytab; + krb5_ccache ccache; + krb5_keytab_entry kte; + krb5_creds in_creds, *out_creds; + krb5_auth_context authcon; + krb5_data ap_req; + int keytab_key_exists, rd_req_succeeds, nofail; + + keytab_key_exists = 0; + rd_req_succeeds = 0; + nofail = 0; + + /* KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN */ + + server = NULL; + keytab = NULL; + ccache = NULL; + out_creds = NULL; + authcon = NULL; + ap_req.data = NULL; + + if (server_arg) { + server = server_arg; + } else { + if (ret = krb5_sname_to_principal(context, NULL, NULL, + KRB5_NT_SRV_HST, &server)) + goto cleanup; + } + + /* first, check if the server is in the keytab. If not, there's + no reason to continue. rd_req does all this, but there's + no way to know that a given error is caused by a missing + keytab or key, and not by some other problem. */ + + if (keytab_arg) { + keytab = keytab_arg; + } else { + if (ret = krb5_kt_default(context, &keytab)) + goto cleanup; + } + + if (ret = krb5_kt_get_entry(context, keytab, server, 0, 0, &kte)) + goto cleanup; + + krb5_kt_free_entry(context, &kte); + keytab_key_exists = 1; + + /* If the creds are for the server principal, we're set, just do + a mk_req. Otherwise, do a get_credentials first. */ + + if (krb5_principal_compare(context, server, creds->server)) { + /* make an ap_req */ + if (ret = krb5_mk_req_extended(context, &authcon, 0, NULL, creds, + &ap_req)) + goto cleanup; + } else { + /* this is unclean, but it's the easiest way without ripping the + library into very small pieces. store the client's initial cred + in a memory ccache, then call the library. Later, we'll copy + everything except the initial cred into the ccache we return to + the user. A clean implementation would involve library + internals with a coherent idea of "in" and "out". */ + + /* insert the initial cred into the ccache */ + + if (ret = krb5_cc_resolve(context, "MEMORY:rd_req", &ccache)) + goto cleanup; + + if (ret = krb5_cc_initialize(context, ccache, creds->client)) + goto cleanup; + + if (ret = krb5_cc_store_cred(context, ccache, creds)) + goto cleanup; + + /* set up for get_creds */ + memset(&in_creds, 0, sizeof(in_creds)); + in_creds.client = creds->client; + in_creds.server = server; + if (ret = krb5_timeofday(context, &in_creds.times.endtime)) + goto cleanup; + in_creds.times.endtime += 5*60; + + if (ret = krb5_get_credentials(context, 0, ccache, &in_creds, + &out_creds)) + goto cleanup; + + /* make an ap_req */ + if (ret = krb5_mk_req_extended(context, &authcon, 0, NULL, out_creds, + &ap_req)) + goto cleanup; + } + + /* wipe the auth context for mk_req */ + if (authcon) { + krb5_auth_con_free(context, authcon); + authcon = NULL; + } + + /* verify the ap_req */ + + if (ret = krb5_rd_req(context, &authcon, &ap_req, server, keytab, + NULL, NULL)) + goto cleanup; + + rd_req_succeeds = 1; + +cleanup: + /* I could test the error case first, but then there would be a + chance that the verification would succeed when there was + actually a significant failure (some transient condition could + make rd_req fail, and this would not be a problem if nofail was + not set */ + + if (!keytab_key_exists) { + krb5_error_code ret2; + + if (options && + (options->flags & KRB5_VERIFY_INIT_CREDS_OPT_AP_REQ_NOFAIL)) + nofail = options->ap_req_nofail; + else if ((ret2 = krb5_appdefault_boolean(context, &creds->client->realm, + "verify_ap_req_nofail", + &nofail)) + == 0) + ; + else + nofail = 0; + } + + if ((keytab_key_exists && rd_req_succeeds) || + (!keytab_key_exists && !nofail)) { + ret = 0; + + if (ccache_arg && ccache) { + if (*ccache_arg == NULL) { + krb5_ccache retcc; + + retcc = NULL; + + if ((ret = krb5_cc_resolve(context, "MEMORY:rd_req2", &retcc)) || + (ret = krb5_cc_initialize(context, retcc, creds->client)) || + (ret = krb5_cc_copy_creds_except(context, ccache, retcc, + creds->server))) { + if (retcc) + krb5_cc_destroy(context, retcc); + } else { + *ccache_arg = retcc; + } + } else { + /* if this returns an error, then that's the return + from this function */ + ret = krb5_cc_copy_creds_except(context, ccache, *ccache_arg, + server); + } + } + } + + if (!server_arg) + krb5_free_principal(context, server); + if (!keytab_arg) + krb5_kt_close(context, keytab); + if (ccache) + krb5_cc_destroy(context, ccache); + if (out_creds) + krb5_free_creds(context, out_creds); + if (authcon) + krb5_auth_con_free(context, authcon); + if (ap_req.data) + krb5_xfree(ap_req.data); + + return(ret); +} + + + diff --git a/src/lib/krb5/krb/vic_opt.c b/src/lib/krb5/krb/vic_opt.c new file mode 100644 index 000000000..d28d709fb --- /dev/null +++ b/src/lib/krb5/krb/vic_opt.c @@ -0,0 +1,17 @@ +#include "k5-int.h" + +KRB5_DLLIMP void KRB5_CALLCONV +krb5_verify_init_creds_opt_init(opt) + krb5_verify_init_creds_opt *opt; +{ + opt->flags = 0; +} + +KRB5_DLLIMP void KRB5_CALLCONV +krb5_verify_init_creds_opt_set_ap_req_nofail(opt, ap_req_nofail) + krb5_verify_init_creds_opt *opt; + int ap_req_nofail; +{ + opt->flags |= KRB5_VERIFY_INIT_CREDS_OPT_AP_REQ_NOFAIL; + opt->ap_req_nofail = ap_req_nofail; +}