summaryrefslogtreecommitdiffstats
path: root/src/lib
diff options
context:
space:
mode:
authorSam Hartman <hartmans@mit.edu>2006-10-03 19:07:17 +0000
committerSam Hartman <hartmans@mit.edu>2006-10-03 19:07:17 +0000
commit63a8ab15aa5ee116b26a50c073fe8ee33e147cbd (patch)
tree40296b89921ea7edb3117c4d38c4132494d4ce06 /src/lib
parent7f7a4fff296db90d36c39fb01dd35b61bdd6a2b0 (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.et34
-rw-r--r--src/lib/krb5/krb/Makefile.in2
-rw-r--r--src/lib/krb5/krb/get_in_tkt.c262
-rw-r--r--src/lib/krb5/krb/init_ctx.c2
-rw-r--r--src/lib/krb5/krb/preauth2.c539
-rw-r--r--src/lib/krb5/os/init_os_ctx.c2
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