diff options
| author | Sam Hartman <hartmans@mit.edu> | 2006-10-03 19:07:17 +0000 |
|---|---|---|
| committer | Sam Hartman <hartmans@mit.edu> | 2006-10-03 19:07:17 +0000 |
| commit | 63a8ab15aa5ee116b26a50c073fe8ee33e147cbd (patch) | |
| tree | 40296b89921ea7edb3117c4d38c4132494d4ce06 /src/lib | |
| parent | 7f7a4fff296db90d36c39fb01dd35b61bdd6a2b0 (diff) | |
Preauthentication Plugin Framework
Patch from Nalin Dahyabhai at Redhat to implement a preauthentication
framework based on the plugin architecture. Currently. the API is
considered internal and the header is not installed.
See src/include/krb5/preauth_plugin.h for the interface.
ticket: new
Tags: enhancement
Status: open
git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@18641 dc483132-0cff-0310-8789-dd5450dbe970
Diffstat (limited to 'src/lib')
| -rw-r--r-- | src/lib/krb5/error_tables/krb5_err.et | 34 | ||||
| -rw-r--r-- | src/lib/krb5/krb/Makefile.in | 2 | ||||
| -rw-r--r-- | src/lib/krb5/krb/get_in_tkt.c | 262 | ||||
| -rw-r--r-- | src/lib/krb5/krb/init_ctx.c | 2 | ||||
| -rw-r--r-- | src/lib/krb5/krb/preauth2.c | 539 | ||||
| -rw-r--r-- | src/lib/krb5/os/init_os_ctx.c | 2 |
6 files changed, 750 insertions, 91 deletions
diff --git a/src/lib/krb5/error_tables/krb5_err.et b/src/lib/krb5/error_tables/krb5_err.et index 918f351c2..92e45ad61 100644 --- a/src/lib/krb5/error_tables/krb5_err.et +++ b/src/lib/krb5/error_tables/krb5_err.et @@ -103,26 +103,26 @@ error_code KRB5PLACEHOLD_58, "KRB5 error code 58" error_code KRB5PLACEHOLD_59, "KRB5 error code 59" error_code KRB5KRB_ERR_GENERIC, "Generic error (see e-text)" error_code KRB5KRB_ERR_FIELD_TOOLONG, "Field is too long for this implementation" -error_code KRB5PLACEHOLD_62, "KRB5 error code 62" -error_code KRB5PLACEHOLD_63, "KRB5 error code 63" -error_code KRB5PLACEHOLD_64, "KRB5 error code 64" -error_code KRB5PLACEHOLD_65, "KRB5 error code 65" -error_code KRB5PLACEHOLD_66, "KRB5 error code 66" +error_code KRB5KDC_ERR_CLIENT_NOT_TRUSTED, "Client not trusted" +error_code KRB5KDC_ERR_KDC_NOT_TRUSTED, "KDC not trusted" +error_code KRB5KDC_ERR_INVALID_SIG, "Invalid signature" +error_code KRB5KDC_ERR_DH_KEY_PARAMETERS_NOT_ACCEPTED, "Key parameters not accepted" +error_code KRB5KDC_ERR_CERTIFICATE_MISMATCH, "Certificate mismatch" error_code KRB5PLACEHOLD_67, "KRB5 error code 67" error_code KRB5PLACEHOLD_68, "KRB5 error code 68" error_code KRB5PLACEHOLD_69, "KRB5 error code 69" -error_code KRB5PLACEHOLD_70, "KRB5 error code 70" -error_code KRB5PLACEHOLD_71, "KRB5 error code 71" -error_code KRB5PLACEHOLD_72, "KRB5 error code 72" -error_code KRB5PLACEHOLD_73, "KRB5 error code 73" -error_code KRB5PLACEHOLD_74, "KRB5 error code 74" -error_code KRB5PLACEHOLD_75, "KRB5 error code 75" -error_code KRB5PLACEHOLD_76, "KRB5 error code 76" -error_code KRB5PLACEHOLD_77, "KRB5 error code 77" -error_code KRB5PLACEHOLD_78, "KRB5 error code 78" -error_code KRB5PLACEHOLD_79, "KRB5 error code 79" -error_code KRB5PLACEHOLD_80, "KRB5 error code 80" -error_code KRB5PLACEHOLD_81, "KRB5 error code 81" +error_code KRB5KDC_ERR_CANT_VERIFY_CERTIFICATE, "Can't verify certificate" +error_code KRB5KDC_ERR_INVALID_CERTIFICATE, "Invalid certificate" +error_code KRB5KDC_ERR_REVOKED_CERTIFICATE, "Revoked certificate" +error_code KRB5KDC_ERR_REVOCATION_STATUS_UNKNOWN, "Revocation status unknown" +error_code KRB5KDC_ERR_REVOCATION_STATUS_UNAVAILABLE, "Revocation status unavailable" +error_code KRB5KDC_ERR_CLIENT_NAME_MISMATCH, "Client name mismatch" +error_code KRB5KDC_ERR_KDC_NAME_MISMATCH, "KDC name mismatch" +error_code KRB5KDC_ERR_INCONSISTENT_KEY_PURPOSE, "Inconsistent key purpose" +error_code KRB5KDC_ERR_DIGEST_IN_CERT_NOT_ACCEPTED, "Digest in certificate not accepted" +error_code KRB5KDC_ERR_PA_CHECKSUM_MUST_BE_INCLUDED, "Checksum must be included" +error_code KRB5KDC_ERR_DIGEST_IN_SIGNED_DATA_NOT_ACCEPTED, "Digest in signed-data not accepted" +error_code KRB5KDC_ERR_PUBLIC_KEY_ENCRYPTION_NOT_SUPPORTED, "Public key encryption not supported" error_code KRB5PLACEHOLD_82, "KRB5 error code 82" error_code KRB5PLACEHOLD_83, "KRB5 error code 83" error_code KRB5PLACEHOLD_84, "KRB5 error code 84" diff --git a/src/lib/krb5/krb/Makefile.in b/src/lib/krb5/krb/Makefile.in index 4cbc4b9b0..aceca17e7 100644 --- a/src/lib/krb5/krb/Makefile.in +++ b/src/lib/krb5/krb/Makefile.in @@ -6,7 +6,7 @@ RUN_SETUP = @KRB5_RUN_ENV@ PROG_LIBPATH=-L$(TOPLIBD) PROG_RPATH=$(KRB5_LIBDIR) LOCALINCLUDES = -I$(srcdir)/../os -I$(SRCTOP) -DEFS= +DEFS=-DLIBDIR=\"$(KRB5_LIBDIR)\" ##DOS##BUILDTOP = ..\..\.. ##DOS##PREFIXDIR=krb diff --git a/src/lib/krb5/krb/get_in_tkt.c b/src/lib/krb5/krb/get_in_tkt.c index 462dc7c82..53042fb2c 100644 --- a/src/lib/krb5/krb/get_in_tkt.c +++ b/src/lib/krb5/krb/get_in_tkt.c @@ -78,6 +78,9 @@ typedef krb5_error_code (*git_decrypt_proc) (krb5_context, static krb5_error_code make_preauth_list (krb5_context, krb5_preauthtype *, int, krb5_pa_data ***); +static krb5_error_code sort_krb5_padata_sequence(krb5_context context, + krb5_data *realm, + krb5_pa_data **padata); /* * This function performs 32 bit bounded addition so we can generate @@ -105,7 +108,6 @@ static krb5_int32 krb5int_addint32 (krb5_int32 x, krb5_int32 y) static krb5_error_code send_as_request(krb5_context context, krb5_kdc_req *request, - krb5_timestamp *time_now, krb5_error ** ret_err_reply, krb5_kdc_rep ** ret_as_reply, int *use_master) @@ -116,17 +118,16 @@ send_as_request(krb5_context context, krb5_data reply; char k4_version; /* same type as *(krb5_data::data) */ int tcp_only = 0; + krb5_timestamp time_now; reply.data = 0; - - if ((retval = krb5_timeofday(context, time_now))) - goto cleanup; - /* - * XXX we know they are the same size... and we should do - * something better than just the current time - */ - request->nonce = (krb5_int32) *time_now; + /* set the nonce if the caller expects us to do it */ + if (request->nonce == 0) { + if ((retval = krb5_timeofday(context, &time_now))) + goto cleanup; + request->nonce = (krb5_int32) time_now; + } /* encode & send to KDC */ if ((retval = encode_krb5_as_req(request, &packet)) != 0) @@ -437,7 +438,6 @@ static const krb5_enctype get_in_tkt_enctypes[] = { 0 }; - krb5_error_code KRB5_CALLCONV krb5_get_in_tkt(krb5_context context, const krb5_flags options, @@ -486,6 +486,7 @@ krb5_get_in_tkt(krb5_context context, request.kdc_options = options; request.client = creds->client; request.server = creds->server; + request.nonce = 0; request.from = creds->times.starttime; request.till = creds->times.endtime; request.rtime = creds->times.renew_till; @@ -553,7 +554,17 @@ krb5_get_in_tkt(krb5_context context, err_reply = 0; as_reply = 0; - if ((retval = send_as_request(context, &request, &time_now, &err_reply, + + if ((retval = krb5_timeofday(context, &time_now))) + goto cleanup; + + /* + * XXX we know they are the same size... and we should do + * something better than just the current time + */ + request.nonce = (krb5_int32) time_now; + + if ((retval = send_as_request(context, &request, &err_reply, &as_reply, &use_master))) goto cleanup; @@ -565,6 +576,11 @@ krb5_get_in_tkt(krb5_context context, krb5_free_error(context, err_reply); if (retval) goto cleanup; + retval = sort_krb5_padata_sequence(context, + &request.server->realm, + padata); + if (retval) + goto cleanup; continue; } else { retval = (krb5_error_code) err_reply->error @@ -746,6 +762,75 @@ krb5_libdefault_boolean(krb5_context context, const krb5_data *realm, return(0); } +/* Sort a pa_data sequence so that types named in the "preferred_preauth_types" + * libdefaults entry are listed before any others. */ +static krb5_error_code KRB5_CALLCONV +sort_krb5_padata_sequence(krb5_context context, krb5_data *realm, + krb5_pa_data **padata) +{ + int i, j, base; + krb5_error_code ret; + const char *p; + long l; + char *q, *preauth_types = NULL; + krb5_pa_data *tmp; + + if ((padata == NULL) || (padata[0] == NULL)) { + return 0; + } + + ret = krb5_libdefault_string(context, realm, "preferred_preauth_types", + &preauth_types); + if ((ret != 0) || (preauth_types == NULL)) { + /* Try to use PKINIT first. */ + preauth_types = "17, 16, 15, 14"; + } + +#ifdef DEBUG + fprintf (stderr, "preauth data types before sorting:"); + for (i = 0; padata[i]; i++) { + fprintf (stderr, " %d", padata[i]->pa_type); + } + fprintf (stderr, "\n"); +#endif + + base = 0; + for (p = preauth_types; *p != '\0'; p++) { + /* skip whitespace to find an entry */ + p += strspn(p, ", "); + if (*p != '\0') { + /* see if we can extract a number */ + l = strtol(p, &q, 10); + if ((q != NULL) && (q > p)) { + /* got a valid number; search for a matchin entry */ + for (i = base; padata[i] != NULL; i++) { + /* bubble the matching entry to the front of the list */ + if (padata[i]->pa_type == l) { + tmp = padata[i]; + for (j = i; j > base; j--) + padata[j] = padata[j - 1]; + padata[base] = tmp; + base++; + break; + } + } + p = q; + } else { + break; + } + } + } + +#ifdef DEBUG + fprintf (stderr, "preauth data types after sorting:"); + for (i = 0; padata[i]; i++) + fprintf (stderr, " %d", padata[i]->pa_type); + fprintf (stderr, "\n"); +#endif + + return 0; +} + krb5_error_code KRB5_CALLCONV krb5_get_init_creds(krb5_context context, krb5_creds *creds, @@ -762,7 +847,8 @@ krb5_get_init_creds(krb5_context context, { krb5_error_code ret; krb5_kdc_req request; - krb5_pa_data **padata; + krb5_data *encoded_request_body, *encoded_previous_request; + krb5_pa_data **preauth_to_use, **kdc_padata; int tempint; char *tempstr; krb5_deltat tkt_life; @@ -775,6 +861,7 @@ krb5_get_init_creds(krb5_context context, krb5_kdc_rep *local_as_reply; krb5_timestamp time_now; krb5_enctype etype = 0; + krb5_preauth_context *preauth_context; /* initialize everything which will be freed at cleanup */ @@ -784,19 +871,28 @@ krb5_get_init_creds(krb5_context context, request.ktype = NULL; request.addresses = NULL; request.padata = NULL; - padata = NULL; + encoded_request_body = NULL; + encoded_previous_request = NULL; + preauth_to_use = NULL; + kdc_padata = NULL; as_key.length = 0; salt.length = 0; salt.data = NULL; local_as_reply = 0; + preauth_context = NULL; + err_reply = NULL; + /* * Set up the basic request structure */ request.magic = KV5M_KDC_REQ; request.msg_type = KRB5_AS_REQ; + /* request.nonce is filled in when we send a request to the kdc */ + request.nonce = 0; + /* request.padata is filled in later */ request.kdc_options = context->kdc_default_options; @@ -921,7 +1017,9 @@ krb5_get_init_creds(krb5_context context, goto cleanup; } - /* nonce is filled in by send_as_request */ + krb5_init_preauth_context(context, &preauth_context); + + /* nonce is filled in by send_as_request if we don't take care of it */ if (options && (options->flags & KRB5_GET_INIT_CREDS_OPT_ETYPE_LIST)) { request.ktype = options->etype_list; @@ -960,8 +1058,8 @@ krb5_get_init_creds(krb5_context context, 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))) + options->preauth_list_length, + &preauth_to_use))) goto cleanup; } @@ -975,44 +1073,106 @@ krb5_get_init_creds(krb5_context context, salt.data = NULL; } - /* now, loop processing preauth data and talking to the kdc */ + /* set the request nonce */ + if ((ret = krb5_timeofday(context, &time_now))) + goto cleanup; + /* + * XXX we know they are the same size... and we should do + * something better than just the current time + */ + request.nonce = (krb5_int32) time_now; + + /* give the preauth plugins a chance to prep the request body */ + krb5_preauth_prepare_request(context, &preauth_context, options, &request); + ret = encode_krb5_kdc_req_body(&request, &encoded_request_body); + if (ret) + goto cleanup; + + /* 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 (!err_reply) { + /* either our first attempt, or retrying after PREAUTH_NEEDED */ + if (request.padata) { + krb5_free_pa_data(context, request.padata); + request.padata = NULL; + } + if ((ret = krb5_do_preauth(context, &preauth_context, + &request, + encoded_request_body, + encoded_previous_request, + preauth_to_use, &request.padata, + &salt, &s2kparams, &etype, &as_key, + prompter, prompter_data, + gak_fct, gak_data))) + goto cleanup; + } else { + /* retrying after an error other than PREAUTH_NEEDED, using e-data + * to figure out what to change */ + if (krb5_do_preauth_tryagain(context, &preauth_context, + &request, + encoded_request_body, + encoded_previous_request, + preauth_to_use, err_reply, + &request.padata, + &salt, &s2kparams, + &etype, &as_key, + prompter, prompter_data, + gak_fct, gak_data)) { + /* couldn't come up with anything better */ + ret = err_reply->error + ERROR_TABLE_BASE_krb5; + krb5_free_error(context, err_reply); + err_reply = NULL; + goto cleanup; + } + krb5_free_error(context, err_reply); + err_reply = NULL; } - if ((ret = krb5_do_preauth(context, &request, - padata, &request.padata, - &salt, &s2kparams, &etype, &as_key, prompter, - prompter_data, gak_fct, gak_data))) + if (encoded_previous_request != NULL) { + krb5_free_data(context, encoded_previous_request); + encoded_previous_request = NULL; + } + ret = encode_krb5_as_req(&request, &encoded_previous_request); + if (ret) 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, + if ((ret = send_as_request(context, &request, &err_reply, &local_as_reply, use_master))) goto cleanup; if (err_reply) { if (err_reply->error == KDC_ERR_PREAUTH_REQUIRED && err_reply->e_data.length > 0) { + /* reset the list of preauth types to try */ + if (preauth_to_use) { + krb5_free_pa_data(context, preauth_to_use); + preauth_to_use = NULL; + } ret = decode_krb5_padata_sequence(&err_reply->e_data, - &padata); + &preauth_to_use); krb5_free_error(context, err_reply); + err_reply = NULL; + if (ret) + goto cleanup; + ret = sort_krb5_padata_sequence(context, + &request.server->realm, + preauth_to_use); if (ret) goto cleanup; + /* continue to next iteration */ } else { - ret = (krb5_error_code) err_reply->error - + ERROR_TABLE_BASE_krb5; - krb5_free_error(context, err_reply); - goto cleanup; + if (err_reply->e_data.length > 0) { + /* continue to next iteration */ + } else { + /* error + no hints = give up */ + ret = (krb5_error_code) err_reply->error + + ERROR_TABLE_BASE_krb5; + krb5_free_error(context, err_reply); + goto cleanup; + } } } else if (local_as_reply) { break; @@ -1028,16 +1188,18 @@ krb5_get_init_creds(krb5_context context, } /* process any preauth data in the as_reply */ - - if ((ret = krb5_do_preauth(context, &request, - local_as_reply->padata, &padata, + krb5_clear_preauth_context_use_counts(context, preauth_context); + if ((ret = sort_krb5_padata_sequence(context, &request.server->realm, + local_as_reply->padata))) + goto cleanup; + if ((ret = krb5_do_preauth(context, &preauth_context, + &request, + encoded_request_body, encoded_previous_request, + local_as_reply->padata, &kdc_padata, &salt, &s2kparams, &etype, &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 For 1.1.1 and prior KDC's, when SAM is used w/ USE_SAD_AS_KEY, the AS_REP comes back encrypted in the user's longterm key instead of in the SAD. If there was a SAM preauth, there @@ -1090,6 +1252,18 @@ krb5_get_init_creds(krb5_context context, ret = 0; cleanup: + if (preauth_context != NULL) { + krb5_free_preauth_context(context, preauth_context); + preauth_context = NULL; + } + if (encoded_previous_request != NULL) { + krb5_free_data(context, encoded_previous_request); + encoded_previous_request = NULL; + } + if (encoded_request_body != NULL) { + krb5_free_data(context, encoded_request_body); + encoded_request_body = NULL; + } if (request.server) krb5_free_principal(context, request.server); if (request.ktype && @@ -1099,8 +1273,10 @@ cleanup: (!(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 (preauth_to_use) + krb5_free_pa_data(context, preauth_to_use); + if (kdc_padata) + krb5_free_pa_data(context, kdc_padata); if (request.padata) krb5_free_pa_data(context, request.padata); if (as_key.length) diff --git a/src/lib/krb5/krb/init_ctx.c b/src/lib/krb5/krb/init_ctx.c index 8e4ce8c3b..46c3068ee 100644 --- a/src/lib/krb5/krb/init_ctx.c +++ b/src/lib/krb5/krb/init_ctx.c @@ -534,6 +534,8 @@ krb5_copy_context(krb5_context ctx, krb5_context *nctx_out) nctx->prompt_types = NULL; nctx->os_context->default_ccname = NULL; + memset(&nctx->preauth_plugins, 0, sizeof(nctx->preauth_plugins)); + memset(&nctx->libkrb5_plugins, 0, sizeof(nctx->libkrb5_plugins)); nctx->vtbl = NULL; nctx->locate_fptrs = NULL; diff --git a/src/lib/krb5/krb/preauth2.c b/src/lib/krb5/krb/preauth2.c index e146c3d3a..c218389c7 100644 --- a/src/lib/krb5/krb/preauth2.c +++ b/src/lib/krb5/krb/preauth2.c @@ -30,6 +30,16 @@ */ #include "k5-int.h" +#include "osconf.h" +#include <krb5/preauth_plugin.h> + +#include <unistd.h> + +#if TARGET_OS_MAC +static const char *objdirs[] = { KRB5_PLUGIN_BUNDLE_DIR, LIBDIR "/krb5/plugins/preauth", NULL }; /* should be a list */ +#else +static const char *objdirs[] = { LIBDIR "/krb5/plugins/preauth", NULL }; +#endif typedef krb5_error_code (*pa_function)(krb5_context, krb5_kdc_req *request, @@ -49,8 +59,396 @@ typedef struct _pa_types_t { int flags; } pa_types_t; -#define PA_REAL 0x0001 -#define PA_INFO 0x0002 +/* This structure lets us keep track of all of the modules which are loaded, + * turning the list of modules and their lists of implemented preauth types + * into a single list which we can walk easily. */ +struct _krb5_preauth_context { + int n_modules; + struct _krb5_preauth_context_module { + /* Which of the possibly more than one preauth types which the + * module supports we're using at this point in the list. */ + krb5_preauthtype pa_type; + /* Encryption types which the client claims to support -- we + * copy them directly into the krb5_kdc_req structure during + * krb5_preauth_prepare_request(). */ + krb5_enctype *enctypes; + /* The module's per-module context and a function to clear it. */ + void *module_context; + void (*client_fini)(krb5_context context, krb5_preauthtype pa_type, + void *module_context); + /* The module's table, and some of its members, copied here for + * convenience when we populated the list. */ + struct krb5plugin_preauth_ftable_v0 *ftable; + const char *name; + int flags, use_count; + krb5_error_code (*client_process)(krb5_context context, + void *module_context, + void **request_context, + krb5_kdc_req *request, + krb5_data *encoded_request_body, + krb5_data *encoded_previous_request, + krb5_pa_data *pa_data, + krb5_prompter_fct prompter, + void *prompter_data, + preauth_get_as_key_proc gak_fct, + krb5_data *salt, + krb5_data *s2kparams, + void *gak_data, + krb5_keyblock *as_key, + krb5_pa_data **out_pa_data); + krb5_error_code (*client_tryagain)(krb5_context context, + void *module_context, + void **request_context, + krb5_kdc_req *request, + krb5_data *encoded_request_body, + krb5_error *err_reply, + krb5_pa_data *old_pa_data, + krb5_pa_data **new_pa_data); + void (*client_cleanup)(krb5_context context, void *module_context, + void **request_context); + /* The per-pa_type context which the client_process() function + * might allocate, which we'll need to clean up later by + * calling the client_cleanup() function. */ + void *request_context; + } *modules; +}; + +/* Create the per-AS-REQ context. This means loading the modules if we haven't + * done that yet (applications which never obtain initial credentials should + * never hit this routine), breaking up the module's list of support pa_types + * so that we can iterate over the modules more easily, and copying over the + * relevant parts of the module's table. */ +void +krb5_init_preauth_context(krb5_context kcontext, + krb5_preauth_context **preauth_context) +{ + int n_modules, n_tables, i, j, k; + void **tables; + struct krb5plugin_preauth_ftable_v0 *table; + krb5_preauth_context *context; + void *module_context; + krb5_preauthtype pa_type; + + /* load the plugins for the current context */ + if (PLUGIN_DIR_OPEN(&kcontext->preauth_plugins) == 0) { + if (krb5int_open_plugin_dirs(objdirs, NULL, + &kcontext->preauth_plugins, + &kcontext->err) != 0) { + return; + } + } + + /* pull out the module function tables for all of the modules */ + tables = NULL; + if (krb5int_get_plugin_dir_data(&kcontext->preauth_plugins, + "preauthentication0", + &tables, + &kcontext->err) != 0) { + return; + } + if (tables == NULL) { + return; + } + + /* count how many modules we ended up loading, and how many preauth + * types we may claim to support as a result */ + n_modules = 0; + for (n_tables = 0; + (tables != NULL) && (tables[n_tables] != NULL); + n_tables++) { + table = tables[n_tables]; + if ((table->client_pa_type_list != NULL) && + (table->client_process != NULL)) { + for (j = 0; table->client_pa_type_list[j] > 0; j++) { + n_modules++; + } + } + } + + /* allocate the space we need */ + context = malloc(sizeof(*context)); + if (context == NULL) { + return; + } + context->modules = malloc(sizeof(context->modules[0]) * n_modules); + if (context->modules == NULL) { + free(context); + return; + } + memset(context->modules, 0, sizeof(context->modules[0]) * n_modules); + context->n_modules = n_modules; + + /* fill in the structure */ + k = 0; + for (i = 0; i < n_tables; i++) { + table = tables[i]; + if ((table->client_pa_type_list != NULL) && + (table->client_process != NULL)) { + for (j = 0; table->client_pa_type_list[j] > 0; j++) { + pa_type = table->client_pa_type_list[j]; + module_context = NULL; + if ((table->client_init != NULL) && + ((*table->client_init)(kcontext, pa_type, + &module_context) != 0)) { +#ifdef DEBUG + fprintf (stderr, "skip module \"%s\", pa_type %d\n", + table->name, pa_type); +#endif + continue; + } + context->modules[k].pa_type = pa_type; + context->modules[k].enctypes = table->client_enctype_list; + context->modules[k].module_context = module_context; + context->modules[k].client_fini = table->client_fini; + context->modules[k].ftable = table; + context->modules[k].name = table->name; + context->modules[k].flags = (*table->client_flags)(kcontext, + pa_type); + context->modules[k].use_count = 0; + context->modules[k].client_process = table->client_process; + context->modules[k].client_tryagain = table->client_tryagain; + context->modules[k].client_cleanup = table->client_cleanup; + context->modules[k].request_context = NULL; +#ifdef DEBUG + fprintf (stderr, "init module \"%s\", pa_type %d, flag %d\n", + context->modules[k].name, + context->modules[k].pa_type, + context->modules[k].flags); +#endif + k++; + } + } + } + + /* return the result */ + *preauth_context = context; +} + +/* Zero the use counts for the modules herein. Usually used before we + * start processing any data from the server, at which point every module + * will again be able to take a crack at whatever the server sent. */ +void +krb5_clear_preauth_context_use_counts(krb5_context context, + krb5_preauth_context *preauth_context) +{ + int i; + if (preauth_context != NULL) { + for (i = 0; i < preauth_context->n_modules; i++) { + preauth_context->modules[i].use_count = 0; + } + } +} + +/* Free the per-AS-REQ context. This means clearing any module-specific or + * request-specific context which the modules may have created, and then + * freeing the context itself. */ +void +krb5_free_preauth_context(krb5_context context, + krb5_preauth_context *preauth_context) +{ + int i; + krb5_preauthtype pa_type; + void **rctx, *mctx; + if (preauth_context != NULL) { + for (i = 0; i < preauth_context->n_modules; i++) { + mctx = preauth_context->modules[i].module_context; + if (preauth_context->modules[i].request_context != NULL) { + if (preauth_context->modules[i].client_cleanup != NULL) { + rctx = &preauth_context->modules[i].request_context; + preauth_context->modules[i].client_cleanup(context, + mctx, rctx); + } + preauth_context->modules[i].request_context = NULL; + } + if (preauth_context->modules[i].client_fini != NULL) { + pa_type = preauth_context->modules[i].pa_type; + (*preauth_context->modules[i].client_fini)(context, pa_type, + mctx); + } + memset(&preauth_context->modules[i], 0, + sizeof(preauth_context->modules[i])); + } + if (preauth_context->modules != NULL) { + free(preauth_context->modules); + preauth_context->modules = NULL; + } + free(preauth_context); + } +} + +/* Add the named encryption type to the existing list of ktypes. */ +static void +grow_ktypes(krb5_enctype **out_ktypes, int *out_nktypes, krb5_enctype ktype) +{ + int i; + krb5_enctype *ktypes; + for (i = 0; i < *out_nktypes; i++) { + if ((*out_ktypes)[i] == ktype) + return; + } + ktypes = malloc((*out_nktypes + 2) * sizeof(ktype)); + if (ktypes) { + for (i = 0; i < *out_nktypes; i++) + ktypes[i] = (*out_ktypes)[i]; + ktypes[i++] = ktype; + ktypes[i] = 0; + free(*out_ktypes); + *out_ktypes = ktypes; + *out_nktypes = i; + } +} + +/* Add the given pa_data item to the list of items. Factored out here to make + * reading the do_preauth logic easier to read. */ +static int +grow_pa_list(krb5_pa_data ***out_pa_list, int *out_pa_list_size, + krb5_pa_data *addition) +{ + krb5_pa_data **pa_list; + int i; + + if (out_pa_list == NULL) { + return EINVAL; + } + + if (*out_pa_list == NULL) { + /* Allocate room for one entry and a NULL terminator. */ + pa_list = malloc(2 * sizeof(krb5_pa_data *)); + if (pa_list == NULL) + return ENOMEM; + pa_list[0] = addition; + pa_list[1] = NULL; + *out_pa_list = pa_list; + *out_pa_list_size = 1; + } else { + /* Allocate room for one more entry and a NULL terminator. */ + pa_list = malloc((*out_pa_list_size + 2) * sizeof(krb5_pa_data *)); + if (pa_list == NULL) + return ENOMEM; + for (i = 0; i < *out_pa_list_size; i++) + pa_list[i] = (*out_pa_list)[i]; + pa_list[i++] = addition; + pa_list[i++] = NULL; + free(*out_pa_list); + *out_pa_list = pa_list; + *out_pa_list_size = i; + } + return 0; +} + +/* Tweak the request body, for now adding any enctypes which the module claims + * to add support for to the list, but in the future perhaps doing more + * involved things. */ +void +krb5_preauth_prepare_request(krb5_context kcontext, + krb5_preauth_context **preauth_context, + krb5_get_init_creds_opt *options, + krb5_kdc_req *request) +{ + int i, j, k; + krb5_enctype *ktypes; + + if ((preauth_context == NULL) || (*preauth_context == NULL)) { + return; + } + /* Add the module-specific enctype list to the request, but only if + * it's something we can safely modify. */ + if (!(options && (options->flags & KRB5_GET_INIT_CREDS_OPT_ETYPE_LIST))) { + for (i = 0; i < (*preauth_context)->n_modules; i++) { + if ((*preauth_context)->modules[i].enctypes == NULL) + continue; + for (j = 0; (*preauth_context)->modules[i].enctypes[j] != 0; j++) { + grow_ktypes(&request->ktype, &request->nktypes, + (*preauth_context)->modules[i].enctypes[j]); + } + } + } +} + +/* Find the first module which provides for the named preauth type which also + * hasn't had a chance to run yet (INFO modules don't count, because as a rule + * they don't generate preauth data), and run it. */ +static krb5_error_code +krb5_run_preauth_plugins(krb5_context kcontext, + krb5_preauth_context *preauth_context, + int module_required_flags, + krb5_kdc_req *request, + krb5_data *encoded_request_body, + krb5_data *encoded_previous_request, + krb5_pa_data *in_padata, + krb5_prompter_fct prompter, + void *prompter_data, + preauth_get_as_key_proc gak_fct, + krb5_data *salt, + krb5_data *s2kparams, + void *gak_data, + krb5_keyblock *as_key, + krb5_pa_data ***out_pa_list, + int *out_pa_list_size, + int *module_ret, + int *module_flags) +{ + int i; + krb5_pa_data *out_pa_data; + krb5_error_code ret; + struct _krb5_preauth_context_module *module; + + if (preauth_context == NULL) { + return ENOENT; + } + /* iterate over all loaded modules */ + for (i = 0; i < preauth_context->n_modules; i++) { + module = &preauth_context->modules[i]; + /* skip over those which don't match the preauth type */ + if (module->pa_type != in_padata->pa_type) + continue; + /* skip over those which don't match the flags (INFO vs REAL, mainly) */ + if ((module->flags & module_required_flags) == 0) + continue; + /* if it's a REAL module, try to call it only once per library call */ + if (module_required_flags & PA_REAL) { + if (module->use_count > 0) { +#ifdef DEBUG + fprintf(stderr, "skipping already-used module \"%s\"(%d)\n", + module->name, module->pa_type); +#endif + continue; + } + module->use_count++; + } + /* run the module's callback function */ + out_pa_data = NULL; +#ifdef DEBUG + fprintf(stderr, "using module \"%s\" (%d), flags = %d\n", + module->name, module->pa_type, module->flags); +#endif + ret = module->client_process(kcontext, + module->module_context, + &module->request_context, + request, + encoded_request_body, + encoded_previous_request, + in_padata, + prompter, prompter_data, + gak_fct, salt, s2kparams, gak_data, + as_key, + &out_pa_data); + /* Make note of the module's flags and status. */ + *module_flags = module->flags; + *module_ret = ret; + /* Save the new preauth data item. */ + if (out_pa_data != NULL) { + ret = grow_pa_list(out_pa_list, out_pa_list_size, out_pa_data); + if (ret != 0) + return ret; + } + break; + } + if (i >= preauth_context->n_modules) { + return ENOENT; + } + return 0; +} static krb5_error_code pa_salt(krb5_context context, @@ -819,9 +1217,70 @@ static const pa_types_t pa_types[] = { }, }; +/* + * If one of the modules can adjust its AS_REQ data using the contents of the + * err_reply, return 0. If it's the sort of correction which requires that we + * ask the user another question, we let the calling application deal with it. + */ +krb5_error_code +krb5_do_preauth_tryagain(krb5_context kcontext, + krb5_preauth_context **preauth_context, + krb5_kdc_req *request, + krb5_data *encoded_request_body, + krb5_error *err_reply, krb5_pa_data **padata) +{ + krb5_error_code ret; + krb5_pa_data *out_padata; + krb5_preauth_context *context; + struct _krb5_preauth_context_module *module; + int i, j; + + ret = KRB_ERR_GENERIC; + if (preauth_context == NULL) { + return KRB_ERR_GENERIC; + } + context = *preauth_context; + if (context == NULL) { + return KRB_ERR_GENERIC; + } + + for (i = 0; padata[i]->pa_type != 0; i++) { + out_padata = NULL; + for (j = 0; j < context->n_modules; j++) { + module = &context->modules[j]; + if (module->pa_type != padata[i]->pa_type) { + continue; + } + if (module->client_tryagain == NULL) { + continue; + } + if ((*module->client_tryagain)(kcontext, + module->module_context, + module->request_context, + request, + encoded_request_body, + err_reply, + padata[i], + &out_padata) == 0) { + if (out_padata != NULL) { + if (padata[i]->contents != NULL) + free(padata[i]->contents); + free(padata[i]); + padata[i] = out_padata; + return 0; + } + } + } + } + return ret; +} + krb5_error_code krb5_do_preauth(krb5_context context, + krb5_preauth_context **preauth_context, krb5_kdc_req *request, + krb5_data *encoded_request_body, + krb5_data *encoded_previous_request, krb5_pa_data **in_padata, krb5_pa_data ***out_padata, krb5_data *salt, krb5_data *s2kparams, krb5_enctype *etype, @@ -967,9 +1426,14 @@ krb5_do_preauth(krb5_context context, default: ; } - for (j=0; pa_types[j].type >= 0; j++) { + /* Try the internally-provided preauth type list. */ + if (!realdone) 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])) { +#ifdef DEBUG + fprintf (stderr, "calling internal function for pa_type " + "%d, flag %d\n", pa_types[j].type, paorder[h]); +#endif out_pa = NULL; if ((ret = ((*pa_types[j].fct)(context, request, @@ -980,41 +1444,56 @@ krb5_do_preauth(krb5_context context, goto cleanup; } - if (out_pa) { - if (out_pa_list == NULL) { - if ((out_pa_list = - (krb5_pa_data **) - malloc(2*sizeof(krb5_pa_data *))) - == NULL) { - ret = ENOMEM; - goto cleanup; - } - } 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. */ - ret = ENOMEM; - goto cleanup; - } - } - - out_pa_list[out_pa_list_size++] = out_pa; + ret = grow_pa_list(&out_pa_list, &out_pa_list_size, + out_pa); + if (ret != 0) { + goto cleanup; } if (paorder[h] == PA_REAL) realdone = 1; } } + + /* Try to use plugins now. */ + if ((!realdone) && (preauth_context != NULL)) { + if (*preauth_context == NULL) { + krb5_init_preauth_context(context, preauth_context); + } + if (*preauth_context != NULL) { + int module_ret, module_flags; +#ifdef DEBUG + fprintf (stderr, "trying modules for pa_type %d, flag %d\n", + in_padata[i]->pa_type, paorder[h]); +#endif + ret = krb5_run_preauth_plugins(context, + *preauth_context, + paorder[h], + request, + encoded_request_body, + encoded_previous_request, + in_padata[i], + prompter, + prompter_data, + gak_fct, + salt, s2kparams, + gak_data, + as_key, + &out_pa_list, + &out_pa_list_size, + &module_ret, + &module_flags); + if (ret == 0) { + if (module_ret == 0) { + if (paorder[h] == PA_REAL) { + realdone = 1; + } + } + } + } + } } } - if (out_pa_list) - out_pa_list[out_pa_list_size++] = NULL; - *out_padata = out_pa_list; if (etype_info) krb5_free_etype_info(context, etype_info); diff --git a/src/lib/krb5/os/init_os_ctx.c b/src/lib/krb5/os/init_os_ctx.c index 893355ef9..bc7e007ff 100644 --- a/src/lib/krb5/os/init_os_ctx.c +++ b/src/lib/krb5/os/init_os_ctx.c @@ -391,6 +391,7 @@ krb5_os_init_context(krb5_context ctx, krb5_boolean kdc) ctx->vtbl = 0; PLUGIN_DIR_INIT(&ctx->libkrb5_plugins); + PLUGIN_DIR_INIT(&ctx->preauth_plugins); retval = os_init_paths(ctx, kdc); /* @@ -492,6 +493,7 @@ krb5_os_free_context(krb5_context ctx) ctx->profile = 0; } + krb5int_close_plugin_dirs (&ctx->preauth_plugins); krb5int_close_plugin_dirs (&ctx->libkrb5_plugins); #ifdef _WIN32 |
